2024-06-13 13:24:24 +02:00
//go:build (linux && !android) || freebsd
2023-06-12 14:43:55 +02:00
2022-11-23 13:39:42 +01:00
package dns
import (
"context"
"encoding/binary"
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/netip"
2024-01-30 09:58:56 +01:00
"strings"
2023-02-13 15:25:11 +01:00
"time"
2022-11-23 13:39:42 +01:00
"github.com/godbus/dbus/v5"
"github.com/hashicorp/go-version"
"github.com/miekg/dns"
log "github.com/sirupsen/logrus"
2023-12-18 11:46:58 +01:00
nbversion "github.com/netbirdio/netbird/version"
2022-11-23 13:39:42 +01:00
)
const (
networkManagerDest = "org.freedesktop.NetworkManager"
networkManagerDbusObjectNode = "/org/freedesktop/NetworkManager"
networkManagerDbusDNSManagerInterface = "org.freedesktop.NetworkManager.DnsManager"
networkManagerDbusDNSManagerObjectNode = networkManagerDbusObjectNode + "/DnsManager"
networkManagerDbusDNSManagerModeProperty = networkManagerDbusDNSManagerInterface + ".Mode"
networkManagerDbusDNSManagerRcManagerProperty = networkManagerDbusDNSManagerInterface + ".RcManager"
networkManagerDbusVersionProperty = "org.freedesktop.NetworkManager.Version"
networkManagerDbusGetDeviceByIPIfaceMethod = networkManagerDest + ".GetDeviceByIpIface"
networkManagerDbusDeviceInterface = "org.freedesktop.NetworkManager.Device"
networkManagerDbusDeviceGetAppliedConnectionMethod = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
networkManagerDbusDeviceReapplyMethod = networkManagerDbusDeviceInterface + ".Reapply"
networkManagerDbusDeviceDeleteMethod = networkManagerDbusDeviceInterface + ".Delete"
networkManagerDbusDefaultBehaviorFlag networkManagerConfigBehavior = 0
networkManagerDbusIPv4Key = "ipv4"
networkManagerDbusIPv6Key = "ipv6"
networkManagerDbusDNSKey = "dns"
networkManagerDbusDNSSearchKey = "dns-search"
networkManagerDbusDNSPriorityKey = "dns-priority"
// dns priority doc https://wiki.gnome.org/Projects/NetworkManager/DNS
networkManagerDbusPrimaryDNSPriority int32 = - 500
networkManagerDbusWithMatchDomainPriority int32 = 0
networkManagerDbusSearchDomainOnlyPriority int32 = 50
)
2024-01-30 09:58:56 +01:00
var supportedNetworkManagerVersionConstraints = [ ] string {
">= 1.16, < 1.27" ,
">= 1.44, < 1.45" ,
}
2022-11-23 13:39:42 +01:00
type networkManagerDbusConfigurator 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://networkmanager.dev/docs/api/latest/gdbus-org.freedesktop.NetworkManager.Device.html on Network Manager input types
// networkManagerConnSettings maps to a (a{sa{sv}}) dbus output from GetAppliedConnection and input for Reapply methods
type networkManagerConnSettings map [ string ] map [ string ] dbus . Variant
// networkManagerConfigVersion maps to a (t) dbus output from GetAppliedConnection and input for Reapply methods
type networkManagerConfigVersion uint64
// networkManagerConfigBehavior maps to a (u) dbus input for GetAppliedConnection and Reapply methods
type networkManagerConfigBehavior uint32
// cleanDeprecatedSettings cleans deprecated settings that still returned by
// the GetAppliedConnection methods but can't be reApplied
func ( s networkManagerConnSettings ) cleanDeprecatedSettings ( ) {
for _ , key := range [ ] string { "addresses" , "routes" } {
delete ( s [ networkManagerDbusIPv4Key ] , key )
delete ( s [ networkManagerDbusIPv6Key ] , key )
}
}
2024-01-30 09:58:56 +01:00
func newNetworkManagerDbusConfigurator ( wgInterface string ) ( hostManager , error ) {
2022-11-23 13:39:42 +01:00
obj , closeConn , err := getDbusObject ( networkManagerDest , networkManagerDbusObjectNode )
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , fmt . Errorf ( "get nm dbus: %w" , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
var s string
2024-01-30 09:58:56 +01:00
err = obj . Call ( networkManagerDbusGetDeviceByIPIfaceMethod , dbusDefaultFlag , wgInterface ) . Store ( & s )
2022-11-23 13:39:42 +01:00
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , fmt . Errorf ( "call: %w" , err )
2022-11-23 13:39:42 +01:00
}
2024-01-30 09:58:56 +01:00
log . Debugf ( "got network manager dbus Link Object: %s from net interface %s" , s , wgInterface )
2022-11-23 13:39:42 +01:00
return & networkManagerDbusConfigurator {
dbusLinkObject : dbus . ObjectPath ( s ) ,
} , nil
}
2023-05-17 00:03:26 +02:00
func ( n * networkManagerDbusConfigurator ) supportCustomPort ( ) bool {
return false
}
2023-12-18 11:46:58 +01:00
func ( n * networkManagerDbusConfigurator ) applyDNSConfig ( config HostDNSConfig ) error {
2022-11-23 13:39:42 +01:00
connSettings , configVersion , err := n . getAppliedConnectionSettings ( )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "retrieving the applied connection settings, error: %w" , err )
2022-11-23 13:39:42 +01:00
}
connSettings . cleanDeprecatedSettings ( )
2023-12-18 11:46:58 +01:00
dnsIP , 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
}
2022-11-23 13:39:42 +01:00
convDNSIP := binary . LittleEndian . Uint32 ( dnsIP . AsSlice ( ) )
connSettings [ networkManagerDbusIPv4Key ] [ networkManagerDbusDNSKey ] = dbus . MakeVariant ( [ ] uint32 { convDNSIP } )
var (
searchDomains [ ] string
matchDomains [ ] string
)
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
}
2023-12-18 11:46:58 +01:00
if dConf . MatchOnly {
matchDomains = append ( matchDomains , "~." + dns . Fqdn ( dConf . Domain ) )
2022-11-23 13:39:42 +01:00
continue
}
2023-12-18 11:46:58 +01:00
searchDomains = append ( searchDomains , dns . Fqdn ( dConf . Domain ) )
2022-11-23 13:39:42 +01:00
}
2023-11-27 16:40:02 +01:00
newDomainList := append ( searchDomains , matchDomains ... ) //nolint:gocritic
2022-11-23 13:39:42 +01:00
priority := networkManagerDbusSearchDomainOnlyPriority
switch {
2023-12-18 11:46:58 +01:00
case config . RouteAll :
2022-11-23 13:39:42 +01:00
priority = networkManagerDbusPrimaryDNSPriority
newDomainList = append ( newDomainList , "~." )
if ! n . routingAll {
2023-12-18 11:46:58 +01:00
log . Infof ( "configured %s:%d as main DNS forwarder for this peer" , config . ServerIP , config . ServerPort )
2022-11-23 13:39:42 +01:00
}
case len ( matchDomains ) > 0 :
priority = networkManagerDbusWithMatchDomainPriority
}
if priority != networkManagerDbusPrimaryDNSPriority && n . 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
n . routingAll = false
}
connSettings [ networkManagerDbusIPv4Key ] [ networkManagerDbusDNSPriorityKey ] = dbus . MakeVariant ( priority )
connSettings [ networkManagerDbusIPv4Key ] [ networkManagerDbusDNSSearchKey ] = dbus . MakeVariant ( newDomainList )
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 network-manager restoration
if err := createUncleanShutdownIndicator ( defaultResolvConfPath , networkManager , dnsIP . 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 = n . reApplyConnectionSettings ( connSettings , configVersion )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "reapplying the connection with new settings, error: %w" , err )
2022-11-23 13:39:42 +01:00
}
return nil
}
func ( n * networkManagerDbusConfigurator ) restoreHostDNS ( ) error {
// once the interface is gone network manager cleans all config associated with it
2024-01-30 09:58:56 +01:00
if err := n . deleteConnectionSettings ( ) ; err != nil {
return fmt . Errorf ( "delete connection settings: %w" , err )
}
if err := removeUncleanShutdownIndicator ( ) ; err != nil {
log . Errorf ( "failed to remove unclean shutdown resolv.conf backup: %s" , err )
}
return nil
2022-11-23 13:39:42 +01:00
}
func ( n * networkManagerDbusConfigurator ) getAppliedConnectionSettings ( ) ( networkManagerConnSettings , networkManagerConfigVersion , error ) {
obj , closeConn , err := getDbusObject ( networkManagerDest , n . dbusLinkObject )
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , 0 , fmt . Errorf ( "attempting to retrieve the applied connection settings, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
ctx , cancel := context . WithTimeout ( context . TODO ( ) , 5 * time . Second )
defer cancel ( )
var (
connSettings networkManagerConnSettings
configVersion networkManagerConfigVersion
)
err = obj . CallWithContext ( ctx , networkManagerDbusDeviceGetAppliedConnectionMethod , dbusDefaultFlag ,
networkManagerDbusDefaultBehaviorFlag ) . Store ( & connSettings , & configVersion )
if err != nil {
2024-01-30 09:58:56 +01:00
return nil , 0 , fmt . Errorf ( "calling GetAppliedConnection method with context, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
return connSettings , configVersion , nil
}
func ( n * networkManagerDbusConfigurator ) reApplyConnectionSettings ( connSettings networkManagerConnSettings , configVersion networkManagerConfigVersion ) error {
obj , closeConn , err := getDbusObject ( networkManagerDest , n . dbusLinkObject )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the applied connection settings, err: %w" , 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 , networkManagerDbusDeviceReapplyMethod , dbusDefaultFlag ,
connSettings , configVersion , networkManagerDbusDefaultBehaviorFlag ) . Store ( )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "calling ReApply method with context, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
return nil
}
func ( n * networkManagerDbusConfigurator ) deleteConnectionSettings ( ) error {
obj , closeConn , err := getDbusObject ( networkManagerDest , n . dbusLinkObject )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the applied connection settings, err: %w" , err )
2022-11-23 13:39:42 +01:00
}
defer closeConn ( )
ctx , cancel := context . WithTimeout ( context . TODO ( ) , 5 * time . Second )
defer cancel ( )
2024-01-30 09:58:56 +01:00
// this call is required to remove the device for DNS cleanup, even if it fails
2022-11-23 13:39:42 +01:00
err = obj . CallWithContext ( ctx , networkManagerDbusDeviceDeleteMethod , dbusDefaultFlag ) . Store ( )
if err != nil {
2024-01-30 09:58:56 +01:00
var dbusErr dbus . Error
if errors . As ( err , & dbusErr ) && dbusErr . Name == dbus . ErrMsgUnknownMethod . Name {
// interface is gone already
return nil
}
return fmt . Errorf ( "calling delete method with context, err: %s" , err )
2022-11-23 13:39:42 +01:00
}
return nil
}
2024-01-30 09:58:56 +01:00
func ( n * networkManagerDbusConfigurator ) restoreUncleanShutdownDNS ( * netip . Addr ) error {
if err := n . restoreHostDNS ( ) ; err != nil {
return fmt . Errorf ( "restoring dns via network-manager: %w" , err )
}
return nil
}
2022-11-23 13:39:42 +01:00
func isNetworkManagerSupported ( ) bool {
return isNetworkManagerSupportedVersion ( ) && isNetworkManagerSupportedMode ( )
}
func isNetworkManagerSupportedMode ( ) bool {
var mode string
err := getNetworkManagerDNSProperty ( networkManagerDbusDNSManagerModeProperty , & mode )
if err != nil {
log . Error ( err )
return false
}
switch mode {
case "dnsmasq" , "unbound" , "systemd-resolved" :
return true
default :
var rcManager string
err = getNetworkManagerDNSProperty ( networkManagerDbusDNSManagerRcManagerProperty , & rcManager )
if err != nil {
log . Error ( err )
return false
}
if rcManager == "unmanaged" {
return false
}
}
return true
}
func getNetworkManagerDNSProperty ( property string , store any ) error {
obj , closeConn , err := getDbusObject ( networkManagerDest , networkManagerDbusDNSManagerObjectNode )
if err != nil {
2024-01-30 09:58:56 +01:00
return fmt . Errorf ( "attempting to retrieve the network manager dns manager object, error: %w" , err )
2022-11-23 13:39:42 +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-23 13:39:42 +01:00
}
return v . Store ( store )
}
func isNetworkManagerSupportedVersion ( ) bool {
obj , closeConn , err := getDbusObject ( networkManagerDest , networkManagerDbusObjectNode )
if err != nil {
log . Errorf ( "got error while attempting to get the network manager object, err: %s" , err )
return false
}
defer closeConn ( )
value , err := obj . GetProperty ( networkManagerDbusVersionProperty )
if err != nil {
log . Errorf ( "unable to retrieve network manager mode, got error: %s" , err )
return false
}
versionValue , err := parseVersion ( value . Value ( ) . ( string ) )
if err != nil {
2024-01-30 09:58:56 +01:00
log . Errorf ( "nm: parse version: %s" , err )
2022-11-23 13:39:42 +01:00
return false
}
2024-01-30 09:58:56 +01:00
var supported bool
for _ , constraint := range supportedNetworkManagerVersionConstraints {
constr , err := version . NewConstraint ( constraint )
if err != nil {
log . Errorf ( "nm: create constraint: %s" , err )
return false
}
if met := constr . Check ( versionValue ) ; met {
supported = true
break
}
2022-11-23 13:39:42 +01:00
}
2024-01-30 09:58:56 +01:00
log . Debugf ( "network manager constraints [%s] met: %t" , strings . Join ( supportedNetworkManagerVersionConstraints , " | " ) , supported )
return supported
2022-11-23 13:39:42 +01:00
}
func parseVersion ( inputVersion string ) ( * version . Version , error ) {
2023-11-27 13:01:00 +01:00
if inputVersion == "" || ! nbversion . SemverRegexp . MatchString ( inputVersion ) {
2022-11-23 13:39:42 +01:00
return nil , fmt . Errorf ( "couldn't parse the provided version: Not SemVer" )
}
verObj , err := version . NewVersion ( inputVersion )
if err != nil {
return nil , err
}
return verObj , nil
}