mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-04 19:55:09 -04:00
## Describe your changes `getInterfaceIndex` in the iOS upstream DNS resolver dereferenced the result of `net.InterfaceByName` before checking the error, so a missing interface (e.g. during teardown or renaming) caused a nil-pointer panic instead of a DNS client error. The helper now returns a wrapped error before touching the interface; the only caller, `GetClientPrivate`, already propagates the error. The helper moved to an un-build-tagged file so it can be unit-tested on host platforms while remaining available to the iOS build. Added a test covering the missing-interface path. Verified with the new host test, an iOS arm64 CGO compile, and `git diff --check`. ## Issue ticket number and link N/A ## Stack <!-- branch-stack --> Standalone PR based on `main`. ### Checklist - [x] Is it a bug fix - [ ] Is a typo/documentation fix - [ ] Is a feature enhancement - [ ] It is a refactor - [x] Created tests that fail without the change (if possible) - [x] This change does **not** modify the public API, gRPC protocols, functionality behavior, CLI / service flags, or introduce a new feature — **OR** I have discussed it with the NetBird team beforehand (link the issue / Slack thread in the description). See [CONTRIBUTING.md](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTING.md#discuss-changes-with-the-netbird-team-first). > By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md). ## Documentation Select exactly one: - [ ] I added/updated documentation for this change - [x] Documentation is **not needed** for this change (explain why) Internal crash fix in the iOS DNS path; no user-facing behavior or configuration changes. ### Docs PR URL (required if "docs added" is checked) N/A <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved handling of network interface lookup failures with clearer error messages that identify the affected interface. * Added validation for network interface lookups, including reliable error handling when an interface cannot be found. * **Tests** * Added coverage for both successful interface resolution and missing-interface scenarios. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
133 lines
3.4 KiB
Go
133 lines
3.4 KiB
Go
//go:build ios
|
|
|
|
package dns
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/miekg/dns"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/sys/unix"
|
|
|
|
"github.com/netbirdio/netbird/client/internal/peer"
|
|
"github.com/netbirdio/netbird/shared/management/domain"
|
|
)
|
|
|
|
type upstreamResolverIOS struct {
|
|
*upstreamResolverBase
|
|
wgIface WGIface
|
|
}
|
|
|
|
func newUpstreamResolver(
|
|
ctx context.Context,
|
|
wgIface WGIface,
|
|
statusRecorder *peer.Status,
|
|
_ *hostsDNSHolder,
|
|
d domain.Domain,
|
|
) (*upstreamResolverIOS, error) {
|
|
upstreamResolverBase := newUpstreamResolverBase(ctx, statusRecorder, d)
|
|
|
|
ios := &upstreamResolverIOS{
|
|
upstreamResolverBase: upstreamResolverBase,
|
|
wgIface: wgIface,
|
|
}
|
|
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{
|
|
Timeout: ClientTimeout,
|
|
}
|
|
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, err := netip.ParseAddr(upstreamHost)
|
|
if err != nil {
|
|
log.Warnf("failed to parse upstream host %s: %s", upstreamHost, err)
|
|
} else {
|
|
upstreamIP = upstreamIP.Unmap()
|
|
}
|
|
addr := u.wgIface.Address()
|
|
var routed bool
|
|
if u.selectedRoutes != nil {
|
|
// Only a concrete prefix match binds to the tunnel: dialing
|
|
// through a private client for an upstream we can't prove is
|
|
// routed would break public resolvers.
|
|
routed, _ = haMapContains(u.selectedRoutes(), upstreamIP)
|
|
}
|
|
needsPrivate := addr.Network.Contains(upstreamIP) ||
|
|
addr.IPv6Net.Contains(upstreamIP) ||
|
|
routed
|
|
if needsPrivate {
|
|
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)
|
|
}
|
|
}
|
|
|
|
return ExchangeWithFallback(ctx, client, r, upstream)
|
|
}
|
|
|
|
// 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", 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 bindIP.Is6() {
|
|
proto, opt = unix.IPPROTO_IPV6, unix.IPV6_BOUND_IF
|
|
}
|
|
|
|
dialer := &net.Dialer{
|
|
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) {
|
|
operr = unix.SetsockoptInt(int(s), proto, opt, 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,
|
|
Timeout: dialTimeout,
|
|
}
|
|
return client, nil
|
|
}
|