mirror of
https://github.com/fosrl/olm.git
synced 2026-07-15 13:32:22 -05:00
130 lines
4.4 KiB
Go
130 lines
4.4 KiB
Go
//go:build linux && !android
|
|
|
|
package dns
|
|
|
|
import (
|
|
"bufio"
|
|
"net"
|
|
"net/netip"
|
|
"os"
|
|
"strings"
|
|
|
|
platform "github.com/fosrl/olm/dns/platform"
|
|
)
|
|
|
|
// readSystemDNS returns the current system DNS servers in "host:53" format.
|
|
//
|
|
// Resolution order:
|
|
// 1. /run/systemd/resolve/resolv.conf, but only when systemd-resolved is
|
|
// actually running (checked live via D-Bus) — the file lives in /run and
|
|
// can linger there, frozen at whatever it last contained, long after the
|
|
// service that maintained it has stopped (e.g. it ran earlier in the
|
|
// boot and was since disabled). Trusting its mere existence would report
|
|
// that stale snapshot forever instead of falling through to a live
|
|
// source. When the service is actually up the file is maintained with
|
|
// the real per-link DNS servers, updated on every DHCP change and never
|
|
// touched by olm's D-Bus DNS override.
|
|
// 2. NetworkManager, queried live over D-Bus — NetworkManager's own view of
|
|
// each active connection's DNS servers, independent of what is currently
|
|
// written to /etc/resolv.conf. This covers NetworkManager's "dnsmasq" and
|
|
// "unbound" DNS modes, where /etc/resolv.conf only contains a loopback
|
|
// stub address, and stays accurate even if olm's own override has
|
|
// directly overwritten /etc/resolv.conf, without going stale the way a
|
|
// one-time backup snapshot would if the real DNS changes mid-override
|
|
// (e.g. the user switches WiFi networks). olm's own NetworkManager
|
|
// override is itself a NetworkManager-level global DNS override (see
|
|
// platform.GetNetworkManagerNameservers), so this also reads each
|
|
// device's raw DHCP lease and applied-connection settings, which that
|
|
// override does not touch, to recover the real servers.
|
|
// 3. /etc/resolv.conf.olm.backup — written by olm before it overrides
|
|
// /etc/resolv.conf on non-systemd systems, for when NetworkManager isn't
|
|
// in use at all.
|
|
// 4. /etc/resolv.conf — plain fallback.
|
|
//
|
|
// Loopback and link-local addresses (e.g. 127.0.0.53, ::1) are excluded
|
|
// because they are stub resolver addresses, not real upstream servers.
|
|
func readSystemDNS() []string {
|
|
// Prefer systemd-resolved's resolved (non-stub) resolv.conf, but only if
|
|
// systemd-resolved is actually alive right now - see resolution order
|
|
// note above on why the file's existence alone isn't enough.
|
|
if platform.IsSystemdResolvedAvailable() {
|
|
if servers := parseResolvConf("/run/systemd/resolve/resolv.conf"); len(servers) > 0 {
|
|
return servers
|
|
}
|
|
}
|
|
|
|
if servers := readNetworkManagerDNS(); len(servers) > 0 {
|
|
return servers
|
|
}
|
|
|
|
// If olm has already overridden /etc/resolv.conf the backup holds the
|
|
// original pre-override DNS servers.
|
|
if _, err := os.Stat("/etc/resolv.conf.olm.backup"); err == nil {
|
|
if servers := parseResolvConf("/etc/resolv.conf.olm.backup"); len(servers) > 0 {
|
|
return servers
|
|
}
|
|
}
|
|
|
|
return parseResolvConf("/etc/resolv.conf")
|
|
}
|
|
|
|
// readNetworkManagerDNS returns the DNS servers NetworkManager reports over
|
|
// D-Bus for its active connections, in "host:53" format. Returns nil if
|
|
// NetworkManager isn't running or reports nothing usable.
|
|
func readNetworkManagerDNS() []string {
|
|
addrs, err := platform.GetNetworkManagerNameservers()
|
|
if err != nil || len(addrs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
result := make([]string, 0, len(addrs))
|
|
for _, addr := range addrs {
|
|
result = append(result, addrToHostPort(addr))
|
|
}
|
|
return result
|
|
}
|
|
|
|
// parseResolvConf reads nameserver lines from a resolv.conf-style file,
|
|
// skipping loopback and link-local addresses.
|
|
func parseResolvConf(path string) []string {
|
|
f, err := os.Open(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
defer f.Close()
|
|
|
|
var result []string
|
|
seen := make(map[string]bool)
|
|
|
|
scanner := bufio.NewScanner(f)
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if !strings.HasPrefix(line, "nameserver") {
|
|
continue
|
|
}
|
|
fields := strings.Fields(line)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
addr, err := netip.ParseAddr(fields[1])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if addr.IsLoopback() || addr.IsLinkLocalUnicast() {
|
|
continue
|
|
}
|
|
s := addrToHostPort(addr)
|
|
if !seen[s] {
|
|
seen[s] = true
|
|
result = append(result, s)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// addrToHostPort converts a netip.Addr to "addr:53" format, wrapping IPv6
|
|
// addresses in brackets as required by net.JoinHostPort.
|
|
func addrToHostPort(addr netip.Addr) string {
|
|
return net.JoinHostPort(addr.String(), "53")
|
|
}
|