- mesh-dns-resolver: a mesh seat delivering wildcard-resolution, so every node's resolver
configuration resolves to its one holder; node-dns-resolver kept until nothing claims it.
- ${bound:<provision>:address}: the providing machine's private address, for the one consumer
that cannot use a name — a machine's resolver configuration.
- zone: a module declares the zone it answers and the listen that answers it; the controller
settles it per node, refuses duplicates and shadowing, and hands the resolver .Zones to forward.
- node-hosts-file: a node seat whose holder owns /etc/hosts, with entries/add/remove.
The resolver tests follow the catalogue: no runtime dns (containers copy the machine's resolvers),
live-restore held by resolv-conf, resolv.conf naming the resolver by address then a public one.
118 lines
4.1 KiB
Go
118 lines
4.1 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// Zones: names a module answers itself (novox/hq ADR 0199).
|
|
//
|
|
// The mesh's resolver holds each node's internal domain and nothing else (ADR 0191, 0194). A module
|
|
// whose names are its own — the lab's scenario machines, known only while a scenario runs — declares
|
|
// the zone it answers and the listen that answers it; the controller hands the resolver's holder every
|
|
// zone with the declaring node's private address and the port that listen is published on, and the
|
|
// holder forwards the zone there. **A definition names no address** (ADR 0112): the zone is a setting,
|
|
// the listen is the module's own, and where they are is the mesh's fact.
|
|
|
|
// Zone is the manifest's declaration that a module answers the names in one zone.
|
|
type Zone struct {
|
|
// Name is the zone: a label or a dotted name, normally `${setting:<key>}`, so the operator chooses
|
|
// it and the definition does not.
|
|
Name string `json:"name"`
|
|
// Listen names one of the module's listens: the DNS answerer for the zone.
|
|
Listen string `json:"listen"`
|
|
}
|
|
|
|
// ZoneAt is a declared zone where the mesh placed it: what the resolver's holder forwards, and where.
|
|
type ZoneAt struct {
|
|
Zone string
|
|
Node string
|
|
Module string
|
|
Address string
|
|
Port int
|
|
}
|
|
|
|
// zoneProblems is what is wrong with a module's zone declaration on its own, before any node.
|
|
func zoneProblems(m Manifest) []string {
|
|
if m.Zone == nil {
|
|
return nil
|
|
}
|
|
var problems []string
|
|
if strings.TrimSpace(m.Zone.Name) == "" {
|
|
problems = append(problems, fmt.Sprintf("%s declares a zone with no name", m.Module))
|
|
}
|
|
if !m.hasListen(m.Zone.Listen) {
|
|
problems = append(problems, fmt.Sprintf(
|
|
"%s declares zone %q answered by listen %q, and has no listen of that name",
|
|
m.Module, m.Zone.Name, m.Zone.Listen))
|
|
}
|
|
return problems
|
|
}
|
|
|
|
func (m Manifest) hasListen(name string) bool {
|
|
if name == "" {
|
|
return false
|
|
}
|
|
for _, l := range m.Listens {
|
|
if l.Name == name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ZoneOn is one module's zone as one node places it: the name settled from the node's settings, the
|
|
// port its answering listen is published on there. Nothing when the module declares no zone.
|
|
func ZoneOn(m Manifest, layers []Layer, published map[int]int, node, address string) (*ZoneAt, error) {
|
|
if m.Zone == nil {
|
|
return nil, nil
|
|
}
|
|
settled, err := Settle(map[string]any{"zone": m.Zone.Name}, layers)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s's zone on %s: %w", m.Module, node, err)
|
|
}
|
|
zone := strings.Trim(strings.ToLower(fmt.Sprint(settled["zone"])), ".")
|
|
var port int
|
|
for _, l := range m.Listens {
|
|
if l.Name == m.Zone.Listen {
|
|
port = l.Port
|
|
if at, given := published[l.Port]; given {
|
|
port = at
|
|
}
|
|
}
|
|
}
|
|
return &ZoneAt{Zone: zone, Node: node, Module: m.Module, Address: address, Port: port}, nil
|
|
}
|
|
|
|
// ZonesProblems is what the mesh refuses about its zones together: one zone declared twice, a zone
|
|
// that is the mesh's suffix or under it, a zone that is a node's public domain or under one. A module
|
|
// may not shadow names the mesh's resolver or the public DNS answers.
|
|
func ZonesProblems(zones []ZoneAt, suffix string, publicDomains []string) []string {
|
|
var problems []string
|
|
under := func(zone, domain string) bool {
|
|
domain = strings.Trim(strings.ToLower(domain), ".")
|
|
return domain != "" && (zone == domain || strings.HasSuffix(zone, "."+domain))
|
|
}
|
|
seen := map[string]ZoneAt{}
|
|
for _, z := range zones {
|
|
if other, twice := seen[z.Zone]; twice && (other.Node != z.Node || other.Module != z.Module) {
|
|
problems = append(problems, fmt.Sprintf("zone %q is declared by %s on %s and by %s on %s; one module answers a zone",
|
|
z.Zone, other.Module, other.Node, z.Module, z.Node))
|
|
}
|
|
seen[z.Zone] = z
|
|
if under(z.Zone, suffix) {
|
|
problems = append(problems, fmt.Sprintf("%s on %s declares zone %q, which is the mesh's own suffix or under it",
|
|
z.Module, z.Node, z.Zone))
|
|
}
|
|
for _, d := range publicDomains {
|
|
if under(z.Zone, d) {
|
|
problems = append(problems, fmt.Sprintf("%s on %s declares zone %q, which is the public domain %q or under it",
|
|
z.Module, z.Node, z.Zone, d))
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(problems)
|
|
return problems
|
|
}
|