mirror of
https://github.com/netbirdio/netbird.git
synced 2024-12-15 19:31:06 +01:00
115 lines
2.8 KiB
Go
115 lines
2.8 KiB
Go
//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
|
|
}
|