A carried peer is nameable, and the mesh answers for it (hq 112)

The tunnel the hub took over routes to machines the predecessor knows
by name and the mesh knew only by address — taking the resolver in that
state silences three machines at once. Now the operator states which
machine a carried address is (overlay name <address> <name>), the
statement rides tunnel_peer.named, and namesInTheMesh answers for named
not-yet-enrolled peers — one reading, so the hosts fact, a container's
hosts and the resolver cannot disagree. Enrolment verifies the word:
a machine enrolling under a named peer's key with a different name is
refused where the operator can read it, the stated name keeps the
carried address, and an enrolled peer's name is the node's — naming it
again refuses. The issue's rule holds: a name the predecessor answers
for keeps resolving until the machine behind it is a node.
This commit is contained in:
2026-09-26 20:09:42 +02:00
parent 50734095b8
commit 952092ccb3
5 changed files with 195 additions and 4 deletions
+45 -2
View File
@@ -85,6 +85,9 @@ type CarriedPeer struct {
Address string
// EnrolledAs names the node that enrolled with this key, or is empty while none has.
EnrolledAs string
// Named is what the operator said this peer is, before it enrolled (novox/hq issue 112) —
// a statement the mesh records and cannot verify, which is why enrolment checks it.
Named string
}
// ErrNoTunnel is asking about a tunnel on a node that presented none.
@@ -247,7 +250,7 @@ func (i *Inventory) AdoptedTunnel(ctx context.Context) (Tunnel, string, bool, er
// adopted no tunnel.
func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
rows, err := i.store.Pool().Query(ctx,
`select p.public_key, host(p.address), coalesce(n.name, '')
`select p.public_key, host(p.address), coalesce(n.name, ''), coalesce(p.named, '')
from tunnel_peer p
join node hub on hub.id = p.node and hub.is_hub
and hub.tunnel is not null and hub.overlay_key = hub.tunnel->>'public_key'
@@ -260,7 +263,7 @@ func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
var out []CarriedPeer
for rows.Next() {
var p CarriedPeer
if err := rows.Scan(&p.PublicKey, &p.Address, &p.EnrolledAs); err != nil {
if err := rows.Scan(&p.PublicKey, &p.Address, &p.EnrolledAs, &p.Named); err != nil {
return nil, err
}
out = append(out, p)
@@ -268,6 +271,46 @@ func (i *Inventory) CarriedPeers(ctx context.Context) ([]CarriedPeer, error) {
return out, rows.Err()
}
// NamePeer records the operator's statement that a carried address is a particular machine
// (novox/hq issue 112). Refused when nothing carried has that address, when a node of the mesh
// already has the name — the statement would collide with something verified — and when another
// peer was already named it. Naming an enrolled peer is refused too: its name is the node's now.
func (i *Inventory) NamePeer(ctx context.Context, address, name string) error {
peers, err := i.CarriedPeers(ctx)
if err != nil {
return err
}
var at *CarriedPeer
for idx := range peers {
if peers[idx].Address == address {
at = &peers[idx]
continue
}
if peers[idx].Named == name {
return fmt.Errorf("the carried peer at %s is already named %q — one machine per name",
peers[idx].Address, name)
}
}
if at == nil {
return fmt.Errorf("no carried peer has the address %s — `overlay show` lists them", address)
}
if at.EnrolledAs != "" {
return fmt.Errorf("the peer at %s enrolled as %q — its name is the node's now", address, at.EnrolledAs)
}
if _, err := i.NodeByName(ctx, name); err == nil {
return fmt.Errorf("%q is a node of this mesh — a carried peer cannot be named after one", name)
}
tag, err := i.store.Pool().Exec(ctx,
`update tunnel_peer set named = $2 where host(address) = $1`, address, name)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("no carried peer has the address %s", address)
}
return nil
}
// FoundTunnel is a node's found tunnel with the node's mode, for composing: the takeover is
// declared to an adopted node only, since only there is a found unit kept to be stopped.
type FoundTunnel struct {