Files
mesh-controller/internal/catalogue/zones.go
T
jschoubben e56416fa8c The mesh's one resolver: its seat, a provider's address, zones, and a node's hosts file (hq ADR 0194, 0196, 0199)
- 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.
2026-10-04 00:38:48 +02:00

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
}