mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-05 00:26:39 +00:00
[client] Add system DNS fallback for Windows, systemd-resolved, NetworkManager (#6000)
This commit is contained in:
@@ -113,7 +113,6 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
fileDescriptor int32,
|
fileDescriptor int32,
|
||||||
networkChangeListener listener.NetworkChangeListener,
|
networkChangeListener listener.NetworkChangeListener,
|
||||||
dnsManager dns.IosDnsManager,
|
dnsManager dns.IosDnsManager,
|
||||||
dnsAddresses []netip.AddrPort,
|
|
||||||
stateFilePath string,
|
stateFilePath string,
|
||||||
) error {
|
) error {
|
||||||
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
// Set GC percent to 5% to reduce memory usage as iOS only allows 50MB of memory for the extension.
|
||||||
@@ -123,7 +122,6 @@ func (c *ConnectClient) RunOniOS(
|
|||||||
FileDescriptor: fileDescriptor,
|
FileDescriptor: fileDescriptor,
|
||||||
NetworkChangeListener: networkChangeListener,
|
NetworkChangeListener: networkChangeListener,
|
||||||
DnsManager: dnsManager,
|
DnsManager: dnsManager,
|
||||||
HostDNSAddresses: dnsAddresses,
|
|
||||||
StateFilePath: stateFilePath,
|
StateFilePath: stateFilePath,
|
||||||
}
|
}
|
||||||
return c.run(mobileDependency, nil, "")
|
return c.run(mobileDependency, nil, "")
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ type hostManager interface {
|
|||||||
restoreHostDNS() error
|
restoreHostDNS() error
|
||||||
supportCustomPort() bool
|
supportCustomPort() bool
|
||||||
string() string
|
string() string
|
||||||
|
// getOriginalNameservers returns the OS-side resolvers used as PriorityFallback
|
||||||
|
// upstreams: pre-takeover snapshots on desktop, the OS-pushed list on Android,
|
||||||
|
// hardcoded Quad9 on iOS, nil for noop / mock.
|
||||||
|
getOriginalNameservers() []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemDNSSettings struct {
|
type SystemDNSSettings struct {
|
||||||
@@ -131,3 +135,11 @@ func (n noopHostConfigurator) supportCustomPort() bool {
|
|||||||
func (n noopHostConfigurator) string() string {
|
func (n noopHostConfigurator) string() string {
|
||||||
return "noop"
|
return "noop"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n noopHostConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mockHostConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,28 +1,43 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/internal/statemanager"
|
"github.com/netbirdio/netbird/client/internal/statemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// androidHostManager is a noop on the OS side (Android's VPN service handles
|
||||||
|
// DNS for us) but tracks the OS-reported resolver list pushed via
|
||||||
|
// OnUpdatedHostDNSServer so it can serve as the fallback nameserver source.
|
||||||
type androidHostManager struct {
|
type androidHostManager struct {
|
||||||
|
holder *hostsDNSHolder
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostManager() (*androidHostManager, error) {
|
func newHostManager(holder *hostsDNSHolder) (*androidHostManager, error) {
|
||||||
return &androidHostManager{}, nil
|
return &androidHostManager{holder: holder}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a androidHostManager) applyDNSConfig(HostDNSConfig, *statemanager.Manager) error {
|
func (a *androidHostManager) applyDNSConfig(HostDNSConfig, *statemanager.Manager) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a androidHostManager) restoreHostDNS() error {
|
func (a *androidHostManager) restoreHostDNS() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a androidHostManager) supportCustomPort() bool {
|
func (a *androidHostManager) supportCustomPort() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a androidHostManager) string() string {
|
func (a *androidHostManager) string() string {
|
||||||
return "none"
|
return "none"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *androidHostManager) getOriginalNameservers() []netip.Addr {
|
||||||
|
hosts := a.holder.get()
|
||||||
|
out := make([]netip.Addr, 0, len(hosts))
|
||||||
|
for ap := range hosts {
|
||||||
|
out = append(out, ap.Addr())
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package dns
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/netip"
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
@@ -14,6 +15,14 @@ type iosHostManager struct {
|
|||||||
config HostDNSConfig
|
config HostDNSConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a iosHostManager) getOriginalNameservers() []netip.Addr {
|
||||||
|
// Quad9 v4+v6: 9.9.9.9, 2620:fe::fe.
|
||||||
|
return []netip.Addr{
|
||||||
|
netip.AddrFrom4([4]byte{9, 9, 9, 9}),
|
||||||
|
netip.AddrFrom16([16]byte{0x26, 0x20, 0x00, 0xfe, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfe}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newHostManager(dnsManager IosDnsManager) (*iosHostManager, error) {
|
func newHostManager(dnsManager IosDnsManager) (*iosHostManager, error) {
|
||||||
return &iosHostManager{
|
return &iosHostManager{
|
||||||
dnsManager: dnsManager,
|
dnsManager: dnsManager,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
@@ -45,7 +46,9 @@ const (
|
|||||||
nrptMaxDomainsPerRule = 50
|
nrptMaxDomainsPerRule = 50
|
||||||
|
|
||||||
interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`
|
interfaceConfigPath = `SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\Interfaces`
|
||||||
|
interfaceConfigPathV6 = `SYSTEM\CurrentControlSet\Services\Tcpip6\Parameters\Interfaces`
|
||||||
interfaceConfigNameServerKey = "NameServer"
|
interfaceConfigNameServerKey = "NameServer"
|
||||||
|
interfaceConfigDhcpNameSrvKey = "DhcpNameServer"
|
||||||
interfaceConfigSearchListKey = "SearchList"
|
interfaceConfigSearchListKey = "SearchList"
|
||||||
|
|
||||||
// Network interface DNS registration settings
|
// Network interface DNS registration settings
|
||||||
@@ -71,6 +74,7 @@ type registryConfigurator struct {
|
|||||||
routingAll bool
|
routingAll bool
|
||||||
gpo bool
|
gpo bool
|
||||||
nrptEntryCount int
|
nrptEntryCount int
|
||||||
|
origNameservers []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
||||||
@@ -94,6 +98,17 @@ func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
|||||||
gpo: useGPO,
|
gpo: useGPO,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
origNameservers, err := configurator.captureOriginalNameservers()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
log.Warnf("capture original nameservers from non-WG adapters: %v", err)
|
||||||
|
case len(origNameservers) == 0:
|
||||||
|
log.Warnf("no original nameservers captured from non-WG adapters; DNS fallback will be empty")
|
||||||
|
default:
|
||||||
|
log.Debugf("captured %d original nameservers from non-WG adapters: %v", len(origNameservers), origNameservers)
|
||||||
|
}
|
||||||
|
configurator.origNameservers = origNameservers
|
||||||
|
|
||||||
if err := configurator.configureInterface(); err != nil {
|
if err := configurator.configureInterface(); err != nil {
|
||||||
log.Errorf("failed to configure interface settings: %v", err)
|
log.Errorf("failed to configure interface settings: %v", err)
|
||||||
}
|
}
|
||||||
@@ -101,6 +116,98 @@ func newHostManager(wgInterface WGIface) (*registryConfigurator, error) {
|
|||||||
return configurator, nil
|
return configurator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// captureOriginalNameservers reads DNS addresses from every Tcpip(6) interface
|
||||||
|
// registry key except the WG adapter. v4 and v6 servers live in separate
|
||||||
|
// hives (Tcpip vs Tcpip6) keyed by the same interface GUID.
|
||||||
|
func (r *registryConfigurator) captureOriginalNameservers() ([]netip.Addr, error) {
|
||||||
|
seen := make(map[netip.Addr]struct{})
|
||||||
|
var out []netip.Addr
|
||||||
|
var merr *multierror.Error
|
||||||
|
for _, root := range []string{interfaceConfigPath, interfaceConfigPathV6} {
|
||||||
|
addrs, err := r.captureFromTcpipRoot(root)
|
||||||
|
if err != nil {
|
||||||
|
merr = multierror.Append(merr, fmt.Errorf("%s: %w", root, err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
if _, dup := seen[addr]; dup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[addr] = struct{}{}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nberrors.FormatErrorOrNil(merr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) captureFromTcpipRoot(rootPath string) ([]netip.Addr, error) {
|
||||||
|
root, err := registry.OpenKey(registry.LOCAL_MACHINE, rootPath, registry.READ)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("open key: %w", err)
|
||||||
|
}
|
||||||
|
defer closer(root)
|
||||||
|
|
||||||
|
guids, err := root.ReadSubKeyNames(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("read subkeys: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, guid := range guids {
|
||||||
|
if strings.EqualFold(guid, r.guid) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, readInterfaceNameservers(rootPath, guid)...)
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readInterfaceNameservers(rootPath, guid string) []netip.Addr {
|
||||||
|
keyPath := rootPath + "\\" + guid
|
||||||
|
k, err := registry.OpenKey(registry.LOCAL_MACHINE, keyPath, registry.QUERY_VALUE)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer closer(k)
|
||||||
|
|
||||||
|
// Static NameServer wins over DhcpNameServer for actual resolution.
|
||||||
|
for _, name := range []string{interfaceConfigNameServerKey, interfaceConfigDhcpNameSrvKey} {
|
||||||
|
raw, _, err := k.GetStringValue(name)
|
||||||
|
if err != nil || raw == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if out := parseRegistryNameservers(raw); len(out) > 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRegistryNameservers(raw string) []netip.Addr {
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, field := range strings.FieldsFunc(raw, func(r rune) bool { return r == ',' || r == ' ' || r == '\t' }) {
|
||||||
|
addr, err := netip.ParseAddr(strings.TrimSpace(field))
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr = addr.Unmap()
|
||||||
|
if !addr.IsValid() || addr.IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Drop unzoned link-local: not routable without a scope id. If
|
||||||
|
// the user wrote "fe80::1%eth0" ParseAddr preserves the zone.
|
||||||
|
if addr.IsLinkLocalUnicast() && addr.Zone() == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *registryConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
|
return slices.Clone(r.origNameservers)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *registryConfigurator) supportCustomPort() bool {
|
func (r *registryConfigurator) supportCustomPort() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ func (h *hostsDNSHolder) set(list []netip.AddrPort) {
|
|||||||
h.mutex.Unlock()
|
h.mutex.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:unused
|
||||||
func (h *hostsDNSHolder) get() map[netip.AddrPort]struct{} {
|
func (h *hostsDNSHolder) get() map[netip.AddrPort]struct{} {
|
||||||
h.mutex.RLock()
|
h.mutex.RLock()
|
||||||
l := h.unprotectedDNSList
|
l := h.unprotectedDNSList
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,6 +33,15 @@ const (
|
|||||||
networkManagerDbusDeviceGetAppliedConnectionMethod = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
|
networkManagerDbusDeviceGetAppliedConnectionMethod = networkManagerDbusDeviceInterface + ".GetAppliedConnection"
|
||||||
networkManagerDbusDeviceReapplyMethod = networkManagerDbusDeviceInterface + ".Reapply"
|
networkManagerDbusDeviceReapplyMethod = networkManagerDbusDeviceInterface + ".Reapply"
|
||||||
networkManagerDbusDeviceDeleteMethod = networkManagerDbusDeviceInterface + ".Delete"
|
networkManagerDbusDeviceDeleteMethod = networkManagerDbusDeviceInterface + ".Delete"
|
||||||
|
networkManagerDbusDeviceIp4ConfigProperty = networkManagerDbusDeviceInterface + ".Ip4Config"
|
||||||
|
networkManagerDbusDeviceIp6ConfigProperty = networkManagerDbusDeviceInterface + ".Ip6Config"
|
||||||
|
networkManagerDbusDeviceIfaceProperty = networkManagerDbusDeviceInterface + ".Interface"
|
||||||
|
networkManagerDbusGetDevicesMethod = networkManagerDest + ".GetDevices"
|
||||||
|
networkManagerDbusIp4ConfigInterface = "org.freedesktop.NetworkManager.IP4Config"
|
||||||
|
networkManagerDbusIp6ConfigInterface = "org.freedesktop.NetworkManager.IP6Config"
|
||||||
|
networkManagerDbusIp4ConfigNameserverDataProperty = networkManagerDbusIp4ConfigInterface + ".NameserverData"
|
||||||
|
networkManagerDbusIp4ConfigNameserversProperty = networkManagerDbusIp4ConfigInterface + ".Nameservers"
|
||||||
|
networkManagerDbusIp6ConfigNameserversProperty = networkManagerDbusIp6ConfigInterface + ".Nameservers"
|
||||||
networkManagerDbusDefaultBehaviorFlag networkManagerConfigBehavior = 0
|
networkManagerDbusDefaultBehaviorFlag networkManagerConfigBehavior = 0
|
||||||
networkManagerDbusIPv4Key = "ipv4"
|
networkManagerDbusIPv4Key = "ipv4"
|
||||||
networkManagerDbusIPv6Key = "ipv6"
|
networkManagerDbusIPv6Key = "ipv6"
|
||||||
@@ -54,6 +64,7 @@ type networkManagerDbusConfigurator struct {
|
|||||||
dbusLinkObject dbus.ObjectPath
|
dbusLinkObject dbus.ObjectPath
|
||||||
routingAll bool
|
routingAll bool
|
||||||
ifaceName string
|
ifaceName string
|
||||||
|
origNameservers []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// the types below are based on dbus specification, each field is mapped to a dbus type
|
// the types below are based on dbus specification, each field is mapped to a dbus type
|
||||||
@@ -92,10 +103,200 @@ func newNetworkManagerDbusConfigurator(wgInterface string) (*networkManagerDbusC
|
|||||||
|
|
||||||
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface)
|
log.Debugf("got network manager dbus Link Object: %s from net interface %s", s, wgInterface)
|
||||||
|
|
||||||
return &networkManagerDbusConfigurator{
|
c := &networkManagerDbusConfigurator{
|
||||||
dbusLinkObject: dbus.ObjectPath(s),
|
dbusLinkObject: dbus.ObjectPath(s),
|
||||||
ifaceName: wgInterface,
|
ifaceName: wgInterface,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
origNameservers, err := c.captureOriginalNameservers()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
log.Warnf("capture original nameservers from NetworkManager: %v", err)
|
||||||
|
case len(origNameservers) == 0:
|
||||||
|
log.Warnf("no original nameservers captured from non-WG NetworkManager devices; DNS fallback will be empty")
|
||||||
|
default:
|
||||||
|
log.Debugf("captured %d original nameservers from non-WG NetworkManager devices: %v", len(origNameservers), origNameservers)
|
||||||
|
}
|
||||||
|
c.origNameservers = origNameservers
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// captureOriginalNameservers reads DNS servers from every NM device's
|
||||||
|
// IP4Config / IP6Config except our WG device.
|
||||||
|
func (n *networkManagerDbusConfigurator) captureOriginalNameservers() ([]netip.Addr, error) {
|
||||||
|
devices, err := networkManagerListDevices()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list devices: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[netip.Addr]struct{})
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, dev := range devices {
|
||||||
|
if dev == n.dbusLinkObject {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ifaceName := readNetworkManagerDeviceInterface(dev)
|
||||||
|
for _, addr := range readNetworkManagerDeviceDNS(dev) {
|
||||||
|
addr = addr.Unmap()
|
||||||
|
if !addr.IsValid() || addr.IsUnspecified() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// IP6Config.Nameservers is a byte slice without zone info;
|
||||||
|
// reattach the device's interface name so a captured fe80::…
|
||||||
|
// stays routable.
|
||||||
|
if addr.IsLinkLocalUnicast() && ifaceName != "" {
|
||||||
|
addr = addr.WithZone(ifaceName)
|
||||||
|
}
|
||||||
|
if _, dup := seen[addr]; dup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[addr] = struct{}{}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkManagerDeviceInterface(devicePath dbus.ObjectPath) string {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, devicePath)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
v, err := obj.GetProperty(networkManagerDbusDeviceIfaceProperty)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
s, _ := v.Value().(string)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func networkManagerListDevices() ([]dbus.ObjectPath, error) {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, networkManagerDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("dbus NetworkManager: %w", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
var devs []dbus.ObjectPath
|
||||||
|
if err := obj.Call(networkManagerDbusGetDevicesMethod, dbusDefaultFlag).Store(&devs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return devs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkManagerDeviceDNS(devicePath dbus.ObjectPath) []netip.Addr {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, devicePath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
var out []netip.Addr
|
||||||
|
if path := readNetworkManagerConfigPath(obj, networkManagerDbusDeviceIp4ConfigProperty); path != "" {
|
||||||
|
out = append(out, readIPv4ConfigDNS(path)...)
|
||||||
|
}
|
||||||
|
if path := readNetworkManagerConfigPath(obj, networkManagerDbusDeviceIp6ConfigProperty); path != "" {
|
||||||
|
out = append(out, readIPv6ConfigDNS(path)...)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNetworkManagerConfigPath(obj dbus.BusObject, property string) dbus.ObjectPath {
|
||||||
|
v, err := obj.GetProperty(property)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
path, ok := v.Value().(dbus.ObjectPath)
|
||||||
|
if !ok || path == "/" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIPv4ConfigDNS(path dbus.ObjectPath) []netip.Addr {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
|
||||||
|
// NameserverData (NM 1.13+) carries strings; older NMs only expose the
|
||||||
|
// legacy uint32 Nameservers property.
|
||||||
|
if out := readIPv4NameserverData(obj); len(out) > 0 {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
return readIPv4LegacyNameservers(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIPv4NameserverData(obj dbus.BusObject) []netip.Addr {
|
||||||
|
v, err := obj.GetProperty(networkManagerDbusIp4ConfigNameserverDataProperty)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
entries, ok := v.Value().([]map[string]dbus.Variant)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, entry := range entries {
|
||||||
|
addrVar, ok := entry["address"]
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, ok := addrVar.Value().(string)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if a, err := netip.ParseAddr(s); err == nil {
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIPv4LegacyNameservers(obj dbus.BusObject) []netip.Addr {
|
||||||
|
v, err := obj.GetProperty(networkManagerDbusIp4ConfigNameserversProperty)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
raw, ok := v.Value().([]uint32)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]netip.Addr, 0, len(raw))
|
||||||
|
for _, n := range raw {
|
||||||
|
var b [4]byte
|
||||||
|
binary.LittleEndian.PutUint32(b[:], n)
|
||||||
|
out = append(out, netip.AddrFrom4(b))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func readIPv6ConfigDNS(path dbus.ObjectPath) []netip.Addr {
|
||||||
|
obj, closeConn, err := getDbusObject(networkManagerDest, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
v, err := obj.GetProperty(networkManagerDbusIp6ConfigNameserversProperty)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
raw, ok := v.Value().([][]byte)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := make([]netip.Addr, 0, len(raw))
|
||||||
|
for _, b := range raw {
|
||||||
|
if a, ok := netip.AddrFromSlice(b); ok {
|
||||||
|
out = append(out, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *networkManagerDbusConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
|
return slices.Clone(n.origNameservers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
|
func (n *networkManagerDbusConfigurator) supportCustomPort() bool {
|
||||||
|
|||||||
@@ -125,12 +125,6 @@ const (
|
|||||||
nsVerdictUnhealthy
|
nsVerdictUnhealthy
|
||||||
)
|
)
|
||||||
|
|
||||||
// hostManagerWithOriginalNS extends the basic hostManager interface
|
|
||||||
type hostManagerWithOriginalNS interface {
|
|
||||||
hostManager
|
|
||||||
getOriginalNameservers() []netip.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// DefaultServer dns server object
|
// DefaultServer dns server object
|
||||||
type DefaultServer struct {
|
type DefaultServer struct {
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
@@ -159,6 +153,11 @@ type DefaultServer struct {
|
|||||||
permanent bool
|
permanent bool
|
||||||
hostsDNSHolder *hostsDNSHolder
|
hostsDNSHolder *hostsDNSHolder
|
||||||
|
|
||||||
|
// fallbackHandler is the upstream resolver currently registered at
|
||||||
|
// PriorityFallback. Tracked so registerFallback can Stop() the previous
|
||||||
|
// instance instead of leaking its context.
|
||||||
|
fallbackHandler handlerWithStop
|
||||||
|
|
||||||
// make sense on mobile only
|
// make sense on mobile only
|
||||||
searchDomainNotifier *notifier
|
searchDomainNotifier *notifier
|
||||||
iosDnsManager IosDnsManager
|
iosDnsManager IosDnsManager
|
||||||
@@ -248,7 +247,6 @@ func NewDefaultServerPermanentUpstream(
|
|||||||
|
|
||||||
ds.hostsDNSHolder.set(hostsDnsList)
|
ds.hostsDNSHolder.set(hostsDnsList)
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.addHostRootZone()
|
|
||||||
ds.currentConfig = dnsConfigToHostDNSConfig(config, ds.service.RuntimeIP(), ds.service.RuntimePort())
|
ds.currentConfig = dnsConfigToHostDNSConfig(config, ds.service.RuntimeIP(), ds.service.RuntimePort())
|
||||||
ds.searchDomainNotifier = newNotifier(ds.SearchDomains())
|
ds.searchDomainNotifier = newNotifier(ds.SearchDomains())
|
||||||
ds.searchDomainNotifier.setListener(listener)
|
ds.searchDomainNotifier.setListener(listener)
|
||||||
@@ -256,21 +254,17 @@ func NewDefaultServerPermanentUpstream(
|
|||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDefaultServerIos returns a new dns server. It optimized for ios
|
// NewDefaultServerIos returns a new dns server. It optimized for ios.
|
||||||
func NewDefaultServerIos(
|
func NewDefaultServerIos(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
wgInterface WGIface,
|
wgInterface WGIface,
|
||||||
iosDnsManager IosDnsManager,
|
iosDnsManager IosDnsManager,
|
||||||
hostsDnsList []netip.AddrPort,
|
|
||||||
statusRecorder *peer.Status,
|
statusRecorder *peer.Status,
|
||||||
disableSys bool,
|
disableSys bool,
|
||||||
) *DefaultServer {
|
) *DefaultServer {
|
||||||
log.Debugf("iOS host dns address list is: %v", hostsDnsList)
|
|
||||||
ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys)
|
ds := newDefaultServer(ctx, wgInterface, NewServiceViaMemory(wgInterface), statusRecorder, nil, disableSys)
|
||||||
ds.iosDnsManager = iosDnsManager
|
ds.iosDnsManager = iosDnsManager
|
||||||
ds.hostsDNSHolder.set(hostsDnsList)
|
|
||||||
ds.permanent = true
|
ds.permanent = true
|
||||||
ds.addHostRootZone()
|
|
||||||
return ds
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -461,6 +455,13 @@ func (s *DefaultServer) Initialize() (err error) {
|
|||||||
return fmt.Errorf("initialize: %w", err)
|
return fmt.Errorf("initialize: %w", err)
|
||||||
}
|
}
|
||||||
s.hostManager = hostManager
|
s.hostManager = hostManager
|
||||||
|
// On mobile-permanent setups the seeded host DNS list is the only
|
||||||
|
// source until the first network-map arrives; register it now so DNS
|
||||||
|
// works in that window. Desktop host managers register fallback when
|
||||||
|
// applyConfiguration runs.
|
||||||
|
if s.permanent {
|
||||||
|
s.registerFallback()
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -516,10 +517,9 @@ func (s *DefaultServer) disableDNS() (retErr error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deregister original nameservers if they were registered as fallback
|
if s.fallbackHandler != nil {
|
||||||
if srvs, ok := s.hostManager.(hostManagerWithOriginalNS); ok && len(srvs.getOriginalNameservers()) > 0 {
|
log.Debugf("deregistering fallback handlers")
|
||||||
log.Debugf("deregistering original nameservers as fallback handlers")
|
s.clearFallback()
|
||||||
s.deregisterHandler([]string{nbdns.RootZone}, PriorityFallback)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.hostManager.restoreHostDNS(); err != nil {
|
if err := s.hostManager.restoreHostDNS(); err != nil {
|
||||||
@@ -533,26 +533,16 @@ func (s *DefaultServer) disableDNS() (retErr error) {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// OnUpdatedHostDNSServer update the DNS servers addresses for root zones
|
// OnUpdatedHostDNSServer updates the fallback DNS upstreams. Called by Android
|
||||||
// It will be applied if the mgm server do not enforce DNS settings for root zone
|
// outside the engine's sync mux when the OS reports a network change, so it
|
||||||
|
// takes s.mux to serialize against host manager swaps in Initialize/enableDNS.
|
||||||
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []netip.AddrPort) {
|
func (s *DefaultServer) OnUpdatedHostDNSServer(hostsDnsList []netip.AddrPort) {
|
||||||
s.hostsDNSHolder.set(hostsDnsList)
|
s.hostsDNSHolder.set(hostsDnsList)
|
||||||
|
|
||||||
var hasRootHandler bool
|
|
||||||
for _, handler := range s.dnsMuxMap {
|
|
||||||
if handler.domain == nbdns.RootZone {
|
|
||||||
hasRootHandler = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasRootHandler {
|
|
||||||
log.Debugf("on new host DNS config but skip to apply it")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("update host DNS settings: %+v", hostsDnsList)
|
log.Debugf("update host DNS settings: %+v", hostsDnsList)
|
||||||
s.addHostRootZone()
|
|
||||||
|
s.mux.Lock()
|
||||||
|
defer s.mux.Unlock()
|
||||||
|
s.registerFallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateDNSServer processes an update received from the management service
|
// UpdateDNSServer processes an update received from the management service
|
||||||
@@ -774,19 +764,17 @@ func (s *DefaultServer) applyHostConfig() {
|
|||||||
s.currentConfigHash = hash
|
s.currentConfigHash = hash
|
||||||
}
|
}
|
||||||
|
|
||||||
s.registerFallback(config)
|
s.registerFallback()
|
||||||
}
|
}
|
||||||
|
|
||||||
// registerFallback registers original nameservers as low-priority fallback handlers.
|
// registerFallback registers original nameservers as low-priority fallback handlers.
|
||||||
func (s *DefaultServer) registerFallback(config HostDNSConfig) {
|
// Replaces and Stop()s the previously-registered fallback handler so its
|
||||||
hostMgrWithNS, ok := s.hostManager.(hostManagerWithOriginalNS)
|
// context is released rather than leaked until GC.
|
||||||
if !ok {
|
func (s *DefaultServer) registerFallback() {
|
||||||
return
|
originalNameservers := s.hostManager.getOriginalNameservers()
|
||||||
}
|
|
||||||
|
|
||||||
originalNameservers := hostMgrWithNS.getOriginalNameservers()
|
|
||||||
if len(originalNameservers) == 0 {
|
if len(originalNameservers) == 0 {
|
||||||
s.deregisterHandler([]string{nbdns.RootZone}, PriorityFallback)
|
log.Debugf("no fallback upstreams to register; clearing PriorityFallback handler")
|
||||||
|
s.clearFallback()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -807,15 +795,24 @@ func (s *DefaultServer) registerFallback(config HostDNSConfig) {
|
|||||||
|
|
||||||
var servers []netip.AddrPort
|
var servers []netip.AddrPort
|
||||||
for _, ns := range originalNameservers {
|
for _, ns := range originalNameservers {
|
||||||
if ns == config.ServerIP {
|
|
||||||
log.Debugf("skipping original nameserver %s as it is the same as the server IP %s", ns, config.ServerIP)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
servers = append(servers, netip.AddrPortFrom(ns, DefaultPort))
|
servers = append(servers, netip.AddrPortFrom(ns, DefaultPort))
|
||||||
}
|
}
|
||||||
handler.addRace(servers)
|
handler.addRace(servers)
|
||||||
|
|
||||||
|
prev := s.fallbackHandler
|
||||||
|
s.fallbackHandler = handler
|
||||||
s.registerHandler([]string{nbdns.RootZone}, handler, PriorityFallback)
|
s.registerHandler([]string{nbdns.RootZone}, handler, PriorityFallback)
|
||||||
|
if prev != nil {
|
||||||
|
prev.Stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *DefaultServer) clearFallback() {
|
||||||
|
s.deregisterHandler([]string{nbdns.RootZone}, PriorityFallback)
|
||||||
|
if s.fallbackHandler != nil {
|
||||||
|
s.fallbackHandler.Stop()
|
||||||
|
s.fallbackHandler = nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]handlerWrapper, []nbdns.CustomZone, error) {
|
func (s *DefaultServer) buildLocalHandlerUpdate(customZones []nbdns.CustomZone) ([]handlerWrapper, []nbdns.CustomZone, error) {
|
||||||
@@ -976,54 +973,15 @@ func (s *DefaultServer) updateMux(muxUpdates []handlerWrapper) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
muxUpdateMap := make(registeredHandlerMap)
|
muxUpdateMap := make(registeredHandlerMap)
|
||||||
var containsRootUpdate bool
|
|
||||||
|
|
||||||
for _, update := range muxUpdates {
|
for _, update := range muxUpdates {
|
||||||
if update.domain == nbdns.RootZone {
|
|
||||||
containsRootUpdate = true
|
|
||||||
}
|
|
||||||
s.registerHandler([]string{update.domain}, update.handler, update.priority)
|
s.registerHandler([]string{update.domain}, update.handler, update.priority)
|
||||||
muxUpdateMap[update.handler.ID()] = update
|
muxUpdateMap[update.handler.ID()] = update
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no root update and we had a root handler, restore it
|
|
||||||
if !containsRootUpdate {
|
|
||||||
for _, existing := range s.dnsMuxMap {
|
|
||||||
if existing.domain == nbdns.RootZone {
|
|
||||||
s.addHostRootZone()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.dnsMuxMap = muxUpdateMap
|
s.dnsMuxMap = muxUpdateMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DefaultServer) addHostRootZone() {
|
|
||||||
hostDNSServers := s.hostsDNSHolder.get()
|
|
||||||
if len(hostDNSServers) == 0 {
|
|
||||||
log.Debug("no host DNS servers available, skipping root zone handler creation")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
handler, err := newUpstreamResolver(
|
|
||||||
s.ctx,
|
|
||||||
s.wgInterface,
|
|
||||||
s.statusRecorder,
|
|
||||||
s.hostsDNSHolder,
|
|
||||||
nbdns.RootZone,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("unable to create a new upstream resolver, error: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
handler.selectedRoutes = s.selectedRoutes
|
|
||||||
|
|
||||||
handler.addRace(maps.Keys(hostDNSServers))
|
|
||||||
|
|
||||||
s.registerHandler([]string{nbdns.RootZone}, handler, PriorityDefault)
|
|
||||||
}
|
|
||||||
|
|
||||||
// updateNSGroupStates records the new group set and pokes the refresher.
|
// updateNSGroupStates records the new group set and pokes the refresher.
|
||||||
// Must hold s.mux; projection runs async (see refreshHealth for why).
|
// Must hold s.mux; projection runs async (see refreshHealth for why).
|
||||||
func (s *DefaultServer) updateNSGroupStates(groups []*nbdns.NameServerGroup) {
|
func (s *DefaultServer) updateNSGroupStates(groups []*nbdns.NameServerGroup) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
func (s *DefaultServer) initialize() (manager hostManager, err error) {
|
func (s *DefaultServer) initialize() (manager hostManager, err error) {
|
||||||
return newHostManager()
|
return newHostManager(s.hostsDNSHolder)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -657,6 +658,7 @@ func TestDNSServerStartStop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
||||||
|
skipUnlessAndroid(t)
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
@@ -684,6 +686,7 @@ func TestDNSPermanent_updateHostDNS_emptyUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_updateUpstream(t *testing.T) {
|
func TestDNSPermanent_updateUpstream(t *testing.T) {
|
||||||
|
skipUnlessAndroid(t)
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
@@ -777,6 +780,7 @@ func TestDNSPermanent_updateUpstream(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDNSPermanent_matchOnly(t *testing.T) {
|
func TestDNSPermanent_matchOnly(t *testing.T) {
|
||||||
|
skipUnlessAndroid(t)
|
||||||
wgIFace, err := createWgInterfaceWithBind(t)
|
wgIFace, err := createWgInterfaceWithBind(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("failed to initialize wg interface")
|
t.Fatal("failed to initialize wg interface")
|
||||||
@@ -849,6 +853,18 @@ func TestDNSPermanent_matchOnly(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skipUnlessAndroid marks tests that exercise the mobile-permanent DNS path,
|
||||||
|
// which only matches a real production setup on android (NewDefaultServerPermanentUpstream
|
||||||
|
// + androidHostManager). On non-android the desktop host manager replaces it
|
||||||
|
// during Initialize and the assertion stops making sense. Skipped here until we
|
||||||
|
// have an android CI runner.
|
||||||
|
func skipUnlessAndroid(t *testing.T) {
|
||||||
|
t.Helper()
|
||||||
|
if runtime.GOOS != "android" {
|
||||||
|
t.Skip("requires android runner; mobile-permanent path doesn't match production on this OS")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
ov := os.Getenv("NB_WG_KERNEL_DISABLED")
|
ov := os.Getenv("NB_WG_KERNEL_DISABLED")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/netip"
|
"net/netip"
|
||||||
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/godbus/dbus/v5"
|
"github.com/godbus/dbus/v5"
|
||||||
@@ -42,8 +43,15 @@ const (
|
|||||||
type systemdDbusConfigurator struct {
|
type systemdDbusConfigurator struct {
|
||||||
dbusLinkObject dbus.ObjectPath
|
dbusLinkObject dbus.ObjectPath
|
||||||
ifaceName string
|
ifaceName string
|
||||||
|
wgIndex int
|
||||||
|
origNameservers []netip.Addr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
systemdDbusLinkDNSProperty = systemdDbusLinkInterface + ".DNS"
|
||||||
|
systemdDbusLinkDefaultRouteProperty = systemdDbusLinkInterface + ".DefaultRoute"
|
||||||
|
)
|
||||||
|
|
||||||
// the types below are based on dbus specification, each field is mapped to a dbus type
|
// 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://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
|
// see https://www.freedesktop.org/software/systemd/man/org.freedesktop.resolve1.html on resolve1 input types
|
||||||
@@ -79,10 +87,145 @@ func newSystemdDbusConfigurator(wgInterface string) (*systemdDbusConfigurator, e
|
|||||||
|
|
||||||
log.Debugf("got dbus Link interface: %s from net interface %s and index %d", s, iface.Name, iface.Index)
|
log.Debugf("got dbus Link interface: %s from net interface %s and index %d", s, iface.Name, iface.Index)
|
||||||
|
|
||||||
return &systemdDbusConfigurator{
|
c := &systemdDbusConfigurator{
|
||||||
dbusLinkObject: dbus.ObjectPath(s),
|
dbusLinkObject: dbus.ObjectPath(s),
|
||||||
ifaceName: wgInterface,
|
ifaceName: wgInterface,
|
||||||
}, nil
|
wgIndex: iface.Index,
|
||||||
|
}
|
||||||
|
|
||||||
|
origNameservers, err := c.captureOriginalNameservers()
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
log.Warnf("capture original nameservers from systemd-resolved: %v", err)
|
||||||
|
case len(origNameservers) == 0:
|
||||||
|
log.Warnf("no original nameservers captured from systemd-resolved default-route links; DNS fallback will be empty")
|
||||||
|
default:
|
||||||
|
log.Debugf("captured %d original nameservers from systemd-resolved default-route links: %v", len(origNameservers), origNameservers)
|
||||||
|
}
|
||||||
|
c.origNameservers = origNameservers
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// captureOriginalNameservers reads per-link DNS from systemd-resolved for
|
||||||
|
// every default-route link except our own WG link. Non-default-route links
|
||||||
|
// (VPNs, docker bridges) are skipped because their upstreams wouldn't
|
||||||
|
// actually serve host queries.
|
||||||
|
func (s *systemdDbusConfigurator) captureOriginalNameservers() ([]netip.Addr, error) {
|
||||||
|
ifaces, err := net.Interfaces()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list interfaces: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
seen := make(map[netip.Addr]struct{})
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, iface := range ifaces {
|
||||||
|
if !s.isCandidateLink(iface) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
linkPath, err := getSystemdLinkPath(iface.Index)
|
||||||
|
if err != nil || !isSystemdLinkDefaultRoute(linkPath) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, addr := range readSystemdLinkDNS(linkPath) {
|
||||||
|
addr = normalizeSystemdAddr(addr, iface.Name)
|
||||||
|
if !addr.IsValid() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, dup := seen[addr]; dup {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[addr] = struct{}{}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) isCandidateLink(iface net.Interface) bool {
|
||||||
|
if iface.Index == s.wgIndex {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if iface.Flags&net.FlagLoopback != 0 || iface.Flags&net.FlagUp == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// normalizeSystemdAddr unmaps v4-mapped-v6, drops unspecified, and reattaches
|
||||||
|
// the link's iface name as zone for link-local v6 (Link.DNS strips it).
|
||||||
|
// Returns the zero Addr to signal "skip this entry".
|
||||||
|
func normalizeSystemdAddr(addr netip.Addr, ifaceName string) netip.Addr {
|
||||||
|
addr = addr.Unmap()
|
||||||
|
if !addr.IsValid() || addr.IsUnspecified() {
|
||||||
|
return netip.Addr{}
|
||||||
|
}
|
||||||
|
if addr.IsLinkLocalUnicast() {
|
||||||
|
return addr.WithZone(ifaceName)
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSystemdLinkPath(ifIndex int) (dbus.ObjectPath, error) {
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, systemdDbusObjectNode)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("dbus resolve1: %w", err)
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
var p string
|
||||||
|
if err := obj.Call(systemdDbusGetLinkMethod, dbusDefaultFlag, int32(ifIndex)).Store(&p); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return dbus.ObjectPath(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isSystemdLinkDefaultRoute(linkPath dbus.ObjectPath) bool {
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, linkPath)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
v, err := obj.GetProperty(systemdDbusLinkDefaultRouteProperty)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b, ok := v.Value().(bool)
|
||||||
|
return ok && b
|
||||||
|
}
|
||||||
|
|
||||||
|
func readSystemdLinkDNS(linkPath dbus.ObjectPath) []netip.Addr {
|
||||||
|
obj, closeConn, err := getDbusObject(systemdResolvedDest, linkPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
defer closeConn()
|
||||||
|
v, err := obj.GetProperty(systemdDbusLinkDNSProperty)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
entries, ok := v.Value().([][]any)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var out []netip.Addr
|
||||||
|
for _, entry := range entries {
|
||||||
|
if len(entry) < 2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
raw, ok := entry[1].([]byte)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addr, ok := netip.AddrFromSlice(raw)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out = append(out, addr)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *systemdDbusConfigurator) getOriginalNameservers() []netip.Addr {
|
||||||
|
return slices.Clone(s.origNameservers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
||||||
|
|||||||
@@ -1815,7 +1815,7 @@ func (e *Engine) newDnsServer(dnsConfig *nbdns.Config) (dns.Server, error) {
|
|||||||
return dnsServer, nil
|
return dnsServer, nil
|
||||||
|
|
||||||
case "ios":
|
case "ios":
|
||||||
dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.mobileDep.HostDNSAddresses, e.statusRecorder, e.config.DisableDNS)
|
dnsServer := dns.NewDefaultServerIos(e.ctx, e.wgInterface, e.mobileDep.DnsManager, e.statusRecorder, e.config.DisableDNS)
|
||||||
return dnsServer, nil
|
return dnsServer, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -161,11 +161,7 @@ func (c *Client) Run(fd int32, interfaceName string, envList *EnvList) error {
|
|||||||
cfg.WgIface = interfaceName
|
cfg.WgIface = interfaceName
|
||||||
|
|
||||||
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
c.connectClient = internal.NewConnectClient(ctx, cfg, c.recorder)
|
||||||
hostDNS := []netip.AddrPort{
|
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, c.stateFile)
|
||||||
netip.MustParseAddrPort("9.9.9.9:53"),
|
|
||||||
netip.MustParseAddrPort("149.112.112.112:53"),
|
|
||||||
}
|
|
||||||
return c.connectClient.RunOniOS(fd, c.networkChangeListener, c.dnsManager, hostDNS, c.stateFile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop the internal client and free the resources
|
// Stop the internal client and free the resources
|
||||||
|
|||||||
Reference in New Issue
Block a user