From 64084ca130751d2ec9f271f711aa5b2f44d667d7 Mon Sep 17 00:00:00 2001 From: Pascal Fischer Date: Fri, 3 Nov 2023 14:26:07 +0100 Subject: [PATCH] trying to bind the DNS resolver dialer to an interface --- client/internal/connect.go | 3 +- client/internal/dns/server.go | 19 +++--- client/internal/dns/upstream.go | 94 ++++++++++++++++++++++++++-- client/internal/engine.go | 2 +- client/internal/mobile_dependency.go | 1 + client/ios/NetBirdSDK/client.go | 5 +- 6 files changed, 109 insertions(+), 15 deletions(-) diff --git a/client/internal/connect.go b/client/internal/connect.go index c5fdf4274..aa3ac629b 100644 --- a/client/internal/connect.go +++ b/client/internal/connect.go @@ -42,9 +42,10 @@ func RunClientMobile(ctx context.Context, config *Config, statusRecorder *peer.S return runClient(ctx, config, statusRecorder, mobileDependency) } -func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, fileDescriptor int32, routeListener routemanager.RouteListener, dnsManager dns.IosDnsManager) error { +func RunClientiOS(ctx context.Context, config *Config, statusRecorder *peer.Status, fileDescriptor int32, routeListener routemanager.RouteListener, dnsManager dns.IosDnsManager, interfaceName string) error { mobileDependency := MobileDependency{ FileDescriptor: fileDescriptor, + InterfaceName: interfaceName, RouteListener: routeListener, DnsManager: dnsManager, } diff --git a/client/internal/dns/server.go b/client/internal/dns/server.go index 03ba6f717..fb763bec2 100644 --- a/client/internal/dns/server.go +++ b/client/internal/dns/server.go @@ -53,6 +53,9 @@ type DefaultServer struct { permanent bool hostsDnsList []string hostsDnsListLock sync.Mutex + + interfaceName string + wgAddr string } type handlerWithStop interface { @@ -66,7 +69,7 @@ type muxUpdate struct { } // NewDefaultServer returns a new dns server -func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string) (*DefaultServer, error) { +func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress string, interfaceName string, wgAddr string) (*DefaultServer, error) { var addrPort *netip.AddrPort if customAddress != "" { parsedAddrPort, err := netip.ParseAddrPort(customAddress) @@ -83,13 +86,13 @@ func NewDefaultServer(ctx context.Context, wgInterface WGIface, customAddress st dnsService = newServiceViaListener(wgInterface, addrPort) } - return newDefaultServer(ctx, wgInterface, dnsService), nil + return newDefaultServer(ctx, wgInterface, dnsService, interfaceName, wgAddr), nil } // NewDefaultServerPermanentUpstream returns a new dns server. It optimized for mobile systems func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, hostsDnsList []string) *DefaultServer { log.Debugf("host dns address list is: %v", hostsDnsList) - ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface)) + ds := newDefaultServer(ctx, wgInterface, newServiceViaMemory(wgInterface), "", "") ds.permanent = true ds.hostsDnsList = hostsDnsList ds.addHostRootZone() @@ -97,7 +100,7 @@ func NewDefaultServerPermanentUpstream(ctx context.Context, wgInterface WGIface, return ds } -func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service) *DefaultServer { +func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService service, interfaceName string, wgAddr string) *DefaultServer { ctx, stop := context.WithCancel(ctx) defaultServer := &DefaultServer{ ctx: ctx, @@ -107,7 +110,9 @@ func newDefaultServer(ctx context.Context, wgInterface WGIface, dnsService servi localResolver: &localResolver{ registeredMap: make(registrationMap), }, - wgInterface: wgInterface, + wgInterface: wgInterface, + interfaceName: interfaceName, + wgAddr: wgAddr, } return defaultServer @@ -295,7 +300,7 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam continue } - handler := newUpstreamResolver(s.ctx) + handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr) for _, ns := range nsGroup.NameServers { if ns.NSType != nbdns.UDPNameServerType { log.Warnf("skiping nameserver %s with type %s, this peer supports only %s", @@ -468,7 +473,7 @@ func (s *DefaultServer) upstreamCallbacks( } func (s *DefaultServer) addHostRootZone() { - handler := newUpstreamResolver(s.ctx) + handler := newUpstreamResolver(s.ctx, s.interfaceName, s.wgAddr) handler.upstreamServers = make([]string, len(s.hostsDnsList)) for n, ua := range s.hostsDnsList { handler.upstreamServers[n] = fmt.Sprintf("%s:53", ua) diff --git a/client/internal/dns/upstream.go b/client/internal/dns/upstream.go index b4febd7a4..69d1a58d6 100644 --- a/client/internal/dns/upstream.go +++ b/client/internal/dns/upstream.go @@ -4,14 +4,19 @@ import ( "context" "errors" "fmt" + "math/rand" "net" + "net/netip" "sync" "sync/atomic" + "syscall" "time" "github.com/cenkalti/backoff/v4" + "github.com/libp2p/go-netroute" "github.com/miekg/dns" log "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) const ( @@ -40,12 +45,80 @@ type upstreamResolver struct { reactivate func() } -func newUpstreamResolver(parentCTX context.Context) *upstreamResolver { +// func newUpstreamResolver(parentCTX context.Context) *upstreamResolver { +// ctx, cancel := context.WithCancel(parentCTX) +// return &upstreamResolver{ +// ctx: ctx, +// cancel: cancel, +// upstreamClient: &dns.Client{}, +// upstreamTimeout: upstreamTimeout, +// reactivatePeriod: reactivatePeriod, +// failsTillDeact: failsTillDeact, +// } +// } + +func getInterfaceIndex(interfaceName string) (int, error) { + iface, err := net.InterfaceByName(interfaceName) + if err != nil { + return 0, err + } + + return iface.Index, nil +} + +func newUpstreamResolver(parentCTX context.Context, interfaceName string, wgAddr string) *upstreamResolver { ctx, cancel := context.WithCancel(parentCTX) + + // Specify the local IP address you want to bind to + localIP, _, err := net.ParseCIDR(wgAddr) // Should be our interface IP + if err != nil { + log.Errorf("error while parsing CIDR: %s", err) + } + index, err := getInterfaceIndex(interfaceName) + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(4001) + 1000 + log.Debugf("UpstreamResolver interface name: %s, index: %d, ip: %s, port: %d", interfaceName, index, localIP, port) + if err != nil { + log.Debugf("unable to get interface index for %s: %s", interfaceName, err) + } + localIFaceIndex := index // Should be our interface index + // Create a custom dialer with the LocalAddr set to the desired IP + dialer := &net.Dialer{ + LocalAddr: &net.UDPAddr{ + IP: localIP, + Port: port, // Let the OS pick a free port + }, + Control: func(network, address string, c syscall.RawConn) error { + var operr error + fn := func(s uintptr) { + operr = syscall.SetsockoptInt(int(s), unix.IPPROTO_IP, unix.IP_BOUND_IF, localIFaceIndex) + } + + if err := c.Control(fn); err != nil { + return err + } + + return operr + }, + } + // pktConn, err := dialer.Dial("udp", "100.127.136.151:10053") + // if err != nil { + // log.Errorf("error while dialing: %s", err) + // + // } else { + // pktConn.Write([]byte("hello")) + // pktConn.Close() + // } + + // Create a new DNS client with the custom dialer + client := &dns.Client{ + Dialer: dialer, + } + return &upstreamResolver{ ctx: ctx, cancel: cancel, - upstreamClient: &dns.Client{}, + upstreamClient: client, upstreamTimeout: upstreamTimeout, reactivatePeriod: reactivatePeriod, failsTillDeact: failsTillDeact, @@ -61,7 +134,7 @@ func (u *upstreamResolver) stop() { func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { defer u.checkUpstreamFails() - log.WithField("question", r.Question[0]).Trace("received an upstream question") + log.WithField("question", r.Question[0]).Debug("received an upstream question") select { case <-u.ctx.Done(): @@ -70,6 +143,19 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { } for _, upstream := range u.upstreamServers { + log.Debugf("querying the upstream %s", upstream) + rr, errR := netroute.New() + if errR != nil { + log.Errorf("unable to create networute: %s", errR) + } else { + add := netip.MustParseAddrPort(upstream) + _, gateway, preferredSrc, errR := rr.Route(add.Addr().AsSlice()) + if errR != nil { + log.Errorf("getting routes returned an error: %v", errR) + } else { + log.Infof("upstream %s gateway: %s, preferredSrc: %s", add.Addr(), gateway, preferredSrc) + } + } ctx, cancel := context.WithTimeout(u.ctx, u.upstreamTimeout) rm, t, err := u.upstreamClient.ExchangeContext(ctx, r, upstream) @@ -87,7 +173,7 @@ func (u *upstreamResolver) ServeDNS(w dns.ResponseWriter, r *dns.Msg) { return } - log.Tracef("took %s to query the upstream %s", t, upstream) + log.Debugf("took %s to query the upstream %s", t, upstream) err = w.WriteMsg(rm) if err != nil { diff --git a/client/internal/engine.go b/client/internal/engine.go index fc5937fb1..58f459de5 100644 --- a/client/internal/engine.go +++ b/client/internal/engine.go @@ -206,7 +206,7 @@ func (e *Engine) Start() error { } else { // todo fix custom address if e.dnsServer == nil { - e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress) + e.dnsServer, err = dns.NewDefaultServer(e.ctx, e.wgInterface, e.config.CustomDNSAddress, e.mobileDep.InterfaceName, wgAddr) if err != nil { e.close() return err diff --git a/client/internal/mobile_dependency.go b/client/internal/mobile_dependency.go index fc01a9de3..b027937ad 100644 --- a/client/internal/mobile_dependency.go +++ b/client/internal/mobile_dependency.go @@ -16,4 +16,5 @@ type MobileDependency struct { DnsReadyListener dns.ReadyListener DnsManager dns.IosDnsManager FileDescriptor int32 + InterfaceName string } diff --git a/client/ios/NetBirdSDK/client.go b/client/ios/NetBirdSDK/client.go index 5cea91b38..ca87c05f4 100644 --- a/client/ios/NetBirdSDK/client.go +++ b/client/ios/NetBirdSDK/client.go @@ -67,8 +67,9 @@ func NewClient(cfgFile, deviceName string, osVersion string, osName string, rout } // Run start the internal client. It is a blocker function -func (c *Client) Run(fd int32) error { +func (c *Client) Run(fd int32, interfaceName string) error { log.Infof("Starting NetBird client") + log.Debugf("Tunnel uses interface: %s", interfaceName) cfg, err := internal.UpdateOrCreateConfig(internal.ConfigInput{ ConfigPath: c.cfgFile, }) @@ -97,7 +98,7 @@ func (c *Client) Run(fd int32) error { // todo do not throw error in case of cancelled context ctx = internal.CtxInitState(ctx) c.onHostDnsFn = func([]string) {} - return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.routeListener, c.dnsManager) + return internal.RunClientiOS(ctx, cfg, c.recorder, fd, c.routeListener, c.dnsManager, interfaceName) } // Stop the internal client and free the resources