Files
olm/dns/platform/systemd.go
Owen 50008f3c12 Basic platform?
Former-commit-id: 423e18edc3
2025-11-23 21:26:15 -05:00

187 lines
5.2 KiB
Go

//go:build linux && !android
package dns
import (
"context"
"fmt"
"net"
"net/netip"
"time"
dbus "github.com/godbus/dbus/v5"
"golang.org/x/sys/unix"
)
const (
systemdResolvedDest = "org.freedesktop.resolve1"
systemdDbusObjectNode = "/org/freedesktop/resolve1"
systemdDbusManagerIface = "org.freedesktop.resolve1.Manager"
systemdDbusGetLinkMethod = systemdDbusManagerIface + ".GetLink"
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
systemdDbusSetDNSMethod = systemdDbusLinkInterface + ".SetDNS"
systemdDbusRevertMethod = systemdDbusLinkInterface + ".Revert"
)
// systemdDbusDNSInput maps to (iay) dbus input for SetDNS method
type systemdDbusDNSInput struct {
Family int32
Address []byte
}
// SystemdResolvedDNSConfigurator manages DNS settings using systemd-resolved D-Bus API
type SystemdResolvedDNSConfigurator struct {
ifaceName string
dbusLinkObject dbus.ObjectPath
originalState *DNSState
}
// NewSystemdResolvedDNSConfigurator creates a new systemd-resolved DNS configurator
func NewSystemdResolvedDNSConfigurator(ifaceName string) (*SystemdResolvedDNSConfigurator, error) {
// Get network interface
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return nil, fmt.Errorf("get interface: %w", err)
}
// Connect to D-Bus
conn, err := dbus.SystemBus()
if err != nil {
return nil, fmt.Errorf("connect to system bus: %w", err)
}
defer conn.Close()
obj := conn.Object(systemdResolvedDest, systemdDbusObjectNode)
// Get the link object for this interface
var linkPath string
if err := obj.Call(systemdDbusGetLinkMethod, 0, iface.Index).Store(&linkPath); err != nil {
return nil, fmt.Errorf("get link: %w", err)
}
return &SystemdResolvedDNSConfigurator{
ifaceName: ifaceName,
dbusLinkObject: dbus.ObjectPath(linkPath),
}, nil
}
// Name returns the configurator name
func (s *SystemdResolvedDNSConfigurator) Name() string {
return "systemd-resolved"
}
// SetDNS sets the DNS servers and returns the original servers
func (s *SystemdResolvedDNSConfigurator) SetDNS(servers []netip.Addr) ([]netip.Addr, error) {
// Get current DNS settings before overriding
originalServers, err := s.GetCurrentDNS()
if err != nil {
// If we can't get current DNS, proceed anyway
originalServers = []netip.Addr{}
}
// Store original state
s.originalState = &DNSState{
OriginalServers: originalServers,
ConfiguratorName: s.Name(),
}
// Apply new DNS servers
if err := s.applyDNSServers(servers); err != nil {
return nil, fmt.Errorf("apply DNS servers: %w", err)
}
return originalServers, nil
}
// RestoreDNS restores the original DNS configuration
func (s *SystemdResolvedDNSConfigurator) RestoreDNS() error {
// Call Revert method to restore systemd-resolved defaults
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("connect to system bus: %w", err)
}
defer conn.Close()
obj := conn.Object(systemdResolvedDest, s.dbusLinkObject)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := obj.CallWithContext(ctx, systemdDbusRevertMethod, 0).Store(); err != nil {
return fmt.Errorf("revert DNS settings: %w", err)
}
return nil
}
// GetCurrentDNS returns the currently configured DNS servers
// Note: systemd-resolved doesn't easily expose current per-link DNS servers via D-Bus
// This is a placeholder that returns an empty list
func (s *SystemdResolvedDNSConfigurator) GetCurrentDNS() ([]netip.Addr, error) {
// systemd-resolved's D-Bus API doesn't have a simple way to query current DNS servers
// We would need to parse resolvectl status output or read from /run/systemd/resolve/
// For now, return empty list
return []netip.Addr{}, nil
}
// applyDNSServers applies DNS server configuration via systemd-resolved
func (s *SystemdResolvedDNSConfigurator) applyDNSServers(servers []netip.Addr) error {
if len(servers) == 0 {
return fmt.Errorf("no DNS servers provided")
}
// Convert servers to systemd-resolved format
var dnsInputs []systemdDbusDNSInput
for _, server := range servers {
family := unix.AF_INET
if server.Is6() {
family = unix.AF_INET6
}
dnsInputs = append(dnsInputs, systemdDbusDNSInput{
Family: int32(family),
Address: server.AsSlice(),
})
}
// Connect to D-Bus
conn, err := dbus.SystemBus()
if err != nil {
return fmt.Errorf("connect to system bus: %w", err)
}
defer conn.Close()
obj := conn.Object(systemdResolvedDest, s.dbusLinkObject)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Call SetDNS method
if err := obj.CallWithContext(ctx, systemdDbusSetDNSMethod, 0, dnsInputs).Store(); err != nil {
return fmt.Errorf("set DNS servers: %w", err)
}
return nil
}
// IsSystemdResolvedAvailable checks if systemd-resolved is available and responsive
func IsSystemdResolvedAvailable() bool {
conn, err := dbus.SystemBus()
if err != nil {
return false
}
defer conn.Close()
obj := conn.Object(systemdResolvedDest, systemdDbusObjectNode)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
// Try to ping systemd-resolved
if err := obj.CallWithContext(ctx, "org.freedesktop.DBus.Peer.Ping", 0).Store(); err != nil {
return false
}
return true
}