Names, from the same graph as the network
Step 5 of the connectivity order. Every node's internal name resolves to its overlay address, on every node, computed centrally because it needs every node at once. Under `.internal`, which IANA reserved for exactly this in 2024 -- a name there can never collide with a public one, so an internal name that leaks into a public resolver fails rather than reaching a stranger's machine. The suffix is settable for a mesh that wants its own. Delivered in the same declaration as the peer list rather than a second one. A node holding the peers and not the names, or the reverse, is half on the network for as long as that lasts. This is not the /etc/hosts floor the design removes. That floor existed because a node had to reach the mesh's database before its own DNS worked -- a fallback for a circularity that is now gone. This is the mechanism: the complete set of names, generated whole and owned by the mesh, rather than a patch written underneath something else. A resolver daemon becomes necessary when names are wanted that are not one-per-node, and that is not yet true. A node resolves its own name to its overlay address rather than a loopback, because a service binding to the name it was given would otherwise listen somewhere nothing else can reach -- and the failure would appear on every other machine rather than that one. A node with no address gets no name. A name resolving to nothing is worse than no name: connecting to an address that does not answer hangs, where a name that does not resolve fails at once and says which name it was. Found while writing it: a test asserting every file in the declaration is mode 0600 would have forced /etc/hosts to 0600 and broken every lookup on the machine, to protect a file that is not secret. Verified in the lab: three machines, nine name lookups, each resolving to the right overlay address and reaching it.
This commit is contained in:
@@ -34,7 +34,7 @@ type Resource map[string]any
|
||||
// Three resources and nothing clever: the tools, the configuration, and the interface running. A
|
||||
// person can read it, which is the point — this is the first thing a node is ever told, and if it
|
||||
// is wrong the node is unreachable and the mistake has to be findable by eye.
|
||||
func Declaration(node Node, peers []Peer, keyPath string) ([]byte, error) {
|
||||
func Declaration(node Node, peers []Peer, everyone []Node, keyPath string) ([]byte, error) {
|
||||
if node.Address == "" {
|
||||
return nil, fmt.Errorf("%s has no address on the overlay, so there is nothing to configure",
|
||||
node.Name)
|
||||
@@ -78,6 +78,18 @@ func Declaration(node Node, peers []Peer, keyPath string) ([]byte, error) {
|
||||
},
|
||||
}
|
||||
|
||||
// And the names, which come from the same graph and arrive in the same declaration. Separate
|
||||
// steps in the design and one delivery in practice: a node that had the peers and not the
|
||||
// names, or the reverse, would be half on the network for as long as that lasted.
|
||||
names, err := Hosts(everyone, node.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resources = append(resources, Resource{
|
||||
"id": "mesh-names", "type": "file", "path": HostsPath,
|
||||
"mode": "0644", "content": names,
|
||||
})
|
||||
|
||||
return json.Marshal(map[string]any{"declaration": 1, "resources": resources})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user