Skip to content
Merged
5 changes: 3 additions & 2 deletions client/anonymize/anonymize.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ type Anonymizer struct {
}

func DefaultAddresses() (netip.Addr, netip.Addr) {
// 198.51.100.0, 100::
return netip.AddrFrom4([4]byte{198, 51, 100, 0}), netip.AddrFrom16([16]byte{0x01})
// 198.51.100.0 (RFC 5737 TEST-NET-2), 2001:db8:ffff:: (RFC 3849 documentation, last /48)
// The old start 100:: (discard, RFC 6666) is now used for fake IPs on Android.
return netip.AddrFrom4([4]byte{198, 51, 100, 0}), netip.MustParseAddr("2001:db8:ffff::")
}

func NewAnonymizer(startIPv4, startIPv6 netip.Addr) *Anonymizer {
Expand Down
14 changes: 7 additions & 7 deletions client/anonymize/anonymize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func TestAnonymizeIP(t *testing.T) {
startIPv4 := netip.MustParseAddr("198.51.100.0")
startIPv6 := netip.MustParseAddr("100::")
startIPv6 := netip.MustParseAddr("2001:db8:ffff::")
anonymizer := anonymize.NewAnonymizer(startIPv4, startIPv6)

tests := []struct {
Expand All @@ -26,9 +26,9 @@ func TestAnonymizeIP(t *testing.T) {
{"Second Public IPv4", "4.3.2.1", "198.51.100.1"},
{"Repeated IPv4", "1.2.3.4", "198.51.100.0"},
{"Private IPv4", "192.168.1.1", "192.168.1.1"},
{"First Public IPv6", "2607:f8b0:4005:805::200e", "100::"},
{"Second Public IPv6", "a::b", "100::1"},
{"Repeated IPv6", "2607:f8b0:4005:805::200e", "100::"},
{"First Public IPv6", "2607:f8b0:4005:805::200e", "2001:db8:ffff::"},
{"Second Public IPv6", "a::b", "2001:db8:ffff::1"},
{"Repeated IPv6", "2607:f8b0:4005:805::200e", "2001:db8:ffff::"},
{"Private IPv6", "fe80::1", "fe80::1"},
{"In Range IPv4", "198.51.100.2", "198.51.100.2"},
}
Expand Down Expand Up @@ -274,17 +274,17 @@ func TestAnonymizeString_IPAddresses(t *testing.T) {
{
name: "IPv6 Address",
input: "Access attempted from 2001:db8::ff00:42",
expect: "Access attempted from 100::",
expect: "Access attempted from 2001:db8:ffff::",
},
{
name: "IPv6 Address with Port",
input: "Access attempted from [2001:db8::ff00:42]:8080",
expect: "Access attempted from [100::]:8080",
expect: "Access attempted from [2001:db8:ffff::]:8080",
},
{
name: "Both IPv4 and IPv6",
input: "IPv4: 142.108.0.1 and IPv6: 2001:db8::ff00:43",
expect: "IPv4: 198.51.100.1 and IPv6: 100::1",
expect: "IPv4: 198.51.100.1 and IPv6: 2001:db8:ffff::1",
},
}

Expand Down
7 changes: 5 additions & 2 deletions client/firewall/uspfilter/forwarder/endpoint.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package forwarder

import (
"fmt"
"net"
"strconv"
"sync/atomic"

wgdevice "golang.zx2c4.com/wireguard/device"
Expand Down Expand Up @@ -109,5 +110,7 @@ type epID stack.TransportEndpointID

func (i epID) String() string {
// src and remote is swapped
return fmt.Sprintf("%s:%d → %s:%d", i.RemoteAddress, i.RemotePort, i.LocalAddress, i.LocalPort)
return net.JoinHostPort(i.RemoteAddress.String(), strconv.Itoa(int(i.RemotePort))) +
" → " +
net.JoinHostPort(i.LocalAddress.String(), strconv.Itoa(int(i.LocalPort)))
}
8 changes: 4 additions & 4 deletions client/firewall/uspfilter/forwarder/forwarder.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,14 +303,14 @@ func (f *Forwarder) Stop() {
f.stack.Wait()
}

func (f *Forwarder) determineDialAddr(addr tcpip.Address) net.IP {
func (f *Forwarder) determineDialAddr(addr tcpip.Address) netip.Addr {
if f.netstack && f.ip.Equal(addr) {
return net.IPv4(127, 0, 0, 1)
return netip.AddrFrom4([4]byte{127, 0, 0, 1})
}
if f.netstack && f.ipv6.Equal(addr) {
return net.IPv6loopback
return netip.IPv6Loopback()
}
return addr.AsSlice()
return addrToNetipAddr(addr)
}

func (f *Forwarder) RegisterRuleID(srcIP, dstIP netip.Addr, srcPort, dstPort uint16, ruleID []byte) {
Expand Down
7 changes: 4 additions & 3 deletions client/firewall/uspfilter/forwarder/icmp.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net"
"net/netip"
"os/exec"
"runtime"
"time"
Expand Down Expand Up @@ -87,7 +88,7 @@ func (f *Forwarder) forwardICMPPacket(id stack.TransportEndpointID, payload []by
}

dstIP := f.determineDialAddr(id.LocalAddress)
dst := &net.IPAddr{IP: dstIP}
dst := &net.IPAddr{IP: dstIP.AsSlice()}

if _, err = conn.WriteTo(payload, dst); err != nil {
if closeErr := conn.Close(); closeErr != nil {
Expand Down Expand Up @@ -340,13 +341,13 @@ const (

// buildPingCommand creates a platform-specific ping command.
// Most platforms auto-detect IPv6 from raw addresses. macOS/iOS/OpenBSD require ping6.
func buildPingCommand(ctx context.Context, target net.IP, timeout time.Duration) *exec.Cmd {
func buildPingCommand(ctx context.Context, target netip.Addr, timeout time.Duration) *exec.Cmd {
timeoutSec := int(timeout.Seconds())
if timeoutSec < 1 {
timeoutSec = 1
}

isV6 := target.To4() == nil
isV6 := target.Is6()
timeoutStr := fmt.Sprintf("%d", timeoutSec)

switch runtime.GOOS {
Expand Down
4 changes: 2 additions & 2 deletions client/firewall/uspfilter/forwarder/tcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ package forwarder

import (
"context"
"fmt"
"io"
"net"
"strconv"
"sync"

"github.com/google/uuid"
Expand Down Expand Up @@ -32,7 +32,7 @@ func (f *Forwarder) handleTCP(r *tcp.ForwarderRequest) {
}
}()

dialAddr := fmt.Sprintf("%s:%d", f.determineDialAddr(id.LocalAddress), id.LocalPort)
dialAddr := net.JoinHostPort(f.determineDialAddr(id.LocalAddress).String(), strconv.Itoa(int(id.LocalPort)))

outConn, err := (&net.Dialer{}).DialContext(f.ctx, "tcp", dialAddr)
if err != nil {
Expand Down
3 changes: 2 additions & 1 deletion client/firewall/uspfilter/forwarder/udp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io"
"net"
"strconv"
"sync"
"sync/atomic"
"time"
Expand Down Expand Up @@ -157,7 +158,7 @@ func (f *Forwarder) handleUDP(r *udp.ForwarderRequest) bool {
}
}()

dstAddr := fmt.Sprintf("%s:%d", f.determineDialAddr(id.LocalAddress), id.LocalPort)
dstAddr := net.JoinHostPort(f.determineDialAddr(id.LocalAddress).String(), strconv.Itoa(int(id.LocalPort)))
outConn, err := (&net.Dialer{}).DialContext(f.ctx, "udp", dstAddr)
if err != nil {
f.logger.Debug2("forwarder: UDP dial error for %v: %v", epID(id), err)
Expand Down
7 changes: 7 additions & 0 deletions client/internal/dns/upstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -29,6 +30,12 @@ import (

var currentMTU uint16 = iface.DefaultMTU

// privateClientIface is the subset of the WireGuard interface needed by GetClientPrivate.
type privateClientIface interface {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It required by ios only. Move into the _ios.go

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't, the signature of the stubs for other platforms also require it.

Name() string
Address() wgaddr.Address
}

func SetCurrentMTU(mtu uint16) {
currentMTU = mtu
}
Expand Down
2 changes: 1 addition & 1 deletion client/internal/dns/upstream_android.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion client/internal/dns/upstream_general.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
57 changes: 23 additions & 34 deletions client/internal/dns/upstream_ios.go
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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

Expand Down Expand Up @@ -69,48 +61,45 @@ 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)
}
}

// Cannot use client.ExchangeContext because it overwrites our Dialer
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) {
Expand Down
5 changes: 2 additions & 3 deletions client/internal/routemanager/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@ package client
import (
"context"
"fmt"
"net"
"net/netip"
"reflect"
"strconv"
"time"

log "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -566,7 +565,7 @@ func HandlerFromRoute(params common.HandlerParams) RouteHandler {
return dnsinterceptor.New(params)
case handlerTypeDynamic:
dns := nbdns.NewServiceViaMemory(params.WgInterface)
dnsAddr := net.JoinHostPort(dns.RuntimeIP().String(), strconv.Itoa(dns.RuntimePort()))
dnsAddr := netip.AddrPortFrom(dns.RuntimeIP(), uint16(dns.RuntimePort()))
return dynamic.NewRoute(params, dnsAddr)
default:
return static.NewRoute(params)
Expand Down
2 changes: 1 addition & 1 deletion client/internal/routemanager/dnsinterceptor/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ func (d *DnsInterceptor) queryUpstreamDNS(ctx context.Context, w dns.ResponseWri
if nsNet != nil {
reply, err = nbdns.ExchangeWithNetstack(ctx, nsNet, r, upstream)
} else {
client, clientErr := nbdns.GetClientPrivate(d.wgInterface.Address().IP, d.wgInterface.Name(), dnsTimeout)
client, clientErr := nbdns.GetClientPrivate(d.wgInterface, upstreamIP, dnsTimeout)
if clientErr != nil {
d.writeDNSError(w, r, logger, fmt.Sprintf("create DNS client: %v", clientErr))
return nil
Expand Down
4 changes: 2 additions & 2 deletions client/internal/routemanager/dynamic/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ type Route struct {
cancel context.CancelFunc
statusRecorder *peer.Status
wgInterface iface.WGIface
resolverAddr string
resolverAddr netip.AddrPort
}

func NewRoute(params common.HandlerParams, resolverAddr string) *Route {
func NewRoute(params common.HandlerParams, resolverAddr netip.AddrPort) *Route {
return &Route{
route: params.Route,
routeRefCounter: params.RouteRefCounter,
Expand Down
Loading
Loading