mirror of
https://github.com/fosrl/olm.git
synced 2026-07-20 14:51:27 +02:00
Improve linux to use networkmanager correctly
This commit is contained in:
@@ -4,6 +4,7 @@ package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
@@ -26,8 +27,15 @@ const (
|
||||
networkManagerDbusActiveInterface = "org.freedesktop.NetworkManager.Connection.Active"
|
||||
networkManagerDbusActiveIP4ConfigProperty = networkManagerDbusActiveInterface + ".Ip4Config"
|
||||
networkManagerDbusActiveIP6ConfigProperty = networkManagerDbusActiveInterface + ".Ip6Config"
|
||||
networkManagerDbusActiveDevicesProperty = networkManagerDbusActiveInterface + ".Devices"
|
||||
networkManagerDbusIP4ConfigInterface = "org.freedesktop.NetworkManager.IP4Config"
|
||||
networkManagerDbusIP6ConfigInterface = "org.freedesktop.NetworkManager.IP6Config"
|
||||
networkManagerDbusDeviceInterface = "org.freedesktop.NetworkManager.Device"
|
||||
networkManagerDbusDeviceDhcp4ConfigProp = networkManagerDbusDeviceInterface + ".Dhcp4Config"
|
||||
networkManagerDbusDeviceDhcp6ConfigProp = networkManagerDbusDeviceInterface + ".Dhcp6Config"
|
||||
networkManagerDbusDhcp4ConfigInterface = "org.freedesktop.NetworkManager.DHCP4Config"
|
||||
networkManagerDbusDhcp6ConfigInterface = "org.freedesktop.NetworkManager.DHCP6Config"
|
||||
networkManagerDbusGetAppliedConnMethod = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
|
||||
|
||||
// NetworkManager dispatcher script path
|
||||
networkManagerDispatcherDir = "/etc/NetworkManager/dispatcher.d"
|
||||
@@ -307,17 +315,34 @@ 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.
|
||||
// GetNetworkManagerNameservers returns the DNS servers NetworkManager knows
|
||||
// about for every active connection, read live via D-Bus.
|
||||
//
|
||||
// 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.
|
||||
// olm's own NetworkManager DNS override (see NetworkManagerDNSConfigurator)
|
||||
// works by writing a [global-dns-domain-*] section to
|
||||
// /etc/NetworkManager/conf.d/olm-dns.conf and reloading NetworkManager. That
|
||||
// is NetworkManager's global DNS override mechanism: it replaces the DNS
|
||||
// servers NetworkManager's DnsManager computes as "effective" system-wide,
|
||||
// for every connection - not just what gets written to /etc/resolv.conf. So
|
||||
// once olm's override is active, even each connection's merged
|
||||
// IP4Config/IP6Config.NameserverData (the previous, sole source used here)
|
||||
// can end up reporting olm's own proxy address instead of the real network
|
||||
// DNS.
|
||||
//
|
||||
// To recover the real DNS regardless, this also reads two further sources
|
||||
// that NetworkManager's DNS merging - and therefore olm's global-dns override
|
||||
// - never touches, since both are populated independently of it:
|
||||
// - Dhcp4Config/Dhcp6Config.Options["*name_servers"]: the raw nameserver
|
||||
// list straight from the DHCP lease.
|
||||
// - Device.GetAppliedConnection()'s ipv4.dns/ipv6.dns: the DNS servers
|
||||
// explicitly configured on the connection profile itself, e.g. a static
|
||||
// DNS override set by the user directly in NetworkManager (the
|
||||
// NetworkManager equivalent of a manually-set Windows adapter DNS).
|
||||
//
|
||||
// IP4Config/IP6Config.NameserverData is still queried too, as a fallback for
|
||||
// setups the other two don't cover. Any of olm's own address that leaks
|
||||
// through any of these sources is expected to be dropped by the caller via
|
||||
// SystemDNSMonitor.SetExcludeIP.
|
||||
func GetNetworkManagerNameservers() ([]netip.Addr, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
@@ -346,8 +371,20 @@ func GetNetworkManagerNameservers() ([]netip.Addr, error) {
|
||||
|
||||
seen := make(map[netip.Addr]bool)
|
||||
var servers []netip.Addr
|
||||
add := func(addr netip.Addr) {
|
||||
addr = addr.Unmap()
|
||||
if !addr.IsValid() || addr.IsLoopback() || addr.IsLinkLocalUnicast() {
|
||||
return
|
||||
}
|
||||
if !seen[addr] {
|
||||
seen[addr] = true
|
||||
servers = append(servers, addr)
|
||||
}
|
||||
}
|
||||
|
||||
for _, activePath := range activePaths {
|
||||
active := conn.Object(networkManagerDest, activePath)
|
||||
|
||||
for _, src := range ipConfigSources {
|
||||
cfgVariant, err := active.GetProperty(src.activeProperty)
|
||||
if err != nil {
|
||||
@@ -375,21 +412,123 @@ func GetNetworkManagerNameservers() ([]netip.Addr, error) {
|
||||
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)
|
||||
if addr, err := netip.ParseAddr(addrStr); err == nil {
|
||||
add(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
devicesVariant, err := active.GetProperty(networkManagerDbusActiveDevicesProperty)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
devicePaths, ok := devicesVariant.Value().([]dbus.ObjectPath)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, devicePath := range devicePaths {
|
||||
device := conn.Object(networkManagerDest, devicePath)
|
||||
|
||||
for _, addr := range dhcpLeaseNameservers(conn, device, networkManagerDbusDeviceDhcp4ConfigProp, networkManagerDbusDhcp4ConfigInterface, "domain_name_servers") {
|
||||
add(addr)
|
||||
}
|
||||
for _, addr := range dhcpLeaseNameservers(conn, device, networkManagerDbusDeviceDhcp6ConfigProp, networkManagerDbusDhcp6ConfigInterface, "dhcp6_name_servers") {
|
||||
add(addr)
|
||||
}
|
||||
for _, addr := range appliedConnectionNameservers(device) {
|
||||
add(addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// dhcpLeaseNameservers reads a space-separated nameserver list out of a
|
||||
// device's Dhcp4Config/Dhcp6Config Options, straight from the DHCP lease -
|
||||
// data NetworkManager's DNS merging (and therefore olm's own global-dns
|
||||
// override) never touches.
|
||||
func dhcpLeaseNameservers(conn *dbus.Conn, device dbus.BusObject, configProperty, configIface, optionsKey string) []netip.Addr {
|
||||
cfgVariant, err := device.GetProperty(configProperty)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
cfgPath, ok := cfgVariant.Value().(dbus.ObjectPath)
|
||||
if !ok || cfgPath == "" || cfgPath == "/" {
|
||||
return nil
|
||||
}
|
||||
|
||||
optsVariant, err := conn.Object(networkManagerDest, cfgPath).GetProperty(configIface + ".Options")
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
opts, ok := optsVariant.Value().(map[string]dbus.Variant)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
raw, ok := opts[optionsKey]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
str, ok := raw.Value().(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
var addrs []netip.Addr
|
||||
for _, field := range strings.Fields(str) {
|
||||
if addr, err := netip.ParseAddr(field); err == nil {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
// appliedConnectionNameservers reads the ipv4.dns/ipv6.dns servers configured
|
||||
// on the device's currently-applied connection profile - e.g. a static DNS
|
||||
// override set by the user directly in NetworkManager - independent of DHCP
|
||||
// and of olm's own global-dns override.
|
||||
func appliedConnectionNameservers(device dbus.BusObject) []netip.Addr {
|
||||
var settings map[string]map[string]dbus.Variant
|
||||
var versionID uint64
|
||||
if err := device.Call(networkManagerDbusGetAppliedConnMethod, 0, uint32(0)).Store(&settings, &versionID); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var addrs []netip.Addr
|
||||
|
||||
if ipv4, ok := settings["ipv4"]; ok {
|
||||
if dnsVariant, ok := ipv4["dns"]; ok {
|
||||
if raw, ok := dnsVariant.Value().([]uint32); ok {
|
||||
for _, v := range raw {
|
||||
var b [4]byte
|
||||
// NetworkManager encodes IPv4 addresses in this setting as
|
||||
// network-byte-order bytes reinterpreted as a native uint32.
|
||||
binary.LittleEndian.PutUint32(b[:], v)
|
||||
addrs = append(addrs, netip.AddrFrom4(b))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ipv6, ok := settings["ipv6"]; ok {
|
||||
if dnsVariant, ok := ipv6["dns"]; ok {
|
||||
if raw, ok := dnsVariant.Value().([][]byte); ok {
|
||||
for _, b := range raw {
|
||||
if len(b) == 16 {
|
||||
var arr [16]byte
|
||||
copy(arr[:], b)
|
||||
addrs = append(addrs, netip.AddrFrom16(arr))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return addrs
|
||||
}
|
||||
|
||||
// GetNetworkManagerVersion returns the version of NetworkManager
|
||||
func GetNetworkManagerVersion() (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
|
||||
@@ -15,8 +15,14 @@ import (
|
||||
// readSystemDNS returns the current system DNS servers in "host:53" format.
|
||||
//
|
||||
// Resolution order:
|
||||
// 1. /run/systemd/resolve/resolv.conf — maintained by systemd-resolved with
|
||||
// the real per-link DNS servers; updated on every DHCP change and never
|
||||
// 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
|
||||
@@ -25,7 +31,11 @@ import (
|
||||
// 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).
|
||||
// (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.
|
||||
@@ -34,9 +44,13 @@ import (
|
||||
// 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.
|
||||
if servers := parseResolvConf("/run/systemd/resolve/resolv.conf"); len(servers) > 0 {
|
||||
return servers
|
||||
// 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 {
|
||||
|
||||
Reference in New Issue
Block a user