Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion client/grpc/dialer_generic.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ func WithCustomDialer(_ bool, _ string) grpc.DialOption {
// the custom dialer requires root permissions which are not required for use cases run as non-root
if currentUser.Uid != "0" {
log.Debug("Not running as root, using standard dialer")
dialer := &net.Dialer{}
dialer := &net.Dialer{
Resolver: nbnet.NewResolver(),
}
return dialer.DialContext(ctx, "tcp", addr)
}
}
Expand Down
7 changes: 6 additions & 1 deletion client/internal/stdnet/stdnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"github.com/pion/transport/v3/stdnet"

"github.com/netbirdio/netbird/client/iface/netstack"
nbnet "github.com/netbirdio/netbird/client/net"
)

const (
Expand All @@ -42,6 +43,8 @@ type Net struct {

// ctx is the context for network operations that supports cancellation
ctx context.Context

resolver *net.Resolver
}

// NewNetWithDiscover creates a new StdNet instance.
Expand All @@ -52,6 +55,7 @@ func NewNetWithDiscover(ctx context.Context, iFaceDiscover ExternalIFaceDiscover
n := &Net{
interfaceFilter: InterfaceFilter(disallowList),
ctx: ctx,
resolver: nbnet.NewResolver(),
}
// current ExternalIFaceDiscover implement in android-client https://github.dev/netbirdio/android-client
// so in android cli use pionDiscover
Expand All @@ -72,6 +76,7 @@ func NewNet(ctx context.Context, disallowList []string) (*Net, error) {
iFaceDiscover: pionDiscover{},
interfaceFilter: InterfaceFilter(disallowList),
ctx: ctx,
resolver: nbnet.NewResolver(),
}
return n, n.UpdateInterfaces()
}
Expand Down Expand Up @@ -110,7 +115,7 @@ func (n *Net) resolveAddr(network, address string) (netip.AddrPort, error) {
ctx, cancel := context.WithTimeout(n.ctx, dnsResolveTimeout)
defer cancel()

addrs, err := net.DefaultResolver.LookupNetIP(ctx, ipNet, host)
addrs, err := n.resolver.LookupNetIP(ctx, ipNet, host)
if err != nil {
return netip.AddrPort{}, err
}
Expand Down
4 changes: 3 additions & 1 deletion client/net/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ type Dialer struct {
// NewDialer returns a customized net.Dialer with overridden Control method
func NewDialer() *Dialer {
dialer := &Dialer{
Dialer: &net.Dialer{},
Dialer: &net.Dialer{
Resolver: NewResolver(),
},
}
dialer.init()
return dialer
Expand Down
47 changes: 47 additions & 0 deletions client/net/resolver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package net

import (
"net"
"os"
"runtime"
"strings"

log "github.com/sirupsen/logrus"
)

const (
// EnvResolver is the environment variable to control DNS resolver behavior
// Values: "system" (use system resolver), "go" (use pure Go resolver), empty (auto-detect)
EnvResolver = "NB_DNS_RESOLVER"
)

// NewResolver creates a DNS resolver with appropriate settings based on platform and configuration.
// On Darwin (macOS), it defaults to the pure Go resolver to avoid getaddrinfo hangs after sleep/wake.
// This is particularly important for connections using this package's Dialer, which bypasses the NetBird
// overlay network for control plane traffic. Since these connections target external infrastructure
// (management, signal, relay servers), it is safe to ignore split DNS configurations that would
// normally be provided by the system resolver.
// On other platforms, it uses the system resolver (cgo).
// This behavior can be overridden using the NB_DNS_RESOLVER environment variable or GODEBUG.
Comment on lines +18 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify the split DNS assumption for control plane traffic.

The comment states it's "safe to ignore split DNS configurations" for control plane traffic because these connections "target external infrastructure." However, in enterprise or self-hosted scenarios, management/signal/relay servers may be internal resources that rely on split DNS. Users with private deployments behind split DNS could experience connectivity issues.

Consider documenting this limitation or adding a configuration option for environments where control plane servers are internal.



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the NewResolver function implementation
cat client/net/resolver.go | head -60

Length of output: 1472


🏁 Script executed:

#!/bin/bash
# Search for NewResolver usage across the codebase
rg -n "NewResolver" -A3 -B3 --type=go

Length of output: 15091


🏁 Script executed:

#!/bin/bash
# Search for split DNS references
rg -n "split.*dns|DNS.*split" -i --type=go

Length of output: 174


🏁 Script executed:

#!/bin/bash
# Search for control plane related DNS or resolver usage
rg -n "control.*plane|management.*server|signal.*server|relay.*server" -i --type=go -C2 | head -100

Length of output: 6138


Update documentation to guide users on split DNS scenarios in enterprise deployments.

The concern about split DNS is valid. While the code provides override via NB_DNS_RESOLVER=system environment variable, the documentation doesn't explain when or why users should use it. In self-hosted or enterprise deployments where management/signal/relay servers are internal and rely on split DNS, users will encounter connectivity issues without knowing how to resolve them.

Recommended improvements:

  • Document in the comment that NB_DNS_RESOLVER=system should be used for deployments with internal servers behind split DNS
  • Explain what the GODEBUG override does and provide an example
  • Add troubleshooting guidance for split DNS scenarios

This allows users to self-serve when they encounter the issue rather than discovering it only through trial and error.

🤖 Prompt for AI Agents
In client/net/resolver.go around lines 18 to 25, expand the existing comment to
document split DNS guidance: add a short note that in self-hosted or enterprise
deployments where management/signal/relay servers are internal and reachable
only via split DNS, users should set NB_DNS_RESOLVER=system to force the system
resolver; briefly explain the GODEBUG override (e.g., how to force the pure Go
resolver via GODEBUG=netdns=go or use system via netdns=cgo) and include a
concrete example of setting the env var, and append a one-paragraph
troubleshooting tip listing symptoms (failed resolution to internal hostnames,
connection timeouts after suspend/resume) and the recommended steps (check split
DNS, try NB_DNS_RESOLVER=system, then GODEBUG variations, and where to file
logs).

func NewResolver() *net.Resolver {
if resolver := os.Getenv(EnvResolver); resolver != "" {
switch strings.ToLower(resolver) {
case "system":
return net.DefaultResolver
case "go":
return &net.Resolver{
PreferGo: true,
}
default:
log.Debugf("Invalid %s value: %s, using platform defaults", EnvResolver, resolver)
}
}

if runtime.GOOS == "darwin" {
return &net.Resolver{
PreferGo: true,
}
}

return net.DefaultResolver
}
Loading