mirror of
https://github.com/netbirdio/netbird.git
synced 2024-11-26 10:03:47 +01:00
7ebe58f20a
* Add DNS list argument for mobile client * Write testable code Many places are checked the wgInterface != nil condition. It is doing it just because to avoid the real wgInterface creation for tests. Instead of this involve a wgInterface interface what is moc-able. * Refactor the DNS server internal code structure With the fake resolver has been involved several if-else statement and generated some unused variables to distinguish the listener and fake resolver solutions at running time. With this commit the fake resolver and listener based solution has been moved into two separated structure. Name of this layer is the 'service'. With this modification the unit test looks simpler and open the option to add new logic for the permanent DNS service usage for mobile systems. * Remove is running check in test We can not ensure the state well so remove this check. The test will fail if the server is not running well.
308 lines
11 KiB
Go
308 lines
11 KiB
Go
//go:build !android
|
|
|
|
package dns
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net/netip"
|
|
"regexp"
|
|
"time"
|
|
|
|
"github.com/godbus/dbus/v5"
|
|
"github.com/hashicorp/go-version"
|
|
"github.com/miekg/dns"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
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
|
|
supportedNetworkManagerVersionConstraint = ">= 1.16, < 1.28"
|
|
)
|
|
|
|
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)
|
|
}
|
|
}
|
|
|
|
func newNetworkManagerDbusConfigurator(wgInterface WGIface) (hostManager, error) {
|
|
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusObjectNode)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer closeConn()
|
|
var s string
|
|
err = obj.Call(networkManagerDbusGetDeviceByIPIfaceMethod, dbusDefaultFlag, wgInterface.Name()).Store(&s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface.Name())
|
|
|
|
return &networkManagerDbusConfigurator{
|
|
dbusLinkObject: dbus.ObjectPath(s),
|
|
}, nil
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
|
|
return false
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) applyDNSConfig(config hostDNSConfig) error {
|
|
connSettings, configVersion, err := n.getAppliedConnectionSettings()
|
|
if err != nil {
|
|
return fmt.Errorf("got an error while retrieving the applied connection settings, error: %s", err)
|
|
}
|
|
|
|
connSettings.cleanDeprecatedSettings()
|
|
|
|
dnsIP, err := netip.ParseAddr(config.serverIP)
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse ip address, error: %s", err)
|
|
}
|
|
convDNSIP := binary.LittleEndian.Uint32(dnsIP.AsSlice())
|
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
|
var (
|
|
searchDomains []string
|
|
matchDomains []string
|
|
)
|
|
for _, dConf := range config.domains {
|
|
if dConf.disabled {
|
|
continue
|
|
}
|
|
if dConf.matchOnly {
|
|
matchDomains = append(matchDomains, "~."+dns.Fqdn(dConf.domain))
|
|
continue
|
|
}
|
|
searchDomains = append(searchDomains, dns.Fqdn(dConf.domain))
|
|
}
|
|
|
|
newDomainList := append(searchDomains, matchDomains...)
|
|
|
|
priority := networkManagerDbusSearchDomainOnlyPriority
|
|
switch {
|
|
case config.routeAll:
|
|
priority = networkManagerDbusPrimaryDNSPriority
|
|
newDomainList = append(newDomainList, "~.")
|
|
if !n.routingAll {
|
|
log.Infof("configured %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
|
}
|
|
case len(matchDomains) > 0:
|
|
priority = networkManagerDbusWithMatchDomainPriority
|
|
}
|
|
|
|
if priority != networkManagerDbusPrimaryDNSPriority && n.routingAll {
|
|
log.Infof("removing %s:%d as main DNS forwarder for this peer", config.serverIP, config.serverPort)
|
|
n.routingAll = false
|
|
}
|
|
|
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(priority)
|
|
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSSearchKey] = dbus.MakeVariant(newDomainList)
|
|
|
|
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 {
|
|
return fmt.Errorf("got an error while reapplying the connection with new settings, error: %s", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) restoreHostDNS() error {
|
|
// once the interface is gone network manager cleans all config associated with it
|
|
return n.deleteConnectionSettings()
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) getAppliedConnectionSettings() (networkManagerConnSettings, networkManagerConfigVersion, error) {
|
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
|
}
|
|
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 {
|
|
return nil, 0, fmt.Errorf("got error while calling GetAppliedConnection method with context, err: %s", err)
|
|
}
|
|
|
|
return connSettings, configVersion, nil
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) reApplyConnectionSettings(connSettings networkManagerConnSettings, configVersion networkManagerConfigVersion) error {
|
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
|
if err != nil {
|
|
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
|
}
|
|
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 {
|
|
return fmt.Errorf("got error while calling ReApply method with context, err: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (n *networkManagerDbusConfigurator) deleteConnectionSettings() error {
|
|
obj, closeConn, err := getDbusObject(networkManagerDest, n.dbusLinkObject)
|
|
if err != nil {
|
|
return fmt.Errorf("got error while attempting to retrieve the applied connection settings, err: %s", err)
|
|
}
|
|
defer closeConn()
|
|
|
|
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
err = obj.CallWithContext(ctx, networkManagerDbusDeviceDeleteMethod, dbusDefaultFlag).Store()
|
|
if err != nil {
|
|
return fmt.Errorf("got error while calling delete method with context, err: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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 {
|
|
return fmt.Errorf("got error while attempting to retrieve the network manager dns manager object, error: %s", err)
|
|
}
|
|
defer closeConn()
|
|
|
|
v, e := obj.GetProperty(property)
|
|
if e != nil {
|
|
return fmt.Errorf("got an error getting property %s: %v", property, e)
|
|
}
|
|
|
|
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 {
|
|
return false
|
|
}
|
|
|
|
constraints, err := version.NewConstraint(supportedNetworkManagerVersionConstraint)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
|
|
return constraints.Check(versionValue)
|
|
}
|
|
|
|
func parseVersion(inputVersion string) (*version.Version, error) {
|
|
reg, err := regexp.Compile(version.SemverRegexpRaw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if inputVersion == "" || !reg.MatchString(inputVersion) {
|
|
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
|
|
}
|