mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-13 00:49:07 +02:00
Harden race fan-out and fix lint
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -44,6 +45,11 @@ const (
|
||||
warningDelayBonusCap = 30 * time.Second
|
||||
)
|
||||
|
||||
// errNoUsableNameservers signals that a merged-domain group has no usable
|
||||
// upstream servers. Callers should skip the group without treating it as a
|
||||
// build failure.
|
||||
var errNoUsableNameservers = errors.New("no usable nameservers")
|
||||
|
||||
// ReadyListener is a notification mechanism what indicate the server is ready to handle host dns address changes
|
||||
type ReadyListener interface {
|
||||
OnReady()
|
||||
@@ -315,6 +321,19 @@ func (s *DefaultServer) SetRouteSources(selected, active func() route.HAMap) {
|
||||
defer s.mux.Unlock()
|
||||
s.selectedRoutes = selected
|
||||
s.activeRoutes = active
|
||||
|
||||
// Permanent / iOS constructors build the root handler before the
|
||||
// engine wires route sources, so its selectedRoutes callback would
|
||||
// otherwise remain nil and overlay upstreams would be classified
|
||||
// as public. Propagate the new accessors to existing handlers.
|
||||
type routeSettable interface {
|
||||
setSelectedRoutes(func() route.HAMap)
|
||||
}
|
||||
for _, entry := range s.dnsMuxMap {
|
||||
if h, ok := entry.handler.(routeSettable); ok {
|
||||
h.setSelectedRoutes(selected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterHandler registers a handler for the given domains with the given priority.
|
||||
@@ -778,7 +797,7 @@ func (s *DefaultServer) registerFallback(config HostDNSConfig) {
|
||||
s.wgInterface,
|
||||
s.statusRecorder,
|
||||
s.hostsDNSHolder,
|
||||
domain.Domain(nbdns.RootZone),
|
||||
nbdns.RootZone,
|
||||
)
|
||||
if err != nil {
|
||||
log.Errorf("failed to create upstream resolver for original nameservers: %v", err)
|
||||
@@ -861,11 +880,13 @@ func (s *DefaultServer) buildUpstreamHandlerUpdate(nameServerGroups []*nbdns.Nam
|
||||
|
||||
update, err := s.buildMergedDomainHandler(domainGroup, priority)
|
||||
if err != nil {
|
||||
if errors.Is(err, errNoUsableNameservers) {
|
||||
log.Errorf("no usable nameservers for domain=%s", domainGroup.domain)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if update != nil {
|
||||
muxUpdates = append(muxUpdates, *update)
|
||||
}
|
||||
muxUpdates = append(muxUpdates, *update)
|
||||
}
|
||||
|
||||
return muxUpdates, nil
|
||||
@@ -897,8 +918,7 @@ func (s *DefaultServer) buildMergedDomainHandler(domainGroup nsGroupsByDomain, p
|
||||
|
||||
if len(handler.upstreamServers) == 0 {
|
||||
handler.Stop()
|
||||
log.Errorf("no usable nameservers for domain=%s", domainGroup.domain)
|
||||
return nil, nil
|
||||
return nil, errNoUsableNameservers
|
||||
}
|
||||
|
||||
log.Debugf("creating merged handler for domain=%s with %d group(s) priority=%d", domainGroup.domain, len(handler.upstreamServers), priority)
|
||||
@@ -927,6 +947,27 @@ func (s *DefaultServer) filterNameServers(nameServers []nbdns.NameServer) []neti
|
||||
return out
|
||||
}
|
||||
|
||||
// usableNameServers returns the subset of nameServers the handler would
|
||||
// actually query. Matches filterNameServers without the warning logs, so
|
||||
// it's safe to call on every health-projection tick.
|
||||
func (s *DefaultServer) usableNameServers(nameServers []nbdns.NameServer) []netip.AddrPort {
|
||||
var runtimeIP netip.Addr
|
||||
if s.service != nil {
|
||||
runtimeIP = s.service.RuntimeIP()
|
||||
}
|
||||
var out []netip.AddrPort
|
||||
for _, ns := range nameServers {
|
||||
if ns.NSType != nbdns.UDPNameServerType {
|
||||
continue
|
||||
}
|
||||
if runtimeIP.IsValid() && ns.IP == runtimeIP {
|
||||
continue
|
||||
}
|
||||
out = append(out, ns.AddrPort())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *DefaultServer) updateMux(muxUpdates []handlerWrapper) {
|
||||
// this will introduce a short period of time when the server is not able to handle DNS requests
|
||||
for _, existing := range s.dnsMuxMap {
|
||||
@@ -1044,11 +1085,14 @@ func (s *DefaultServer) projectNSGroupHealth(snap nsHealthSnapshot) {
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
delay := s.warningDelay(len(snap.selected))
|
||||
delay := s.warningDelay(haMapRouteCount(snap.selected))
|
||||
states := make([]peer.NSGroupState, 0, len(snap.groups))
|
||||
seen := make(map[nsGroupID]struct{}, len(snap.groups))
|
||||
for _, group := range snap.groups {
|
||||
servers := nameServerAddrPorts(group.NameServers)
|
||||
servers := s.usableNameServers(group.NameServers)
|
||||
if len(servers) == 0 {
|
||||
continue
|
||||
}
|
||||
verdict, groupErr := evaluateNSGroupHealth(snap.merged, servers, now)
|
||||
id := generateGroupKey(group)
|
||||
seen[id] = struct{}{}
|
||||
@@ -1069,7 +1113,10 @@ func (s *DefaultServer) projectNSGroupHealth(snap nsHealthSnapshot) {
|
||||
enabled = s.projectUnhealthy(p, servers, immediate, now, delay)
|
||||
case nsVerdictUndecided:
|
||||
// Stay Available until evidence says otherwise, unless a
|
||||
// warning is already active for this group.
|
||||
// warning is already active for this group. Also clear any
|
||||
// prior Unhealthy streak so a later Unhealthy verdict starts
|
||||
// a fresh grace window rather than inheriting a stale one.
|
||||
p.unhealthySince = time.Time{}
|
||||
enabled = !p.warningActive
|
||||
groupErr = nil
|
||||
}
|
||||
@@ -1142,6 +1189,9 @@ func (s *DefaultServer) projectUnhealthy(p *nsGroupProj, servers []netip.AddrPor
|
||||
// count. Scales gently: +1s per 100 routes, capped by
|
||||
// warningDelayBonusCap. Parallel handshakes mean handshake time grows
|
||||
// much slower than route count, so linear scaling would overcorrect.
|
||||
//
|
||||
// TODO: revisit the scaling curve with real-world data — the current
|
||||
// values are a reasonable starting point, not a measured fit.
|
||||
func (s *DefaultServer) warningDelay(routeCount int) time.Duration {
|
||||
bonus := time.Duration(routeCount/100) * time.Second
|
||||
if bonus > warningDelayBonusCap {
|
||||
@@ -1164,11 +1214,16 @@ func (s *DefaultServer) groupHasImmediateUpstream(servers []netip.AddrPort, snap
|
||||
for _, srv := range servers {
|
||||
addr := srv.Addr().Unmap()
|
||||
overlay := overlayV4.IsValid() && overlayV4.Contains(addr)
|
||||
routed := haMapContains(snap.selected, addr)
|
||||
selMatched, selDynamic := haMapContains(snap.selected, addr)
|
||||
// Treat an unknown (dynamic selected route) as possibly routed:
|
||||
// the upstream might reach through a dynamic route whose Network
|
||||
// hasn't resolved yet, and classifying as public would bypass
|
||||
// the startup grace window.
|
||||
routed := selMatched || selDynamic
|
||||
if !overlay && !routed {
|
||||
return true
|
||||
}
|
||||
if haMapContains(snap.active, addr) {
|
||||
if actMatched, _ := haMapContains(snap.active, addr); actMatched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -1290,15 +1345,6 @@ func classifyUpstreamHealth(h UpstreamHealth, now time.Time) upstreamClassificat
|
||||
return upstreamStale
|
||||
}
|
||||
|
||||
// nameServerAddrPorts flattens a NameServer list to AddrPorts.
|
||||
func nameServerAddrPorts(ns []nbdns.NameServer) []netip.AddrPort {
|
||||
out := make([]netip.AddrPort, 0, len(ns))
|
||||
for _, n := range ns {
|
||||
out = append(out, n.AddrPort())
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func joinAddrPorts(servers []netip.AddrPort) string {
|
||||
parts := make([]string, 0, len(servers))
|
||||
for _, s := range servers {
|
||||
@@ -1307,12 +1353,18 @@ func joinAddrPorts(servers []netip.AddrPort) string {
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
// generateGroupKey returns a stable identity for an NS group so health
|
||||
// state (everHealthy / warningActive) survives reorderings in the
|
||||
// configured nameserver or domain lists.
|
||||
func generateGroupKey(nsGroup *nbdns.NameServerGroup) nsGroupID {
|
||||
var servers []string
|
||||
servers := make([]string, 0, len(nsGroup.NameServers))
|
||||
for _, ns := range nsGroup.NameServers {
|
||||
servers = append(servers, ns.AddrPort().String())
|
||||
}
|
||||
return nsGroupID(fmt.Sprintf("%v_%v", servers, nsGroup.Domains))
|
||||
slices.Sort(servers)
|
||||
domains := slices.Clone(nsGroup.Domains)
|
||||
slices.Sort(domains)
|
||||
return nsGroupID(fmt.Sprintf("%v_%v", servers, domains))
|
||||
}
|
||||
|
||||
// groupNSGroupsByDomain groups nameserver groups by their match domains
|
||||
|
||||
Reference in New Issue
Block a user