mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-12 17:59:06 +02:00
Pass the resolver port to systemd-resolved through SetDNSEx
This commit is contained in:
@@ -12,6 +12,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/godbus/dbus/v5/introspect"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/unix"
|
||||
|
||||
@@ -29,6 +30,8 @@ const (
|
||||
systemdDbusLinkInterface = "org.freedesktop.resolve1.Link"
|
||||
systemdDbusRevertMethodSuffix = systemdDbusLinkInterface + ".Revert"
|
||||
systemdDbusSetDNSMethodSuffix = systemdDbusLinkInterface + ".SetDNS"
|
||||
setDNSExMethodName = "SetDNSEx"
|
||||
systemdDbusSetDNSExMethodSuffix = systemdDbusLinkInterface + "." + setDNSExMethodName
|
||||
systemdDbusSetDefaultRouteMethodSuffix = systemdDbusLinkInterface + ".SetDefaultRoute"
|
||||
systemdDbusSetDomainsMethodSuffix = systemdDbusLinkInterface + ".SetDomains"
|
||||
systemdDbusSetDNSSECMethodSuffix = systemdDbusLinkInterface + ".SetDNSSEC"
|
||||
@@ -41,9 +44,13 @@ const (
|
||||
)
|
||||
|
||||
type systemdDbusConfigurator struct {
|
||||
dbusLinkObject dbus.ObjectPath
|
||||
ifaceName string
|
||||
wgIndex int
|
||||
dbusLinkObject dbus.ObjectPath
|
||||
ifaceName string
|
||||
wgIndex int
|
||||
// supportsDNSEx reports whether resolved exposes SetDNSEx on the link, which
|
||||
// is the only way to hand it a resolver port. SetDNS carries the address
|
||||
// alone, so on older resolved a resolver off port 53 cannot be advertised.
|
||||
supportsDNSEx bool
|
||||
origNameservers []netip.Addr
|
||||
}
|
||||
|
||||
@@ -61,6 +68,17 @@ type systemdDbusDNSInput struct {
|
||||
Address []byte
|
||||
}
|
||||
|
||||
// systemdDbusDNSInputEx maps to a (iayqs) dbus input for the SetDNSEx method,
|
||||
// which takes a port and a server name on top of the address SetDNS accepts. A
|
||||
// zero port means the default, and an empty name means no name is pinned for
|
||||
// DNS-over-TLS validation.
|
||||
type systemdDbusDNSInputEx struct {
|
||||
Family int32
|
||||
Address []byte
|
||||
Port uint16
|
||||
Name string
|
||||
}
|
||||
|
||||
// systemdDbusLinkDomainsInput maps to a (sb) dbus input for SetDomains method
|
||||
type systemdDbusLinkDomainsInput struct {
|
||||
Domain string
|
||||
@@ -93,6 +111,12 @@ func newSystemdDbusConfigurator(wgInterface string) (*systemdDbusConfigurator, e
|
||||
wgIndex: iface.Index,
|
||||
}
|
||||
|
||||
c.supportsDNSEx = c.hasDNSExMethod()
|
||||
if !c.supportsDNSEx {
|
||||
log.Infof("systemd-resolved does not expose %s; a DNS resolver on a port other than %d cannot be advertised to it",
|
||||
setDNSExMethodName, DefaultPort)
|
||||
}
|
||||
|
||||
origNameservers, err := c.captureOriginalNameservers()
|
||||
switch {
|
||||
case err != nil:
|
||||
@@ -229,19 +253,65 @@ func (s *systemdDbusConfigurator) getOriginalNameservers() []netip.Addr {
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
||||
return true
|
||||
return s.supportsDNSEx
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
|
||||
// hasDNSExMethod reports whether resolved exposes SetDNSEx on the link
|
||||
// interface. It was added in systemd 240; before that, resolved has no way to
|
||||
// accept a resolver port.
|
||||
func (s *systemdDbusConfigurator) hasDNSExMethod() bool {
|
||||
obj, closeConn, err := getDbusObject(systemdResolvedDest, s.dbusLinkObject)
|
||||
if err != nil {
|
||||
log.Debugf("failed to get dbus link object for introspection: %v", err)
|
||||
return false
|
||||
}
|
||||
defer closeConn()
|
||||
|
||||
node, err := introspect.Call(obj)
|
||||
if err != nil {
|
||||
log.Debugf("failed to introspect systemd-resolved link: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
for _, iface := range node.Interfaces {
|
||||
if iface.Name != systemdDbusLinkInterface {
|
||||
continue
|
||||
}
|
||||
return slices.ContainsFunc(iface.Methods, func(m introspect.Method) bool {
|
||||
return m.Name == setDNSExMethodName
|
||||
})
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// dnsServerMethod picks the resolved method that can carry the resolver's
|
||||
// address and port, and builds its input. SetDNS takes the address alone, so a
|
||||
// resolver on a port other than 53 needs SetDNSEx; the caller is expected to
|
||||
// have checked supportCustomPort before asking for one.
|
||||
func (s *systemdDbusConfigurator) dnsServerMethod(config HostDNSConfig) (string, any) {
|
||||
family := int32(unix.AF_INET)
|
||||
if config.ServerIP.Is6() {
|
||||
family = unix.AF_INET6
|
||||
}
|
||||
defaultLinkInput := systemdDbusDNSInput{
|
||||
|
||||
if config.ServerPort == DefaultPort || !s.supportsDNSEx {
|
||||
return systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{{
|
||||
Family: family,
|
||||
Address: config.ServerIP.AsSlice(),
|
||||
}}
|
||||
}
|
||||
|
||||
return systemdDbusSetDNSExMethodSuffix, []systemdDbusDNSInputEx{{
|
||||
Family: family,
|
||||
Address: config.ServerIP.AsSlice(),
|
||||
}
|
||||
if err := s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput}); err != nil {
|
||||
Port: uint16(config.ServerPort),
|
||||
}}
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
|
||||
method, input := s.dnsServerMethod(config)
|
||||
if err := s.callLinkMethod(method, input); err != nil {
|
||||
return fmt.Errorf("set interface DNS server %s:%d: %w", config.ServerIP, config.ServerPort, err)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
//go:build !android
|
||||
|
||||
package dns
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// The dbus signatures are the contract with resolved: SetDNS has no port field,
|
||||
// so a resolver on a fallback port can only be advertised through SetDNSEx.
|
||||
func TestSystemdDNSInputSignatures(t *testing.T) {
|
||||
assert.Equal(t, "(iay)", dbus.SignatureOf(systemdDbusDNSInput{}).String())
|
||||
assert.Equal(t, "(iayqs)", dbus.SignatureOf(systemdDbusDNSInputEx{}).String())
|
||||
}
|
||||
|
||||
func TestSystemdDNSServerMethod(t *testing.T) {
|
||||
serverIP := netip.MustParseAddr("100.66.100.1")
|
||||
serverIPv6 := netip.MustParseAddr("fd00::1")
|
||||
|
||||
t.Run("default port uses SetDNS", func(t *testing.T) {
|
||||
c := &systemdDbusConfigurator{supportsDNSEx: true}
|
||||
|
||||
method, input := c.dnsServerMethod(HostDNSConfig{ServerIP: serverIP, ServerPort: DefaultPort})
|
||||
|
||||
assert.Equal(t, systemdDbusSetDNSMethodSuffix, method)
|
||||
in, ok := input.([]systemdDbusDNSInput)
|
||||
require.True(t, ok, "input type %T", input)
|
||||
require.Len(t, in, 1)
|
||||
assert.Equal(t, int32(unix.AF_INET), in[0].Family)
|
||||
assert.Equal(t, serverIP.AsSlice(), in[0].Address)
|
||||
})
|
||||
|
||||
t.Run("fallback port carries the port through SetDNSEx", func(t *testing.T) {
|
||||
c := &systemdDbusConfigurator{supportsDNSEx: true}
|
||||
|
||||
method, input := c.dnsServerMethod(HostDNSConfig{ServerIP: serverIP, ServerPort: 5053})
|
||||
|
||||
assert.Equal(t, systemdDbusSetDNSExMethodSuffix, method)
|
||||
in, ok := input.([]systemdDbusDNSInputEx)
|
||||
require.True(t, ok, "input type %T", input)
|
||||
require.Len(t, in, 1)
|
||||
assert.Equal(t, int32(unix.AF_INET), in[0].Family)
|
||||
assert.Equal(t, serverIP.AsSlice(), in[0].Address)
|
||||
assert.Equal(t, uint16(5053), in[0].Port)
|
||||
assert.Empty(t, in[0].Name)
|
||||
})
|
||||
|
||||
t.Run("v6 resolver reports the v6 family", func(t *testing.T) {
|
||||
c := &systemdDbusConfigurator{supportsDNSEx: true}
|
||||
|
||||
_, input := c.dnsServerMethod(HostDNSConfig{ServerIP: serverIPv6, ServerPort: 5053})
|
||||
|
||||
in, ok := input.([]systemdDbusDNSInputEx)
|
||||
require.True(t, ok, "input type %T", input)
|
||||
require.Len(t, in, 1)
|
||||
assert.Equal(t, int32(unix.AF_INET6), in[0].Family)
|
||||
})
|
||||
|
||||
// Without SetDNSEx there is nothing to fall back to but the portless call.
|
||||
// supportCustomPort reports false so the caller keeps primary DNS off the
|
||||
// link instead of pointing it at a port resolved will not use.
|
||||
t.Run("no SetDNSEx support falls back to SetDNS", func(t *testing.T) {
|
||||
c := &systemdDbusConfigurator{supportsDNSEx: false}
|
||||
|
||||
method, input := c.dnsServerMethod(HostDNSConfig{ServerIP: serverIP, ServerPort: 5053})
|
||||
|
||||
assert.Equal(t, systemdDbusSetDNSMethodSuffix, method)
|
||||
assert.IsType(t, []systemdDbusDNSInput{}, input)
|
||||
assert.False(t, c.supportCustomPort())
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user