mirror of
https://github.com/fosrl/olm.git
synced 2026-02-08 05:56:41 +00:00
287 lines
8.5 KiB
Go
287 lines
8.5 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"
|
|
systemdDbusFlushCachesMethod = systemdDbusManagerIface + ".FlushCaches"
|
|
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
|
|
systemdDbusSetDNSMethod = systemdDbusLinkInterface + ".SetDNS"
|
|
systemdDbusSetDefaultRouteMethod = systemdDbusLinkInterface + ".SetDefaultRoute"
|
|
systemdDbusSetDomainsMethod = systemdDbusLinkInterface + ".SetDomains"
|
|
systemdDbusSetDNSSECMethod = systemdDbusLinkInterface + ".SetDNSSEC"
|
|
systemdDbusSetDNSOverTLSMethod = systemdDbusLinkInterface + ".SetDNSOverTLS"
|
|
systemdDbusRevertMethod = systemdDbusLinkInterface + ".Revert"
|
|
|
|
// RootZone is the root DNS zone that matches all queries
|
|
RootZone = "."
|
|
)
|
|
|
|
// systemdDbusDNSInput maps to (iay) dbus input for SetDNS method
|
|
type systemdDbusDNSInput struct {
|
|
Family int32
|
|
Address []byte
|
|
}
|
|
|
|
// systemdDbusDomainsInput maps to (sb) dbus input for SetDomains method
|
|
type systemdDbusDomainsInput struct {
|
|
Domain string
|
|
MatchOnly bool
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// Flush DNS cache after reverting
|
|
if err := s.flushDNSCache(); err != nil {
|
|
fmt.Printf("warning: failed to flush DNS cache: %v\n", 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 to set the DNS servers
|
|
if err := obj.CallWithContext(ctx, systemdDbusSetDNSMethod, 0, dnsInputs).Store(); err != nil {
|
|
return fmt.Errorf("set DNS servers: %w", err)
|
|
}
|
|
|
|
// Set this interface as the default route for DNS
|
|
// This ensures all DNS queries prefer this interface
|
|
if err := s.callLinkMethod(systemdDbusSetDefaultRouteMethod, true); err != nil {
|
|
return fmt.Errorf("set default route: %w", err)
|
|
}
|
|
|
|
// Set the root zone "." as a match-only domain
|
|
// This captures ALL DNS queries and routes them through this interface
|
|
domainsInput := []systemdDbusDomainsInput{
|
|
{
|
|
Domain: RootZone,
|
|
MatchOnly: true,
|
|
},
|
|
}
|
|
if err := s.callLinkMethod(systemdDbusSetDomainsMethod, domainsInput); err != nil {
|
|
return fmt.Errorf("set domains: %w", err)
|
|
}
|
|
|
|
// Disable DNSSEC - we don't support it and it may be enabled by default
|
|
if err := s.callLinkMethod(systemdDbusSetDNSSECMethod, "no"); err != nil {
|
|
// Log warning but don't fail - this is optional
|
|
fmt.Printf("warning: failed to disable DNSSEC: %v\n", err)
|
|
}
|
|
|
|
// Disable DNSOverTLS - we don't support it and it may be enabled by default
|
|
if err := s.callLinkMethod(systemdDbusSetDNSOverTLSMethod, "no"); err != nil {
|
|
// Log warning but don't fail - this is optional
|
|
fmt.Printf("warning: failed to disable DNSOverTLS: %v\n", err)
|
|
}
|
|
|
|
// Flush DNS cache to ensure new settings take effect immediately
|
|
if err := s.flushDNSCache(); err != nil {
|
|
fmt.Printf("warning: failed to flush DNS cache: %v\n", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// callLinkMethod is a helper to call methods on the link object
|
|
func (s *SystemdResolvedDNSConfigurator) callLinkMethod(method string, value any) error {
|
|
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 value != nil {
|
|
if err := obj.CallWithContext(ctx, method, 0, value).Store(); err != nil {
|
|
return fmt.Errorf("call %s: %w", method, err)
|
|
}
|
|
} else {
|
|
if err := obj.CallWithContext(ctx, method, 0).Store(); err != nil {
|
|
return fmt.Errorf("call %s: %w", method, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// flushDNSCache flushes the systemd-resolved DNS cache
|
|
func (s *SystemdResolvedDNSConfigurator) flushDNSCache() error {
|
|
conn, err := dbus.SystemBus()
|
|
if err != nil {
|
|
return fmt.Errorf("connect to system bus: %w", err)
|
|
}
|
|
defer conn.Close()
|
|
|
|
obj := conn.Object(systemdResolvedDest, systemdDbusObjectNode)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := obj.CallWithContext(ctx, systemdDbusFlushCachesMethod, 0).Store(); err != nil {
|
|
return fmt.Errorf("flush caches: %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
|
|
}
|