//go:build ios package dns import ( "context" "fmt" "net" "syscall" "time" "github.com/miekg/dns" log "github.com/sirupsen/logrus" "golang.org/x/sys/unix" "github.com/netbirdio/netbird/client/internal/peer" ) type upstreamResolverIOS struct { *upstreamResolverBase lIP net.IP lNet *net.IPNet interfaceName string } func newUpstreamResolver( ctx context.Context, interfaceName string, ip net.IP, net *net.IPNet, statusRecorder *peer.Status, _ *hostsDNSHolder, ) (*upstreamResolverIOS, error) { upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder) ios := &upstreamResolverIOS{ upstreamResolverBase: upstreamResolverBase, lIP: ip, lNet: net, interfaceName: interfaceName, } ios.upstreamClient = ios return ios, nil } func (u *upstreamResolverIOS) exchange(ctx context.Context, upstream string, r *dns.Msg) (rm *dns.Msg, t time.Duration, err error) { client := &dns.Client{} upstreamHost, _, err := net.SplitHostPort(upstream) if err != nil { return nil, 0, fmt.Errorf("error while parsing upstream host: %s", err) } timeout := upstreamTimeout if deadline, ok := ctx.Deadline(); ok { timeout = time.Until(deadline) } client.DialTimeout = timeout upstreamIP := net.ParseIP(upstreamHost) if u.lNet.Contains(upstreamIP) || net.IP.IsPrivate(upstreamIP) { log.Debugf("using private client to query upstream: %s", upstream) client, err = GetClientPrivate(u.lIP, u.interfaceName, timeout) if err != nil { return nil, 0, fmt.Errorf("error while creating private client: %s", err) } } // Cannot use client.ExchangeContext because it overwrites our Dialer return client.Exchange(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 net.IP, interfaceName string, dialTimeout time.Duration) (*dns.Client, error) { index, err := getInterfaceIndex(interfaceName) if err != nil { log.Debugf("unable to get interface index for %s: %s", interfaceName, err) return nil, err } dialer := &net.Dialer{ LocalAddr: &net.UDPAddr{ IP: ip, Port: 0, // Let the OS pick a free port }, 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) } if err := c.Control(fn); err != nil { return err } if operr != nil { log.Errorf("error while setting socket option: %s", operr) } return operr }, } client := &dns.Client{ Dialer: dialer, } return client, nil } func getInterfaceIndex(interfaceName string) (int, error) { iface, err := net.InterfaceByName(interfaceName) return iface.Index, err }