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 "● : ", 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 }