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.
130 lines
4.6 KiB
Go
130 lines
4.6 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/netip"
|
|
)
|
|
|
|
// The mesh assigns overlay addresses. A node computes nothing about the network it is joining —
|
|
// it generates a keypair, publishes the public half, and receives the rest
|
|
// (novox/hq 08-connectivity).
|
|
|
|
// AssignAddress gives a node its place on the private network, if it has none.
|
|
//
|
|
// Idempotent: a node that already has an address keeps it. Reassigning would change every other
|
|
// node's peer list to chase it, and an address that moves is the thing declaring the hub was
|
|
// meant to stop.
|
|
//
|
|
// **The adopted tunnel's addresses come first** (novox/hq ADR 0105). The hub that took over a
|
|
// tunnel is at the tunnel's own address. A node enrolling with a key the tunnel already routed
|
|
// to keeps the address the tunnel had for it — nothing a peer knows changes. And an address the
|
|
// tunnel holds for a peer that has not enrolled is never handed to anyone else: that peer is
|
|
// still reaching the hub at it.
|
|
//
|
|
// The rest is allocated in order from the range, taking the lowest free one. Not random: a person
|
|
// reading a peer list should be able to guess which node an address belongs to, and reuse of a
|
|
// released address is a smaller problem than a list nobody can hold in their head.
|
|
func (i *Inventory) AssignAddress(ctx context.Context, node, cidr string) (string, error) {
|
|
prefix, err := netip.ParsePrefix(cidr)
|
|
if err != nil {
|
|
return "", fmt.Errorf("%q is not a network the mesh can allocate from: %w", cidr, err)
|
|
}
|
|
|
|
var existing, key *string
|
|
var name string
|
|
var hub bool
|
|
if err := i.store.Pool().QueryRow(ctx,
|
|
`select name, host(overlay_address), overlay_key, is_hub from node where id = $1`, node).
|
|
Scan(&name, &existing, &key, &hub); err != nil {
|
|
return "", err
|
|
}
|
|
if existing != nil && *existing != "" {
|
|
return *existing, nil
|
|
}
|
|
|
|
tunnel, hubName, adopted, err := i.AdoptedTunnel(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if adopted && hub && hubName == name {
|
|
address, err := netip.ParsePrefix(tunnel.Address)
|
|
if err != nil {
|
|
return "", fmt.Errorf("the adopted tunnel's address %q: %w", tunnel.Address, err)
|
|
}
|
|
return i.place(ctx, node, address.Addr().String())
|
|
}
|
|
carried, err := i.CarriedPeers(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
taken := map[string]bool{}
|
|
if adopted {
|
|
// The tunnel's own address is the hub's whether or not the hub has been placed yet.
|
|
if address, err := netip.ParsePrefix(tunnel.Address); err == nil {
|
|
taken[address.Addr().String()] = true
|
|
}
|
|
}
|
|
for _, p := range carried {
|
|
if key != nil && p.PublicKey == *key {
|
|
// The tunnel already routes to this key: the node keeps that address, and the peer
|
|
// notices nothing when its machine enrols.
|
|
//
|
|
// **Unless the operator named it something else** (novox/hq issue 112). The name is
|
|
// what the mesh has been answering for this address in the meantime; a machine
|
|
// enrolling under a different one would silently split the two — the name resolving
|
|
// here, the node known as that — so it is refused where the operator can read it.
|
|
if p.Named != "" && p.Named != name {
|
|
return "", fmt.Errorf(
|
|
"the carried peer at %s was named %q, and %q is enrolling under its key — "+
|
|
"enrol it as %q, or rename the peer first (`overlay name`)",
|
|
p.Address, p.Named, name, p.Named)
|
|
}
|
|
return i.place(ctx, node, p.Address)
|
|
}
|
|
taken[p.Address] = true
|
|
}
|
|
|
|
rows, err := i.store.Pool().Query(ctx,
|
|
`select host(overlay_address) from node where overlay_address is not null`)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
for rows.Next() {
|
|
var a string
|
|
if err := rows.Scan(&a); err != nil {
|
|
rows.Close()
|
|
return "", err
|
|
}
|
|
taken[a] = true
|
|
}
|
|
rows.Close()
|
|
if err := rows.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// The first address in a range is conventionally the network itself and is skipped, so
|
|
// allocation starts one past it.
|
|
candidate := prefix.Masked().Addr().Next()
|
|
for prefix.Contains(candidate) {
|
|
if !taken[candidate.String()] {
|
|
return i.place(ctx, node, candidate.String())
|
|
}
|
|
candidate = candidate.Next()
|
|
}
|
|
// Said plainly rather than returning an empty address that fails later on a machine. A mesh
|
|
// that has outgrown its range needs a person, and renumbering is not something to attempt
|
|
// halfway through assigning one node.
|
|
return "", fmt.Errorf(
|
|
"every address in %s is taken, so %s cannot be given one. The mesh has outgrown its "+
|
|
"range and renumbering it is a deliberate act", cidr, name)
|
|
}
|
|
|
|
func (i *Inventory) place(ctx context.Context, node, address string) (string, error) {
|
|
if _, err := i.store.Pool().Exec(ctx,
|
|
`update node set overlay_address = $2::inet where id = $1`, node, address); err != nil {
|
|
return "", err
|
|
}
|
|
return address, nil
|
|
}
|