[client] Add IPv6 support to ACL manager, USP filter, and forwarder (#5688)

This commit is contained in:
Viktor Liu
2026-04-09 10:56:08 +02:00
committed by GitHub
parent a1e7db2713
commit 1c4e5e71d7
78 changed files with 3606 additions and 1071 deletions
+5 -5
View File
@@ -189,10 +189,10 @@ func (s *serviceViaListener) RuntimeIP() netip.Addr {
}
// 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 +278,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
+7
View File
@@ -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
}
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+23 -34
View File
@@ -19,11 +19,7 @@ import (
type upstreamResolverIOS struct {
*upstreamResolverBase
lIP netip.Addr
lNet netip.Prefix
lIPv6 netip.Addr
lNetV6 netip.Prefix
interfaceName string
wgIface WGIface
}
func newUpstreamResolver(
@@ -37,11 +33,7 @@ func newUpstreamResolver(
ios := &upstreamResolverIOS{
upstreamResolverBase: upstreamResolverBase,
lIP: wgIface.Address().IP,
lNet: wgIface.Address().Network,
lIPv6: wgIface.Address().IPv6,
lNetV6: wgIface.Address().IPv6Net,
interfaceName: wgIface.Name(),
wgIface: wgIface,
}
ios.upstreamClient = ios
@@ -69,24 +61,15 @@ func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *
} else {
upstreamIP = upstreamIP.Unmap()
}
needsPrivate := u.lNet.Contains(upstreamIP) ||
u.lNetV6.Contains(upstreamIP) ||
addr := u.wgIface.Address()
needsPrivate := addr.Network.Contains(upstreamIP) ||
addr.IPv6Net.Contains(upstreamIP) ||
(u.routeMatch != nil && u.routeMatch(upstreamIP))
if needsPrivate {
var bindIP netip.Addr
switch {
case upstreamIP.Is6() && u.lIPv6.IsValid():
bindIP = u.lIPv6
case upstreamIP.Is4() && u.lIP.IsValid():
bindIP = u.lIP
}
if bindIP.IsValid() {
log.Debugf("using private client to query %s via upstream %s", r.Question[0].Name, upstream)
client, err = GetClientPrivate(bindIP, u.interfaceName, timeout)
if err != nil {
return nil, 0, fmt.Errorf("create private client: %s", err)
}
log.Debugf("using private client to query %s via upstream %s", r.Question[0].Name, upstream)
client, err = GetClientPrivate(u.wgIface, upstreamIP, timeout)
if err != nil {
return nil, 0, fmt.Errorf("create private client: %s", err)
}
}
@@ -94,23 +77,29 @@ 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 ip.Is6() {
if bindIP.Is6() {
proto, opt = unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
}
dialer := &net.Dialer{
LocalAddr: net.UDPAddrFromAddrPort(netip.AddrPortFrom(ip, 0)),
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) {