Also check network manager on linux

This commit is contained in:
Owen
2026-07-07 16:41:01 -04:00
parent 7b07745650
commit 1920f52699
2 changed files with 129 additions and 9 deletions

View File

@@ -16,12 +16,18 @@ import (
const (
// NetworkManager D-Bus constants
networkManagerDest = "org.freedesktop.NetworkManager"
networkManagerDbusObjectNode = "/org/freedesktop/NetworkManager"
networkManagerDbusDNSManagerInterface = "org.freedesktop.NetworkManager.DnsManager"
networkManagerDbusDNSManagerObjectNode = networkManagerDbusObjectNode + "/DnsManager"
networkManagerDbusDNSManagerModeProperty = networkManagerDbusDNSManagerInterface + ".Mode"
networkManagerDbusVersionProperty = "org.freedesktop.NetworkManager.Version"
networkManagerDest = "org.freedesktop.NetworkManager"
networkManagerDbusObjectNode = "/org/freedesktop/NetworkManager"
networkManagerDbusDNSManagerInterface = "org.freedesktop.NetworkManager.DnsManager"
networkManagerDbusDNSManagerObjectNode = networkManagerDbusObjectNode + "/DnsManager"
networkManagerDbusDNSManagerModeProperty = networkManagerDbusDNSManagerInterface + ".Mode"
networkManagerDbusVersionProperty = "org.freedesktop.NetworkManager.Version"
networkManagerDbusActiveConnsProperty = networkManagerDest + ".ActiveConnections"
networkManagerDbusActiveInterface = "org.freedesktop.NetworkManager.Connection.Active"
networkManagerDbusActiveIP4ConfigProperty = networkManagerDbusActiveInterface + ".Ip4Config"
networkManagerDbusActiveIP6ConfigProperty = networkManagerDbusActiveInterface + ".Ip6Config"
networkManagerDbusIP4ConfigInterface = "org.freedesktop.NetworkManager.IP4Config"
networkManagerDbusIP6ConfigInterface = "org.freedesktop.NetworkManager.IP6Config"
// NetworkManager dispatcher script path
networkManagerDispatcherDir = "/etc/NetworkManager/dispatcher.d"
@@ -301,6 +307,89 @@ func GetNetworkManagerDNSMode() (string, error) {
return mode, nil
}
// GetNetworkManagerNameservers returns the DNS servers NetworkManager has
// computed for every active connection, read live via D-Bus from each
// connection's IP4Config/IP6Config NameserverData property.
//
// Unlike reading /etc/resolv.conf, this reflects NetworkManager's own view of
// DNS regardless of its DnsManager.Mode: in "dnsmasq" or "unbound" mode
// /etc/resolv.conf only contains a loopback stub address pointing at
// NetworkManager's local resolver, not the real upstream servers, and even in
// "default" mode this stays live and unaffected if something else (such as
// olm's own override) has since overwritten /etc/resolv.conf directly instead
// of going through NetworkManager.
func GetNetworkManagerNameservers() ([]netip.Addr, error) {
conn, err := dbus.SystemBus()
if err != nil {
return nil, fmt.Errorf("connect to system bus: %w", err)
}
defer conn.Close()
nm := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
activeVariant, err := nm.GetProperty(networkManagerDbusActiveConnsProperty)
if err != nil {
return nil, fmt.Errorf("get active connections: %w", err)
}
activePaths, ok := activeVariant.Value().([]dbus.ObjectPath)
if !ok {
return nil, errors.New("ActiveConnections is not a list of object paths")
}
ipConfigSources := []struct {
activeProperty string
configIface string
}{
{networkManagerDbusActiveIP4ConfigProperty, networkManagerDbusIP4ConfigInterface},
{networkManagerDbusActiveIP6ConfigProperty, networkManagerDbusIP6ConfigInterface},
}
seen := make(map[netip.Addr]bool)
var servers []netip.Addr
for _, activePath := range activePaths {
active := conn.Object(networkManagerDest, activePath)
for _, src := range ipConfigSources {
cfgVariant, err := active.GetProperty(src.activeProperty)
if err != nil {
continue
}
cfgPath, ok := cfgVariant.Value().(dbus.ObjectPath)
if !ok || cfgPath == "" || cfgPath == "/" {
continue
}
nsVariant, err := conn.Object(networkManagerDest, cfgPath).GetProperty(src.configIface + ".NameserverData")
if err != nil {
continue
}
entries, ok := nsVariant.Value().([]map[string]dbus.Variant)
if !ok {
continue
}
for _, entry := range entries {
addrVariant, ok := entry["address"]
if !ok {
continue
}
addrStr, ok := addrVariant.Value().(string)
if !ok {
continue
}
addr, err := netip.ParseAddr(addrStr)
if err != nil || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
continue
}
if !seen[addr] {
seen[addr] = true
servers = append(servers, addr)
}
}
}
}
return servers, nil
}
// GetNetworkManagerVersion returns the version of NetworkManager
func GetNetworkManagerVersion() (string, error) {
conn, err := dbus.SystemBus()

View File

@@ -8,6 +8,8 @@ import (
"net/netip"
"os"
"strings"
platform "github.com/fosrl/olm/dns/platform"
)
// readSystemDNS returns the current system DNS servers in "host:53" format.
@@ -16,9 +18,18 @@ import (
// 1. /run/systemd/resolve/resolv.conf — maintained by systemd-resolved with
// the real per-link DNS servers; updated on every DHCP change and never
// touched by olm's D-Bus DNS override.
// 2. /etc/resolv.conf.olm.backup — written by olm before it overrides
// /etc/resolv.conf on non-systemd systems.
// 3. /etc/resolv.conf — plain fallback.
// 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).
// 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.
@@ -28,6 +39,10 @@ func readSystemDNS() []string {
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 {
@@ -39,6 +54,22 @@ func readSystemDNS() []string {
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 {