2023-06-12 14:43:55 +02:00
//go:build !android
2022-11-23 13:39:42 +01:00
package dns
import (
"context"
2024-01-30 09:58:56 +01:00
"errors"
2022-11-23 13:39:42 +01:00
"fmt"
2023-02-13 15:25:11 +01:00
"net"
"net/netip"
"time"
2022-11-23 13:39:42 +01:00
"github.com/godbus/dbus/v5"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
2023-08-03 00:05:13 +02:00
nbdns "github.com/netbirdio/netbird/dns"
2022-11-23 13:39:42 +01:00
)
const (
systemdDbusManagerInterface = "org.freedesktop.resolve1.Manager"
systemdResolvedDest = "org.freedesktop.resolve1"
systemdDbusObjectNode = "/org/freedesktop/resolve1"
systemdDbusGetLinkMethod = systemdDbusManagerInterface + ".GetLink"
systemdDbusFlushCachesMethod = systemdDbusManagerInterface + ".FlushCaches"
2022-11-29 13:37:50 +01:00
systemdDbusResolvConfModeProperty = systemdDbusManagerInterface + ".ResolvConfMode"
2022-11-23 13:39:42 +01:00
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
systemdDbusRevertMethodSuffix = systemdDbusLinkInterface + ".Revert"
systemdDbusSetDNSMethodSuffix = systemdDbusLinkInterface + ".SetDNS"
systemdDbusSetDefaultRouteMethodSuffix = systemdDbusLinkInterface + ".SetDefaultRoute"
systemdDbusSetDomainsMethodSuffix = systemdDbusLinkInterface + ".SetDomains"
2022-11-29 13:37:50 +01:00
systemdDbusResolvConfModeForeign = "foreign"
2024-01-30 09:58:56 +01:00
dbusErrorUnknownObject = "org.freedesktop.DBus.Error.UnknownObject"
2022-11-23 13:39:42 +01:00
)
type systemdDbusConfigurator struct {
dbusLinkObject dbus . ObjectPath
routingAll bool
}
// the types below are based on dbus specification, each field is mapped to a dbus type
// see https://dbus.freedesktop.org/doc/dbus-specification.html#basic-types for more details on dbus types
// see https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html on resolve1 input types
// systemdDbusDNSInput maps to a (iay) dbus input for SetDNS method
type systemdDbusDNSInput struct {
Family int32
Address [ ] byte
}
// systemdDbusLinkDomainsInput maps to a (sb) dbus input for SetDomains method
type systemdDbusLinkDomainsInput struct {
Domain string
MatchOnly bool
}
2024-01-30 09:58:56 +01:00
func newSystemdDbusConfigurator ( wgInterface string ) ( hostManager , error ) {
iface , err := net . InterfaceByName ( wgInterface )
2022-11-23 13:39:42 +01:00
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , fmt . Errorf ( "get interface: %w" , err )
2022-11-23 13:39:42 +01:00
}
obj , closeConn , err := getDbusObject ( systemdResolvedDest , systemdDbusObjectNode )
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , fmt . Errorf ( "get dbus resolved dest: %w" , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
var s string
err = obj . Call ( systemdDbusGetLinkMethod , dbusDefaultFlag , iface . Index ) . Store ( & s )
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , fmt . Errorf ( "get dbus link method: %w" , err )
2022-11-23 13:39:42 +01:00
}
log . Debugf ( "got dbus Link interface: %s from net interface %s and index %d" , s , iface . Name , iface . Index )
return & systemdDbusConfigurator {
dbusLinkObject : dbus . ObjectPath ( s ) ,
} , nil
}
2023-05-17 00:03:26 +02:00
func ( s * systemdDbusConfigurator ) supportCustomPort ( ) bool {
return true
}
2023-12-18 11:46:58 +01:00
func ( s * systemdDbusConfigurator ) applyDNSConfig ( config HostDNSConfig ) error {
parsedIP , err := netip . ParseAddr ( config . ServerIP )
2022-12-13 12:26:48 +01:00
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "unable to parse ip address, error: %w" , err )
2022-12-13 12:26:48 +01:00
}
ipAs4 := parsedIP . As4 ( )
2022-11-23 13:39:42 +01:00
defaultLinkInput := systemdDbusDNSInput {
Family : unix . AF_INET ,
2022-12-13 12:26:48 +01:00
Address : ipAs4 [ : ] ,
2022-11-23 13:39:42 +01:00
}
2022-12-13 12:26:48 +01:00
err = s . callLinkMethod ( systemdDbusSetDNSMethodSuffix , [ ] systemdDbusDNSInput { defaultLinkInput } )
2022-11-23 13:39:42 +01:00
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "setting the interface DNS server %s:%d failed with error: %w" , config . ServerIP , config . ServerPort , err )
2022-11-23 13:39:42 +01:00
}
var (
searchDomains [ ] string
matchDomains [ ] string
domainsInput [ ] systemdDbusLinkDomainsInput
)
2023-12-18 11:46:58 +01:00
for _ , dConf := range config . Domains {
if dConf . Disabled {
2023-02-13 15:25:11 +01:00
continue
}
2022-11-23 13:39:42 +01:00
domainsInput = append ( domainsInput , systemdDbusLinkDomainsInput {
2023-12-18 11:46:58 +01:00
Domain : dns . Fqdn ( dConf . Domain ) ,
MatchOnly : dConf . MatchOnly ,
2022-11-23 13:39:42 +01:00
} )
2023-12-18 11:46:58 +01:00
if dConf . MatchOnly {
matchDomains = append ( matchDomains , dConf . Domain )
2022-11-23 13:39:42 +01:00
continue
}
2023-12-18 11:46:58 +01:00
searchDomains = append ( searchDomains , dConf . Domain )
2022-11-23 13:39:42 +01:00
}
2023-12-18 11:46:58 +01:00
if config . RouteAll {
log . Infof ( "configured %s:%d as main DNS forwarder for this peer" , config . ServerIP , config . ServerPort )
2022-11-23 13:39:42 +01:00
err = s . callLinkMethod ( systemdDbusSetDefaultRouteMethodSuffix , true )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "setting link as default dns router, failed with error: %w" , err )
2022-11-23 13:39:42 +01:00
}
2023-08-03 00:05:13 +02:00
domainsInput = append ( domainsInput , systemdDbusLinkDomainsInput {
Domain : nbdns . RootZone ,
MatchOnly : true ,
} )
2022-11-23 13:39:42 +01:00
s . routingAll = true
} else if s . routingAll {
2023-12-18 11:46:58 +01:00
log . Infof ( "removing %s:%d as main DNS forwarder for this peer" , config . ServerIP , config . ServerPort )
2022-11-23 13:39:42 +01:00
}
2024-01-30 09:58:56 +01:00
// create a backup for unclean shutdown detection before adding domains, as these might end up in the resolv.conf file.
// The file content itself is not important for systemd restoration
if err := createUncleanShutdownIndicator ( defaultResolvConfPath , systemdManager , parsedIP . String ( ) ) ; err != nil {
log . Errorf ( "failed to create unclean shutdown resolv.conf backup: %s" , err )
}
2022-11-23 13:39:42 +01:00
log . Infof ( "adding %d search domains and %d match domains. Search list: %s , Match list: %s" , len ( searchDomains ) , len ( matchDomains ) , searchDomains , matchDomains )
err = s . setDomainsForInterface ( domainsInput )
if err != nil {
log . Error ( err )
}
return nil
}
func ( s * systemdDbusConfigurator ) setDomainsForInterface ( domainsInput [ ] systemdDbusLinkDomainsInput ) error {
err := s . callLinkMethod ( systemdDbusSetDomainsMethodSuffix , domainsInput )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "setting domains configuration failed with error: %w" , err )
2022-11-23 13:39:42 +01:00
}
return s . flushCaches ( )
}
func ( s * systemdDbusConfigurator ) restoreHostDNS ( ) error {
log . Infof ( "reverting link settings and flushing cache" )
if ! isDbusListenerRunning ( systemdResolvedDest , s . dbusLinkObject ) {
return nil
}
2024-01-30 09:58:56 +01:00
// this call is required for DNS cleanup, even if it fails
2022-11-23 13:39:42 +01:00
err := s . callLinkMethod ( systemdDbusRevertMethodSuffix , nil )
if err != nil {
2024-01-30 09:58:56 +01:00
var dbusErr dbus . Error
if errors . As ( err , & dbusErr ) && dbusErr . Name == dbusErrorUnknownObject {
// interface is gone already
return nil
}
return fmt . Errorf ( "unable to revert link configuration, got error: %w" , err )
}
if err := removeUncleanShutdownIndicator ( ) ; err != nil {
log . Errorf ( "failed to remove unclean shutdown resolv.conf backup: %s" , err )
2022-11-23 13:39:42 +01:00
}
2024-01-30 09:58:56 +01:00
2022-11-23 13:39:42 +01:00
return s . flushCaches ( )
}
func ( s * systemdDbusConfigurator ) flushCaches ( ) error {
obj , closeConn , err := getDbusObject ( systemdResolvedDest , systemdDbusObjectNode )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the object %s, err: %w" , systemdDbusObjectNode , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
ctx , cancel := context . WithTimeout ( context . TODO ( ) , 5 * time . Second )
defer cancel ( )
err = obj . CallWithContext ( ctx , systemdDbusFlushCachesMethod , dbusDefaultFlag ) . Store ( )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "calling the FlushCaches method with context, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
return nil
}
func ( s * systemdDbusConfigurator ) callLinkMethod ( method string , value any ) error {
obj , closeConn , err := getDbusObject ( systemdResolvedDest , s . dbusLinkObject )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the object, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
ctx , cancel := context . WithTimeout ( context . TODO ( ) , 5 * time . Second )
defer cancel ( )
if value != nil {
err = obj . CallWithContext ( ctx , method , dbusDefaultFlag , value ) . Store ( )
} else {
err = obj . CallWithContext ( ctx , method , dbusDefaultFlag ) . Store ( )
}
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "calling command with context, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
return nil
}
2022-11-29 13:37:50 +01:00
2024-01-30 09:58:56 +01:00
func ( s * systemdDbusConfigurator ) restoreUncleanShutdownDNS ( * netip . Addr ) error {
if err := s . restoreHostDNS ( ) ; err != nil {
return fmt . Errorf ( "restoring dns via systemd: %w" , err )
}
return nil
}
2022-11-29 13:37:50 +01:00
func getSystemdDbusProperty ( property string , store any ) error {
obj , closeConn , err := getDbusObject ( systemdResolvedDest , systemdDbusObjectNode )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the systemd dns manager object, error: %w" , err )
2022-11-29 13:37:50 +01:00
}
defer closeConn ( )
v , e := obj . GetProperty ( property )
if e != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "getting property %s: %w" , property , e )
2022-11-29 13:37:50 +01:00
}
return v . Store ( store )
}
2024-06-13 13:24:24 +02:00
func isSystemdResolvedRunning ( ) bool {
return isDbusListenerRunning ( systemdResolvedDest , systemdDbusObjectNode )
}
func isSystemdResolveConfMode ( ) bool {
if ! isDbusListenerRunning ( systemdResolvedDest , systemdDbusObjectNode ) {
return false
}
var value string
if err := getSystemdDbusProperty ( systemdDbusResolvConfModeProperty , & value ) ; err != nil {
log . Errorf ( "got an error while checking systemd resolv conf mode, error: %s" , err )
return false
}
if value == systemdDbusResolvConfModeForeign {
return true
}
return false
}