mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-08 17:59:56 +00:00
[management, client] Add IPv6 overlay support (#5631)
This commit is contained in:
@@ -298,6 +298,7 @@ func (s *systemConfigurator) getSystemDNSSettings() (SystemDNSSettings, error) {
|
||||
if ip, err := netip.ParseAddr(address); err == nil && !ip.IsUnspecified() {
|
||||
ip = ip.Unmap()
|
||||
serverAddresses = append(serverAddresses, ip)
|
||||
// Prefer the first IPv4 server as ServerIP since our DNS listener is IPv4.
|
||||
if !dnsSettings.ServerIP.IsValid() && ip.Is4() {
|
||||
dnsSettings.ServerIP = ip
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
|
||||
"github.com/miekg/dns"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/dns/resutil"
|
||||
"github.com/netbirdio/netbird/client/internal/dns/types"
|
||||
@@ -67,9 +66,9 @@ func (d *Resolver) Stop() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
maps.Clear(d.records)
|
||||
maps.Clear(d.domains)
|
||||
maps.Clear(d.zones)
|
||||
clear(d.records)
|
||||
clear(d.domains)
|
||||
clear(d.zones)
|
||||
}
|
||||
|
||||
// ID returns the unique handler ID
|
||||
@@ -444,9 +443,9 @@ func (d *Resolver) Update(customZones []nbdns.CustomZone) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
maps.Clear(d.records)
|
||||
maps.Clear(d.domains)
|
||||
maps.Clear(d.zones)
|
||||
clear(d.records)
|
||||
clear(d.domains)
|
||||
clear(d.zones)
|
||||
|
||||
for _, zone := range customZones {
|
||||
zoneDomain := domain.Domain(strings.ToLower(dns.Fqdn(zone.Domain)))
|
||||
|
||||
@@ -110,8 +110,25 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig, st
|
||||
|
||||
connSettings.cleanDeprecatedSettings()
|
||||
|
||||
convDNSIP := binary.LittleEndian.Uint32(config.ServerIP.AsSlice())
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
||||
ipKey := networkManagerDbusIPv4Key
|
||||
staleKey := networkManagerDbusIPv6Key
|
||||
if config.ServerIP.Is6() {
|
||||
ipKey = networkManagerDbusIPv6Key
|
||||
staleKey = networkManagerDbusIPv4Key
|
||||
raw := config.ServerIP.As16()
|
||||
connSettings[ipKey][networkManagerDbusDNSKey] = dbus.MakeVariant([][]byte{raw[:]})
|
||||
} else {
|
||||
convDNSIP := binary.LittleEndian.Uint32(config.ServerIP.AsSlice())
|
||||
connSettings[ipKey][networkManagerDbusDNSKey] = dbus.MakeVariant([]uint32{convDNSIP})
|
||||
}
|
||||
|
||||
// Clear stale DNS settings from the opposite address family to avoid
|
||||
// leftover entries if the server IP family changed.
|
||||
if staleSettings, ok := connSettings[staleKey]; ok {
|
||||
delete(staleSettings, networkManagerDbusDNSKey)
|
||||
delete(staleSettings, networkManagerDbusDNSPriorityKey)
|
||||
delete(staleSettings, networkManagerDbusDNSSearchKey)
|
||||
}
|
||||
var (
|
||||
searchDomains []string
|
||||
matchDomains []string
|
||||
@@ -146,8 +163,8 @@ func (n *networkManagerDbusConfigurator) applyDNSConfig(config HostDNSConfig, st
|
||||
n.routingAll = false
|
||||
}
|
||||
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(priority)
|
||||
connSettings[networkManagerDbusIPv4Key][networkManagerDbusDNSSearchKey] = dbus.MakeVariant(newDomainList)
|
||||
connSettings[ipKey][networkManagerDbusDNSPriorityKey] = dbus.MakeVariant(priority)
|
||||
connSettings[ipKey][networkManagerDbusDNSSearchKey] = dbus.MakeVariant(newDomainList)
|
||||
|
||||
state := &ShutdownState{
|
||||
ManagerType: networkManager,
|
||||
|
||||
@@ -410,7 +410,7 @@ func (s *DefaultServer) Stop() {
|
||||
log.Errorf("failed to disable DNS: %v", err)
|
||||
}
|
||||
|
||||
maps.Clear(s.extraDomains)
|
||||
clear(s.extraDomains)
|
||||
}
|
||||
|
||||
func (s *DefaultServer) disableDNS() (retErr error) {
|
||||
|
||||
@@ -347,7 +347,7 @@ func TestUpdateDNSServer(t *testing.T) {
|
||||
|
||||
opts := iface.WGIFaceOpts{
|
||||
IFaceName: fmt.Sprintf("utun230%d", n),
|
||||
Address: fmt.Sprintf("100.66.100.%d/32", n+1),
|
||||
Address: wgaddr.MustParseWGAddress(fmt.Sprintf("100.66.100.%d/32", n+1)),
|
||||
WGPort: 33100,
|
||||
WGPrivKey: privKey.String(),
|
||||
MTU: iface.DefaultMTU,
|
||||
@@ -448,7 +448,7 @@ func TestDNSFakeResolverHandleUpdates(t *testing.T) {
|
||||
privKey, _ := wgtypes.GeneratePrivateKey()
|
||||
opts := iface.WGIFaceOpts{
|
||||
IFaceName: "utun2301",
|
||||
Address: "100.66.100.1/32",
|
||||
Address: wgaddr.MustParseWGAddress("100.66.100.1/32"),
|
||||
WGPort: 33100,
|
||||
WGPrivKey: privKey.String(),
|
||||
MTU: iface.DefaultMTU,
|
||||
@@ -929,7 +929,7 @@ func createWgInterfaceWithBind(t *testing.T) (*iface.WGIface, error) {
|
||||
|
||||
opts := iface.WGIFaceOpts{
|
||||
IFaceName: "utun2301",
|
||||
Address: "100.66.100.2/24",
|
||||
Address: wgaddr.MustParseWGAddress("100.66.100.2/24"),
|
||||
WGPort: 33100,
|
||||
WGPrivKey: privKey.String(),
|
||||
MTU: iface.DefaultMTU,
|
||||
|
||||
@@ -16,8 +16,8 @@ const (
|
||||
// This is used when the DNS server cannot bind port 53 directly
|
||||
// and needs firewall rules to redirect traffic.
|
||||
type Firewall interface {
|
||||
AddOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error
|
||||
RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, sourcePort, targetPort uint16) error
|
||||
AddOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error
|
||||
RemoveOutputDNAT(localAddr netip.Addr, protocol firewall.Protocol, originalPort, translatedPort uint16) error
|
||||
}
|
||||
|
||||
type service interface {
|
||||
|
||||
@@ -188,11 +188,10 @@ func (s *serviceViaListener) RuntimeIP() netip.Addr {
|
||||
return s.listenIP
|
||||
}
|
||||
|
||||
|
||||
// evalListenAddress figure out the listen address for the DNS server
|
||||
// first check the 53 port availability on WG interface or lo, if not success
|
||||
// pick a random port on WG interface for eBPF, if not success
|
||||
// check the 5053 port availability on WG interface or lo without eBPF usage,
|
||||
// evalListenAddress figures out the listen address for the DNS server.
|
||||
// IPv4-only: all peers have a v4 overlay address, and DNS config points to v4.
|
||||
// First checks port 53 on WG interface or lo, then tries eBPF on a random port,
|
||||
// then falls back to port 5053.
|
||||
func (s *serviceViaListener) evalListenAddress() (netip.Addr, uint16, error) {
|
||||
if s.customAddr != nil {
|
||||
return s.customAddr.Addr(), s.customAddr.Port(), nil
|
||||
@@ -278,7 +277,7 @@ func (s *serviceViaListener) tryToUseeBPF() (ebpfMgr.Manager, uint16, bool) {
|
||||
}
|
||||
|
||||
ebpfSrv := ebpf.GetEbpfManagerInstance()
|
||||
err = ebpfSrv.LoadDNSFwd(s.wgInterface.Address().IP.String(), int(port))
|
||||
err = ebpfSrv.LoadDNSFwd(s.wgInterface.Address().IP, int(port))
|
||||
if err != nil {
|
||||
log.Warnf("failed to load DNS forwarder eBPF program, error: %s", err)
|
||||
return nil, 0, false
|
||||
|
||||
@@ -90,8 +90,12 @@ func (s *systemdDbusConfigurator) supportCustomPort() bool {
|
||||
}
|
||||
|
||||
func (s *systemdDbusConfigurator) applyDNSConfig(config HostDNSConfig, stateManager *statemanager.Manager) error {
|
||||
family := int32(unix.AF_INET)
|
||||
if config.ServerIP.Is6() {
|
||||
family = unix.AF_INET6
|
||||
}
|
||||
defaultLinkInput := systemdDbusDNSInput{
|
||||
Family: unix.AF_INET,
|
||||
Family: family,
|
||||
Address: config.ServerIP.AsSlice(),
|
||||
}
|
||||
if err := s.callLinkMethod(systemdDbusSetDNSMethodSuffix, []systemdDbusDNSInput{defaultLinkInput}); err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"golang.zx2c4.com/wireguard/tun/netstack"
|
||||
|
||||
"github.com/netbirdio/netbird/client/iface"
|
||||
"github.com/netbirdio/netbird/client/iface/wgaddr"
|
||||
"github.com/netbirdio/netbird/client/internal/dns/resutil"
|
||||
"github.com/netbirdio/netbird/client/internal/dns/types"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
@@ -29,6 +30,12 @@ import (
|
||||
|
||||
var currentMTU uint16 = iface.DefaultMTU
|
||||
|
||||
// privateClientIface is the subset of the WireGuard interface needed by GetClientPrivate.
|
||||
type privateClientIface interface {
|
||||
Name() string
|
||||
Address() wgaddr.Address
|
||||
}
|
||||
|
||||
func SetCurrentMTU(mtu uint16) {
|
||||
currentMTU = mtu
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func (u *upstreamResolver) isLocalResolver(upstream string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func GetClientPrivate(ip netip.Addr, interfaceName string, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
func GetClientPrivate(_ privateClientIface, _ netip.Addr, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
return &dns.Client{
|
||||
Timeout: dialTimeout,
|
||||
Net: "udp",
|
||||
|
||||
@@ -52,7 +52,7 @@ func (u *upstreamResolver) exchange(ctx context.Context, upstream string, r *dns
|
||||
return ExchangeWithFallback(ctx, client, r, upstream)
|
||||
}
|
||||
|
||||
func GetClientPrivate(ip netip.Addr, interfaceName string, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
func GetClientPrivate(_ privateClientIface, _ netip.Addr, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
return &dns.Client{
|
||||
Timeout: dialTimeout,
|
||||
Net: "udp",
|
||||
|
||||
@@ -19,9 +19,7 @@ import (
|
||||
|
||||
type upstreamResolverIOS struct {
|
||||
*upstreamResolverBase
|
||||
lIP netip.Addr
|
||||
lNet netip.Prefix
|
||||
interfaceName string
|
||||
wgIface WGIface
|
||||
}
|
||||
|
||||
func newUpstreamResolver(
|
||||
@@ -35,9 +33,7 @@ func newUpstreamResolver(
|
||||
|
||||
ios := &upstreamResolverIOS{
|
||||
upstreamResolverBase: upstreamResolverBase,
|
||||
lIP: wgIface.Address().IP,
|
||||
lNet: wgIface.Address().Network,
|
||||
interfaceName: wgIface.Name(),
|
||||
wgIface: wgIface,
|
||||
}
|
||||
ios.upstreamClient = ios
|
||||
|
||||
@@ -65,11 +61,13 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
|
||||
} else {
|
||||
upstreamIP = upstreamIP.Unmap()
|
||||
}
|
||||
needsPrivate := u.lNet.Contains(upstreamIP) ||
|
||||
addr := u.wgIface.Address()
|
||||
needsPrivate := addr.Network.Contains(upstreamIP) ||
|
||||
addr.IPv6Net.Contains(upstreamIP) ||
|
||||
(u.routeMatch != nil && u.routeMatch(upstreamIP))
|
||||
if needsPrivate {
|
||||
log.Debugf("using private client to query %s via upstream %s", r.Question[0].Name, upstream)
|
||||
client, err = GetClientPrivate(u.lIP, u.interfaceName, timeout)
|
||||
client, err = GetClientPrivate(u.wgIface, upstreamIP, timeout)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("create private client: %s", err)
|
||||
}
|
||||
@@ -79,25 +77,33 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
|
||||
return ExchangeWithFallback(nil, client, r, upstream)
|
||||
}
|
||||
|
||||
// GetClientPrivate returns a new DNS client bound to the local IP address of the Netbird interface
|
||||
// This method is needed for iOS
|
||||
func GetClientPrivate(ip netip.Addr, interfaceName string, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
index, err := getInterfaceIndex(interfaceName)
|
||||
// GetClientPrivate returns a new DNS client bound to the local IP of the Netbird interface.
|
||||
// It selects the v6 bind address when the upstream is IPv6 and the interface has one, otherwise v4.
|
||||
func GetClientPrivate(iface privateClientIface, upstreamIP netip.Addr, dialTimeout time.Duration) (*dns.Client, error) {
|
||||
index, err := getInterfaceIndex(iface.Name())
|
||||
if err != nil {
|
||||
log.Debugf("unable to get interface index for %s: %s", interfaceName, err)
|
||||
log.Debugf("unable to get interface index for %s: %s", iface.Name(), err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addr := iface.Address()
|
||||
bindIP := addr.IP
|
||||
if upstreamIP.Is6() && addr.HasIPv6() {
|
||||
bindIP = addr.IPv6
|
||||
}
|
||||
|
||||
proto, opt := unix.IPPROTO_IP, unix.IP_BOUND_IF
|
||||
if bindIP.Is6() {
|
||||
proto, opt = unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
LocalAddr: &net.UDPAddr{
|
||||
IP: ip.AsSlice(),
|
||||
Port: 0, // Let the OS pick a free port
|
||||
},
|
||||
Timeout: dialTimeout,
|
||||
LocalAddr: net.UDPAddrFromAddrPort(netip.AddrPortFrom(bindIP, 0)),
|
||||
Timeout: dialTimeout,
|
||||
Control: func(network, address string, c syscall.RawConn) error {
|
||||
var operr error
|
||||
fn := func(s uintptr) {
|
||||
operr = unix.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, index)
|
||||
operr = unix.SetsockoptInt(int(s), proto, opt, index)
|
||||
}
|
||||
|
||||
if err := c.Control(fn); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user