mirror of
https://github.com/fosrl/olm.git
synced 2026-02-08 05:56:41 +00:00
Network manager working by adding a config file
Former-commit-id: 04928aada0
This commit is contained in:
294
dns/platform/network_manager.go
Normal file
294
dns/platform/network_manager.go
Normal file
@@ -0,0 +1,294 @@
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dbus "github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
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"
|
||||
|
||||
// NetworkManager dispatcher script path
|
||||
networkManagerDispatcherDir = "/etc/NetworkManager/dispatcher.d"
|
||||
networkManagerConfDir = "/etc/NetworkManager/conf.d"
|
||||
networkManagerDNSConfFile = "olm-dns.conf"
|
||||
networkManagerDispatcherFile = "01-olm-dns"
|
||||
)
|
||||
|
||||
// NetworkManagerDNSConfigurator manages DNS settings using NetworkManager configuration files
|
||||
// This approach works with unmanaged interfaces by modifying NetworkManager's global DNS settings
|
||||
type NetworkManagerDNSConfigurator struct {
|
||||
ifaceName string
|
||||
originalState *DNSState
|
||||
confPath string
|
||||
dispatchPath string
|
||||
}
|
||||
|
||||
// NewNetworkManagerDNSConfigurator creates a new NetworkManager DNS configurator
|
||||
func NewNetworkManagerDNSConfigurator(ifaceName string) (*NetworkManagerDNSConfigurator, error) {
|
||||
if ifaceName == "" {
|
||||
return nil, fmt.Errorf("interface name is required")
|
||||
}
|
||||
|
||||
// Check that NetworkManager conf.d directory exists
|
||||
if _, err := os.Stat(networkManagerConfDir); os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("NetworkManager conf.d directory not found: %s", networkManagerConfDir)
|
||||
}
|
||||
|
||||
return &NetworkManagerDNSConfigurator{
|
||||
ifaceName: ifaceName,
|
||||
confPath: networkManagerConfDir + "/" + networkManagerDNSConfFile,
|
||||
dispatchPath: networkManagerDispatcherDir + "/" + networkManagerDispatcherFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name returns the configurator name
|
||||
func (n *NetworkManagerDNSConfigurator) Name() string {
|
||||
return "network-manager"
|
||||
}
|
||||
|
||||
// SetDNS sets the DNS servers and returns the original servers
|
||||
func (n *NetworkManagerDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
|
||||
// Get current DNS settings before overriding
|
||||
originalServers, err := n.GetCurrentDNS()
|
||||
if err != nil {
|
||||
// If we can't get current DNS, proceed anyway
|
||||
originalServers = []netip.Addr{}
|
||||
}
|
||||
|
||||
// Store original state
|
||||
n.originalState = &DNSState{
|
||||
OriginalServers: originalServers,
|
||||
ConfiguratorName: n.Name(),
|
||||
}
|
||||
|
||||
// Apply new DNS servers
|
||||
if err := n.applyDNSServers(servers); err != nil {
|
||||
return nil, fmt.Errorf("apply DNS servers: %w", err)
|
||||
}
|
||||
|
||||
return originalServers, nil
|
||||
}
|
||||
|
||||
// RestoreDNS restores the original DNS configuration
|
||||
func (n *NetworkManagerDNSConfigurator) RestoreDNS() error {
|
||||
// Remove our configuration file
|
||||
if err := os.Remove(n.confPath); err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("remove DNS config file: %w", err)
|
||||
}
|
||||
|
||||
// Reload NetworkManager to apply the change
|
||||
if err := n.reloadNetworkManager(); err != nil {
|
||||
return fmt.Errorf("reload NetworkManager: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentDNS returns the currently configured DNS servers by reading /etc/resolv.conf
|
||||
func (n *NetworkManagerDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
|
||||
content, err := os.ReadFile("/etc/resolv.conf")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read resolv.conf: %w", err)
|
||||
}
|
||||
|
||||
var servers []netip.Addr
|
||||
lines := strings.Split(string(content), "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if strings.HasPrefix(line, "nameserver") {
|
||||
fields := strings.Fields(line)
|
||||
if len(fields) >= 2 {
|
||||
if addr, err := netip.ParseAddr(fields[1]); err == nil {
|
||||
servers = append(servers, addr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return servers, nil
|
||||
}
|
||||
|
||||
// applyDNSServers applies DNS server configuration via NetworkManager config file
|
||||
func (n *NetworkManagerDNSConfigurator) applyDNSServers(servers []netip.Addr) error {
|
||||
if len(servers) == 0 {
|
||||
return fmt.Errorf("no DNS servers provided")
|
||||
}
|
||||
|
||||
// Build DNS server list
|
||||
var dnsServers []string
|
||||
for _, server := range servers {
|
||||
dnsServers = append(dnsServers, server.String())
|
||||
}
|
||||
|
||||
// Create NetworkManager configuration file that sets global DNS
|
||||
// This overrides DNS for all connections
|
||||
configContent := fmt.Sprintf(`# Generated by Olm DNS Manager - DO NOT EDIT
|
||||
# This file configures NetworkManager to use Olm's DNS proxy
|
||||
|
||||
[global-dns-domain-*]
|
||||
servers=%s
|
||||
`, strings.Join(dnsServers, ","))
|
||||
|
||||
// Write the configuration file
|
||||
if err := os.WriteFile(n.confPath, []byte(configContent), 0644); err != nil {
|
||||
return fmt.Errorf("write DNS config file: %w", err)
|
||||
}
|
||||
|
||||
// Reload NetworkManager to apply the new configuration
|
||||
if err := n.reloadNetworkManager(); err != nil {
|
||||
// Try to clean up
|
||||
os.Remove(n.confPath)
|
||||
return fmt.Errorf("reload NetworkManager: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// reloadNetworkManager tells NetworkManager to reload its configuration
|
||||
func (n *NetworkManagerDNSConfigurator) reloadNetworkManager() error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Call Reload method with flags=0 (reload everything)
|
||||
// See: https://networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.html#gdbus-method-org-freedesktop-NetworkManager.Reload
|
||||
err = obj.CallWithContext(ctx, networkManagerDest+".Reload", 0, uint32(0)).Store()
|
||||
if err != nil {
|
||||
return fmt.Errorf("call Reload: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNetworkManagerAvailable checks if NetworkManager is available and responsive
|
||||
func IsNetworkManagerAvailable() bool {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Try to ping NetworkManager
|
||||
if err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store(); err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNetworkManagerDNSModeSupported checks if NetworkManager's DNS mode is one we can work with
|
||||
// Some DNS modes delegate to other systems (like systemd-resolved) which we should use directly
|
||||
func IsNetworkManagerDNSModeSupported() bool {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusDNSManagerObjectNode)
|
||||
|
||||
modeVariant, err := obj.GetProperty(networkManagerDbusDNSManagerModeProperty)
|
||||
if err != nil {
|
||||
// If we can't get the mode, assume it's not supported
|
||||
return false
|
||||
}
|
||||
|
||||
mode, ok := modeVariant.Value().(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// If NetworkManager is delegating DNS to systemd-resolved, we should use
|
||||
// systemd-resolved directly for better control
|
||||
switch mode {
|
||||
case "systemd-resolved":
|
||||
// NetworkManager is delegating to systemd-resolved
|
||||
// We should use systemd-resolved configurator instead
|
||||
return false
|
||||
case "dnsmasq", "unbound":
|
||||
// NetworkManager is using a local resolver that it controls
|
||||
// We can configure DNS through NetworkManager
|
||||
return true
|
||||
case "default", "none", "":
|
||||
// NetworkManager is managing DNS directly or not at all
|
||||
// We can configure DNS through NetworkManager
|
||||
return true
|
||||
default:
|
||||
// Unknown mode, try to use it
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// GetNetworkManagerDNSMode returns the current DNS mode of NetworkManager
|
||||
func GetNetworkManagerDNSMode() (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusDNSManagerObjectNode)
|
||||
|
||||
modeVariant, err := obj.GetProperty(networkManagerDbusDNSManagerModeProperty)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get DNS mode property: %w", err)
|
||||
}
|
||||
|
||||
mode, ok := modeVariant.Value().(string)
|
||||
if !ok {
|
||||
return "", errors.New("DNS mode is not a string")
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
// GetNetworkManagerVersion returns the version of NetworkManager
|
||||
func GetNetworkManagerVersion() (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
|
||||
versionVariant, err := obj.GetProperty(networkManagerDbusVersionProperty)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get version property: %w", err)
|
||||
}
|
||||
|
||||
version, ok := versionVariant.Value().(string)
|
||||
if !ok {
|
||||
return "", errors.New("version is not a string")
|
||||
}
|
||||
|
||||
return version, nil
|
||||
}
|
||||
@@ -1,409 +0,0 @@
|
||||
//go:build (linux && !android) || freebsd
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"github.com/fosrl/newt/logger"
|
||||
dbus "github.com/godbus/dbus/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
networkManagerDest = "org.freedesktop.NetworkManager"
|
||||
networkManagerDbusObjectNode = "/org/freedesktop/NetworkManager"
|
||||
networkManagerDbusDNSManagerObjectNode = networkManagerDbusObjectNode + "/DnsManager"
|
||||
networkManagerDbusDNSManagerInterface = "org.freedesktop.NetworkManager.DnsManager"
|
||||
networkManagerDbusDNSManagerMode = networkManagerDbusDNSManagerInterface + ".Mode"
|
||||
networkManagerDbusGetDeviceByIPIface = networkManagerDest + ".GetDeviceByIpIface"
|
||||
networkManagerDbusDeviceInterface = "org.freedesktop.NetworkManager.Device"
|
||||
networkManagerDbusDeviceGetApplied = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
|
||||
networkManagerDbusDeviceReapply = networkManagerDbusDeviceInterface + ".Reapply"
|
||||
networkManagerDbusPrimaryConnection = networkManagerDest + ".PrimaryConnection"
|
||||
networkManagerDbusActiveConnInterface = "org.freedesktop.NetworkManager.Connection.Active"
|
||||
networkManagerDbusActiveConnDevices = networkManagerDbusActiveConnInterface + ".Devices"
|
||||
networkManagerDbusIPv4Key = "ipv4"
|
||||
networkManagerDbusIPv6Key = "ipv6"
|
||||
networkManagerDbusDNSKey = "dns"
|
||||
networkManagerDbusDNSSearchKey = "dns-search"
|
||||
networkManagerDbusDNSPriorityKey = "dns-priority"
|
||||
networkManagerDbusPrimaryDNSPriority = int32(-500)
|
||||
)
|
||||
|
||||
type networkManagerConnSettings map[string]map[string]dbus.Variant
|
||||
type networkManagerConfigVersion uint64
|
||||
|
||||
// cleanDeprecatedSettings removes deprecated settings that are still returned by
|
||||
// GetAppliedConnection but can't be reapplied
|
||||
func (s networkManagerConnSettings) cleanDeprecatedSettings() {
|
||||
for _, key := range []string{"addresses", "routes"} {
|
||||
if ipv4Settings, ok := s[networkManagerDbusIPv4Key]; ok {
|
||||
delete(ipv4Settings, key)
|
||||
}
|
||||
if ipv6Settings, ok := s[networkManagerDbusIPv6Key]; ok {
|
||||
delete(ipv6Settings, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NetworkManagerDNSConfigurator manages DNS settings using NetworkManager D-Bus API
|
||||
// Note: This configures DNS on the PRIMARY active connection, not on tunnel interfaces
|
||||
// which are typically unmanaged by NetworkManager
|
||||
type NetworkManagerDNSConfigurator struct {
|
||||
ifaceName string
|
||||
dbusLinkObject dbus.ObjectPath
|
||||
originalState *DNSState
|
||||
}
|
||||
|
||||
// NewNetworkManagerDNSConfigurator creates a new NetworkManager DNS configurator
|
||||
// It finds the primary active connection's device to configure DNS on
|
||||
func NewNetworkManagerDNSConfigurator(ifaceName string) (*NetworkManagerDNSConfigurator, error) {
|
||||
// First, try to get the primary connection's device
|
||||
// This is what we should configure DNS on, not the tunnel interface
|
||||
primaryDevice, err := getPrimaryConnectionDevice()
|
||||
if err != nil {
|
||||
logger.Warn("Could not get primary connection device: %v, trying specified interface", err)
|
||||
// Fall back to trying the specified interface
|
||||
primaryDevice, err = getDeviceByInterface(ifaceName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get device for interface %s: %w", ifaceName, err)
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info("NetworkManager: using device %s for DNS configuration", primaryDevice)
|
||||
|
||||
return &NetworkManagerDNSConfigurator{
|
||||
ifaceName: ifaceName,
|
||||
dbusLinkObject: primaryDevice,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getPrimaryConnectionDevice gets the device associated with NetworkManager's primary connection
|
||||
func getPrimaryConnectionDevice() (dbus.ObjectPath, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
// Get the primary connection path
|
||||
nmObj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
primaryConnVariant, err := nmObj.GetProperty(networkManagerDbusPrimaryConnection)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get primary connection: %w", err)
|
||||
}
|
||||
|
||||
primaryConnPath, ok := primaryConnVariant.Value().(dbus.ObjectPath)
|
||||
if !ok || primaryConnPath == "/" || primaryConnPath == "" {
|
||||
return "", fmt.Errorf("no primary connection available")
|
||||
}
|
||||
|
||||
logger.Debug("NetworkManager primary connection: %s", primaryConnPath)
|
||||
|
||||
// Get the devices for this active connection
|
||||
activeConnObj := conn.Object(networkManagerDest, primaryConnPath)
|
||||
devicesVariant, err := activeConnObj.GetProperty(networkManagerDbusActiveConnDevices)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get active connection devices: %w", err)
|
||||
}
|
||||
|
||||
devices, ok := devicesVariant.Value().([]dbus.ObjectPath)
|
||||
if !ok || len(devices) == 0 {
|
||||
return "", fmt.Errorf("no devices for primary connection")
|
||||
}
|
||||
|
||||
logger.Debug("NetworkManager primary connection device: %s", devices[0])
|
||||
return devices[0], nil
|
||||
}
|
||||
|
||||
// getDeviceByInterface gets the device path for a specific interface name
|
||||
func getDeviceByInterface(ifaceName string) (dbus.ObjectPath, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
|
||||
var linkPath string
|
||||
if err := obj.Call(networkManagerDbusGetDeviceByIPIface, 0, ifaceName).Store(&linkPath); err != nil {
|
||||
return "", fmt.Errorf("get device by interface: %w", err)
|
||||
}
|
||||
|
||||
return dbus.ObjectPath(linkPath), nil
|
||||
}
|
||||
|
||||
// Name returns the configurator name
|
||||
func (n *NetworkManagerDNSConfigurator) Name() string {
|
||||
return "networkmanager-dbus"
|
||||
}
|
||||
|
||||
// SetDNS sets the DNS servers and returns the original servers
|
||||
func (n *NetworkManagerDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
|
||||
// Get current DNS settings before overriding
|
||||
originalServers, err := n.GetCurrentDNS()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get current DNS: %w", err)
|
||||
}
|
||||
|
||||
// Store original state
|
||||
n.originalState = &DNSState{
|
||||
OriginalServers: originalServers,
|
||||
ConfiguratorName: n.Name(),
|
||||
}
|
||||
|
||||
// Apply new DNS servers
|
||||
if err := n.applyDNSServers(servers); err != nil {
|
||||
return nil, fmt.Errorf("apply DNS servers: %w", err)
|
||||
}
|
||||
|
||||
return originalServers, nil
|
||||
}
|
||||
|
||||
// RestoreDNS restores the original DNS configuration
|
||||
func (n *NetworkManagerDNSConfigurator) RestoreDNS() error {
|
||||
if n.originalState == nil {
|
||||
return fmt.Errorf("no original state to restore")
|
||||
}
|
||||
|
||||
// Restore original DNS servers
|
||||
if err := n.applyDNSServers(n.originalState.OriginalServers); err != nil {
|
||||
return fmt.Errorf("restore DNS servers: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCurrentDNS returns the currently configured DNS servers
|
||||
// Note: NetworkManager may not have DNS settings on the interface level
|
||||
// if DNS is being managed globally, so this may return empty
|
||||
func (n *NetworkManagerDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
|
||||
connSettings, _, err := n.getAppliedConnectionSettings()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get connection settings: %w", err)
|
||||
}
|
||||
|
||||
return n.extractDNSServers(connSettings), nil
|
||||
}
|
||||
|
||||
// applyDNSServers applies DNS server configuration via NetworkManager
|
||||
func (n *NetworkManagerDNSConfigurator) applyDNSServers(servers []netip.Addr) error {
|
||||
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
||||
if err != nil {
|
||||
return fmt.Errorf("get connection settings: %w", err)
|
||||
}
|
||||
|
||||
// Clean deprecated settings that can't be reapplied
|
||||
connSettings.cleanDeprecatedSettings()
|
||||
|
||||
// Ensure IPv4 settings map exists
|
||||
if connSettings[networkManagerDbusIPv4Key] == nil {
|
||||
connSettings[networkManagerDbusIPv4Key] = make(map[string]dbus.Variant)
|
||||
}
|
||||
|
||||
// Convert DNS servers to NetworkManager format (uint32 little-endian)
|
||||
var dnsServers []uint32
|
||||
for _, server := range servers {
|
||||
if server.Is4() {
|
||||
dnsServers = append(dnsServers, binary.LittleEndian.Uint32(server.AsSlice()))
|
||||
}
|
||||
}
|
||||
|
||||
if len(dnsServers) == 0 {
|
||||
return fmt.Errorf("no valid IPv4 DNS servers provided")
|
||||
}
|
||||
|
||||
// Update DNS settings
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant(dnsServers)
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(networkManagerDbusPrimaryDNSPriority)
|
||||
|
||||
// Set dns-search with "~." to make this a catch-all DNS route
|
||||
// This is critical for NetworkManager to route all DNS queries through our server
|
||||
// See: https://wiki.gnome.org/Projects/NetworkManager/DNS
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSSearchKey] = dbus.MakeVariant([]string{"~."})
|
||||
|
||||
logger.Info("NetworkManager: applying DNS servers %v with priority %d and search domains [~.]",
|
||||
servers, networkManagerDbusPrimaryDNSPriority)
|
||||
|
||||
// Reapply connection settings
|
||||
if err := n.reApplyConnectionSettings(connSettings, configVersion); err != nil {
|
||||
return fmt.Errorf("reapply connection settings: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("NetworkManager: successfully applied DNS configuration to interface %s", n.ifaceName)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getAppliedConnectionSettings retrieves current NetworkManager connection settings
|
||||
func (n *NetworkManagerDNSConfigurator) getAppliedConnectionSettings() (networkManagerConnSettings, networkManagerConfigVersion, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, n.dbusLinkObject)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var connSettings networkManagerConnSettings
|
||||
var configVersion networkManagerConfigVersion
|
||||
|
||||
if err := obj.CallWithContext(ctx, networkManagerDbusDeviceGetApplied, 0, uint32(0)).Store(&connSettings, &configVersion); err != nil {
|
||||
return nil, 0, fmt.Errorf("get applied connection: %w", err)
|
||||
}
|
||||
|
||||
return connSettings, configVersion, nil
|
||||
}
|
||||
|
||||
// reApplyConnectionSettings applies new connection settings via NetworkManager
|
||||
func (n *NetworkManagerDNSConfigurator) reApplyConnectionSettings(connSettings networkManagerConnSettings, configVersion networkManagerConfigVersion) error {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, n.dbusLinkObject)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := obj.CallWithContext(ctx, networkManagerDbusDeviceReapply, 0, connSettings, configVersion, uint32(0)).Store(); err != nil {
|
||||
return fmt.Errorf("reapply connection: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// extractDNSServers extracts DNS servers from connection settings
|
||||
// Returns empty slice if no DNS is configured on this interface
|
||||
func (n *NetworkManagerDNSConfigurator) extractDNSServers(connSettings networkManagerConnSettings) []netip.Addr {
|
||||
var servers []netip.Addr
|
||||
|
||||
ipv4Settings, ok := connSettings[networkManagerDbusIPv4Key]
|
||||
if !ok {
|
||||
return servers
|
||||
}
|
||||
|
||||
dnsVariant, ok := ipv4Settings[networkManagerDbusDNSKey]
|
||||
if !ok {
|
||||
// DNS not configured on this interface - this is normal
|
||||
return servers
|
||||
}
|
||||
|
||||
dnsServers, ok := dnsVariant.Value().([]uint32)
|
||||
if !ok || dnsServers == nil {
|
||||
return servers
|
||||
}
|
||||
|
||||
for _, dnsServer := range dnsServers {
|
||||
// Convert uint32 back to IP address
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, dnsServer)
|
||||
|
||||
if addr, ok := netip.AddrFromSlice(buf); ok {
|
||||
servers = append(servers, addr)
|
||||
}
|
||||
}
|
||||
|
||||
return servers
|
||||
}
|
||||
|
||||
// IsNetworkManagerAvailable checks if NetworkManager is available and responsive
|
||||
func IsNetworkManagerAvailable() bool {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusObjectNode)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// Try to ping NetworkManager
|
||||
if err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store(); err != nil {
|
||||
logger.Debug("NetworkManager ping failed: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// GetNetworkManagerDNSMode returns the DNS mode NetworkManager is using
|
||||
// Possible values: "dnsmasq", "systemd-resolved", "unbound", "default", etc.
|
||||
func GetNetworkManagerDNSMode() (string, error) {
|
||||
conn, err := dbus.SystemBus()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("connect to system bus: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
obj := conn.Object(networkManagerDest, networkManagerDbusDNSManagerObjectNode)
|
||||
|
||||
variant, err := obj.GetProperty(networkManagerDbusDNSManagerMode)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("get DNS mode property: %w", err)
|
||||
}
|
||||
|
||||
mode, ok := variant.Value().(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("DNS mode is not a string")
|
||||
}
|
||||
|
||||
return mode, nil
|
||||
}
|
||||
|
||||
// IsNetworkManagerDNSModeSupported checks if NetworkManager's DNS mode
|
||||
// allows direct DNS configuration via D-Bus
|
||||
func IsNetworkManagerDNSModeSupported() bool {
|
||||
mode, err := GetNetworkManagerDNSMode()
|
||||
if err != nil {
|
||||
logger.Debug("Failed to get NetworkManager DNS mode: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
logger.Debug("NetworkManager DNS mode: %s", mode)
|
||||
|
||||
// These modes support D-Bus DNS configuration
|
||||
switch mode {
|
||||
case "dnsmasq", "unbound", "default":
|
||||
return true
|
||||
case "systemd-resolved":
|
||||
// When NM delegates to systemd-resolved, we should use systemd-resolved directly
|
||||
logger.Warn("NetworkManager is using systemd-resolved mode - consider using systemd-resolved configurator instead")
|
||||
return false
|
||||
default:
|
||||
logger.Warn("Unknown NetworkManager DNS mode: %s", mode)
|
||||
return true // Try anyway
|
||||
}
|
||||
}
|
||||
|
||||
// GetNetworkInterfaces returns available network interfaces
|
||||
func GetNetworkInterfaces() ([]string, error) {
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get interfaces: %w", err)
|
||||
}
|
||||
|
||||
var names []string
|
||||
for _, iface := range interfaces {
|
||||
// Skip loopback
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
names = append(names, iface.Name)
|
||||
}
|
||||
|
||||
return names, nil
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
394c3ad0e7be7b93b907a1ae27dc26076a809d4b
|
||||
Reference in New Issue
Block a user