mesh/merge-gate pass: builds networkmanager, systemd-networkd, sent nowhere; no bus step; 4 wait(s) for a person; every machine composes with the change as…
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery delivered
mesh/delivery-group group feat/node-uplink-verbs delivered: every member is delivered
What a machine resolves through had no tool: the resolver file and who wrote it, and every link with its default route and the resolvers its manager knows. Each holder serves the same two node-uplink verbs; the reading is one text carried by both, held to it by a test, and only asking the manager for a link's names is each holder's own.
68 lines
2.0 KiB
Go
68 lines
2.0 KiB
Go
package main
|
|
|
|
// systemd-networkd's word on each link's names: `networkctl status --all`, each link's block read for its
|
|
// DNS servers and search domains, which networkd prints whether or not systemd-resolved runs. A link
|
|
// networkd does not manage — a tunnel a VPN client raised with `ip`, the mesh's own — says none, and the
|
|
// links verb says the link without them.
|
|
|
|
import (
|
|
"context"
|
|
"regexp"
|
|
"strings"
|
|
)
|
|
|
|
// keyed is a line that names its key: a capitalised name of words, a colon, the value.
|
|
var keyed = regexp.MustCompile(`^([A-Z][A-Za-z0-9 ]*): (.*)$`)
|
|
|
|
func managerDNS(ctx context.Context) (map[string]LinkDNS, error) {
|
|
out, err := execRunner(ctx, "networkctl", "status", "--all", "--no-pager", "--no-legend")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return parseNetworkctl(out), nil
|
|
}
|
|
|
|
// parseNetworkctl reads `networkctl status --all`: a block per link, headed "● <index>: <name>", whose
|
|
// "Key: value" lines continue on indented lines with no key.
|
|
func parseNetworkctl(out string) map[string]LinkDNS {
|
|
links := map[string]LinkDNS{}
|
|
device, key := "", ""
|
|
var cur LinkDNS
|
|
flush := func() {
|
|
if device != "" && (len(cur.Servers) > 0 || len(cur.Domains) > 0) {
|
|
cur.Manager = "systemd-networkd"
|
|
links[device] = cur
|
|
}
|
|
device, key, cur = "", "", LinkDNS{}
|
|
}
|
|
for _, line := range strings.Split(out, "\n") {
|
|
trimmed := strings.TrimSpace(line)
|
|
if strings.HasPrefix(trimmed, "●") {
|
|
flush()
|
|
if _, name, ok := strings.Cut(strings.TrimSpace(strings.TrimPrefix(trimmed, "●")), ": "); ok {
|
|
device = strings.TrimSpace(name)
|
|
}
|
|
continue
|
|
}
|
|
if trimmed == "" {
|
|
key = ""
|
|
continue
|
|
}
|
|
value := trimmed
|
|
if m := keyed.FindStringSubmatch(trimmed); m != nil {
|
|
key, value = m[1], strings.TrimSpace(m[2])
|
|
}
|
|
switch key {
|
|
case "DNS":
|
|
cur.Servers = append(cur.Servers, value)
|
|
case "Search Domains":
|
|
cur.Domains = append(cur.Domains, strings.Fields(value)...)
|
|
case "State":
|
|
cur.State = value
|
|
key = ""
|
|
}
|
|
}
|
|
flush()
|
|
return links
|
|
}
|