1515package dnsproxy
1616
1717import (
18+ "context"
1819 "fmt"
1920 "net"
2021 "net/netip"
@@ -44,7 +45,12 @@ type Proxy struct {
4445 alwaysDeny []policy.EgressRule
4546 alwaysAllow []policy.EgressRule
4647 listenAddr string
47- upstreams []string // ordered resolver chain; try next on forward failure
48+ upstreams []string // ordered resolver chain from discovery (immutable after New)
49+ upstreamMu sync.RWMutex
50+ activeUpstreams []string // healthy subset; same order as upstreams; used for forwarding
51+ upstreamProbeName string // wire name for probe (FQDN or "." for root)
52+ upstreamProbeQType uint16 // dns.TypeA or dns.TypeNS etc.
53+ upstreamProbeInterval time.Duration
4854 upstreamExchangeTimeout time.Duration
4955 servers []* dns.Server
5056 shutdownOnce sync.Once
@@ -70,9 +76,14 @@ func New(p *policy.NetworkPolicy, listenAddr string, alwaysDeny, alwaysAllow []p
7076 if err != nil {
7177 return nil , err
7278 }
79+ probeName , probeQType := upstreamProbeFromEnv ()
7380 proxy := & Proxy {
7481 listenAddr : listenAddr ,
7582 upstreams : upstreams ,
83+ activeUpstreams : append ([]string (nil ), upstreams ... ),
84+ upstreamProbeName : probeName ,
85+ upstreamProbeQType : probeQType ,
86+ upstreamProbeInterval : upstreamProbeIntervalFromEnv (),
7687 upstreamExchangeTimeout : upstreamExchangeTimeoutFromEnv (),
7788 userPolicy : ensurePolicyDefaults (p ),
7889 alwaysDeny : append ([]policy.EgressRule (nil ), alwaysDeny ... ),
@@ -101,7 +112,7 @@ func upstreamExchangeTimeoutFromEnv() time.Duration {
101112 return time .Duration (n ) * time .Second
102113}
103114
104- func (p * Proxy ) Start () error {
115+ func (p * Proxy ) Start (ctx context. Context ) error {
105116 handler := dns .HandlerFunc (p .serveDNS )
106117
107118 udpServer := & dns.Server {Addr : p .listenAddr , Net : "udp" , Handler : handler }
@@ -118,13 +129,18 @@ func (p *Proxy) Start() error {
118129 }()
119130 }
120131
132+ timer := time .NewTimer (200 * time .Millisecond )
133+ defer timer .Stop ()
121134 select {
122135 case err := <- errCh :
123136 return fmt .Errorf ("dns proxy failed: %w" , err )
124- case <- time .After (200 * time .Millisecond ):
125- // small grace window; running fine
126- return nil
137+ case <- timer .C :
138+ // listeners bound; start upstream probes only after DNS servers are up
127139 }
140+
141+ go p .runUpstreamProbes (ctx )
142+
143+ return nil
128144}
129145
130146// Shutdown stops UDP/TCP DNS listeners. Safe to call more than once.
@@ -193,8 +209,9 @@ func (p *Proxy) maybeNotifyResolved(domain string, resp *dns.Msg) {
193209}
194210
195211func (p * Proxy ) forward (r * dns.Msg ) (* dns.Msg , error ) {
212+ list := p .forwardUpstreams ()
196213 var lastErr error
197- for i , upstream := range p . upstreams {
214+ for i , upstream := range list {
198215 c := & dns.Client {
199216 Timeout : p .upstreamExchangeTimeout ,
200217 Dialer : p .dialerForUpstream (upstream ),
@@ -209,7 +226,7 @@ func (p *Proxy) forward(r *dns.Msg) (*dns.Msg, error) {
209226 lastErr = fmt .Errorf ("nil response from %s" , upstream )
210227 continue
211228 }
212- if tryNext , reason := p .shouldFailoverAfterResponse (resp , i ); tryNext {
229+ if tryNext , reason := p .shouldFailoverAfterResponse (resp , i , len ( list ) ); tryNext {
213230 lastErr = fmt .Errorf ("%s from %s" , reason , upstream )
214231 log .Warnf ("[dns] upstream %s: %s; trying next" , upstream , reason )
215232 continue
@@ -225,15 +242,15 @@ func (p *Proxy) forward(r *dns.Msg) (*dns.Msg, error) {
225242// shouldFailoverAfterResponse returns whether to try the next upstream.
226243// NXDOMAIN is not retried. NOERROR with an empty Answer (NODATA) is retried when another upstream exists:
227244// a broken or non-recursive first hop may return empty NOERROR while a public resolver succeeds.
228- func (p * Proxy ) shouldFailoverAfterResponse (resp * dns.Msg , upstreamIdx int ) (tryNext bool , reason string ) {
245+ func (p * Proxy ) shouldFailoverAfterResponse (resp * dns.Msg , upstreamIdx , upstreamCount int ) (tryNext bool , reason string ) {
229246 if resp == nil {
230247 return true , "nil response"
231248 }
232249 switch resp .Rcode {
233250 case dns .RcodeNameError :
234251 return false , ""
235252 case dns .RcodeSuccess :
236- if len (resp .Answer ) == 0 && upstreamIdx < len ( p . upstreams ) - 1 {
253+ if len (resp .Answer ) == 0 && upstreamIdx < upstreamCount - 1 {
237254 return true , "empty NOERROR"
238255 }
239256 return false , ""
@@ -246,12 +263,13 @@ func (p *Proxy) shouldFailoverAfterResponse(resp *dns.Msg, upstreamIdx int) (try
246263 }
247264}
248265
249- // UpstreamHost returns the host part of the first upstream resolver, empty on parse error.
266+ // UpstreamHost returns the host part of the first upstream resolver used for forwarding , empty on parse error.
250267func (p * Proxy ) UpstreamHost () string {
251- if len (p .upstreams ) == 0 {
268+ list := p .forwardUpstreams ()
269+ if len (list ) == 0 {
252270 return ""
253271 }
254- host , _ , err := net .SplitHostPort (p . upstreams [0 ])
272+ host , _ , err := net .SplitHostPort (list [0 ])
255273 if err != nil {
256274 return ""
257275 }
@@ -262,15 +280,17 @@ func (p *Proxy) UpstreamHost() string {
262280// Passing nil reverts to the default deny-all policy.
263281func (p * Proxy ) UpdatePolicy (newPolicy * policy.NetworkPolicy ) {
264282 p .policyMu .Lock ()
283+ defer p .policyMu .Unlock ()
284+
265285 p .userPolicy = ensurePolicyDefaults (newPolicy )
266286 p .refreshEffectivePolicy ()
267- p .policyMu .Unlock ()
268287}
269288
270289// CurrentPolicy returns the user policy (POST/PATCH/GET), not the always-deny/allow overlay.
271290func (p * Proxy ) CurrentPolicy () * policy.NetworkPolicy {
272291 p .policyMu .RLock ()
273292 defer p .policyMu .RUnlock ()
293+
274294 return p .userPolicy
275295}
276296
0 commit comments