@@ -63,6 +63,8 @@ const maxEOFTries = uint8(2)
6363
6464const purgethreshold = 1 * time .Minute
6565
66+ const echRetryPeriod = 8 * time .Hour
67+
6668var errNoClient error = errors .New ("no doh client" )
6769
6870type odohtransport struct {
@@ -87,16 +89,17 @@ type transport struct {
8789 url string // endpoint URL
8890 hostname string // endpoint hostname
8991 port uint16
90- skipTLSVerify bool // skips tls verification
91- tlsconfig * tls.Config // preset tlsconfig for the endpoint
92- echconfig * tls.Config // preset echconfig for the endpoint
93- echrejects atomic.Uint32 // number of running ech rejections
94- pxcmu sync.RWMutex // protects pxclients
95- pxclients map [string ]* proxytransport // todo: use weak pointers for Proxy
96- lastpurge * core.Volatile [time.Time ] // last scrubbed time for stale pxclients
97- preferGET bool // saw 405 Method Not Allowed
98- proxies ipn.ProxyProvider // proxy provider, may be nil
99- relay string // dial doh via relay, may be empty
92+ skipTLSVerify bool // skips tls verification
93+ tlsconfig * tls.Config // preset tlsconfig for the endpoint
94+ echconfig * core.Volatile [* tls.Config ] // echconfig for the endpoint; may be nil
95+ echrejects atomic.Uint32 // number of running ech rejections
96+ echlastattempt * core.Volatile [time.Time ] // last attempt fetching ech cfg
97+ pxcmu sync.RWMutex // protects pxclients
98+ pxclients map [string ]* proxytransport // todo: use weak pointers for Proxy
99+ lastpurge * core.Volatile [time.Time ] // last scrubbed time for stale pxclients
100+ preferGET bool // saw 405 Method Not Allowed
101+ proxies ipn.ProxyProvider // proxy provider, may be nil
102+ relay string // dial doh via relay, may be empty
100103 status * core.Volatile [int ]
101104 est core.P2QuantileEstimator
102105}
@@ -136,16 +139,18 @@ func newTransport(ctx context.Context, typ, id, rawurl, otargeturl string, addrs
136139 ctx , done := context .WithCancel (ctx )
137140
138141 t := & transport {
139- ctx : ctx ,
140- done : done ,
141- id : id ,
142- typ : typ ,
143- proxies : px , // may be nil
144- relay : relay , // may be empty
145- status : core .NewVolatile (dnsx .Start ),
146- pxclients : make (map [string ]* proxytransport ),
147- lastpurge : core .NewVolatile (time .Now ()),
148- est : core .NewP50Estimator (ctx ),
142+ ctx : ctx ,
143+ done : done ,
144+ id : id ,
145+ typ : typ ,
146+ proxies : px , // may be nil
147+ relay : relay , // may be empty
148+ status : core .NewVolatile (dnsx .Start ),
149+ pxclients : make (map [string ]* proxytransport ),
150+ echconfig : core .NewZeroVolatile [* tls.Config ](),
151+ echlastattempt : core .NewZeroVolatile [time.Time ](),
152+ lastpurge : core .NewVolatile (time .Now ()),
153+ est : core .NewP50Estimator (ctx ),
149154 }
150155 if ! isodoh {
151156 parsedurl , err := url .Parse (rawurl )
@@ -216,7 +221,8 @@ func newTransport(ctx context.Context, typ, id, rawurl, otargeturl string, addrs
216221 log .I ("doh: ODOH for %s -> %s" , proxy , otargeturl )
217222 }
218223
219- ech := t .ech ()
224+ echcfg := t .getOrCreateEchConfigIfNeeded ()
225+
220226 // TODO: ClientAuth
221227 // Supply a client certificate during TLS handshakes.
222228 // if auth != nil {
@@ -226,16 +232,7 @@ func newTransport(ctx context.Context, typ, id, rawurl, otargeturl string, addrs
226232 // ServerName: t.hostname,
227233 // }
228234 // }
229- if len (ech ) > 0 {
230- t .echconfig = & tls.Config {
231- InsecureSkipVerify : t .skipTLSVerify ,
232- MinVersion : tls .VersionTLS13 , // must be 1.3
233- EncryptedClientHelloConfigList : ech ,
234- SessionTicketsDisabled : false ,
235- ClientSessionCache : core .TlsSessionCache (),
236- EncryptedClientHelloRejectionVerify : t .echVerifyFn (),
237- }
238- }
235+
239236 t .tlsconfig = & tls.Config {
240237 InsecureSkipVerify : t .skipTLSVerify ,
241238 MinVersion : tls .VersionTLS12 ,
@@ -246,7 +243,7 @@ func newTransport(ctx context.Context, typ, id, rawurl, otargeturl string, addrs
246243 }
247244
248245 log .I ("doh: new transport(%s): %s; relay? %t; addrs? %v; resolved? %t, ech? %t" ,
249- t .typ , t .url , len (relay ) > 0 , addrs , renewed , len ( ech ) > 0 )
246+ t .typ , t .url , len (relay ) > 0 , addrs , renewed , echcfg != nil )
250247 return t , nil
251248}
252249
@@ -331,42 +328,110 @@ func (t *transport) purgeProxyClients() {
331328 }
332329}
333330
331+ func (t * transport ) getOrCreateEchConfigIfNeeded () * tls.Config {
332+ echcfg := t .echconfig .Load ()
333+ if echcfg != nil {
334+ return echcfg
335+ }
336+
337+ prev := t .echlastattempt .Load ()
338+ if time .Since (prev ) < echRetryPeriod {
339+ return nil
340+ }
341+ refetch := t .echlastattempt .Cas (prev , time .Now ())
342+ if ! refetch {
343+ return nil
344+ }
345+
346+ if ech := t .ech (); len (ech ) > 0 {
347+ echcfg = & tls.Config {
348+ InsecureSkipVerify : t .skipTLSVerify ,
349+ MinVersion : tls .VersionTLS13 , // must be 1.3
350+ EncryptedClientHelloConfigList : ech ,
351+ SessionTicketsDisabled : false ,
352+ ClientSessionCache : core .TlsSessionCache (),
353+ EncryptedClientHelloRejectionVerify : t .echVerifyFn (),
354+ }
355+ t .echconfig .Store (echcfg )
356+ }
357+
358+ ok := echcfg == nil
359+ logeif (! ok )("doh: %s fetch ech... ok? %t" , t .ID (), ok )
360+ return echcfg
361+ }
362+
334363func (t * transport ) httpClientsFor (p ipn.Proxy ) (c3 , c * http.Client ) {
335364 pid := p .ID ().V ()
336365 t .pxcmu .RLock ()
337366 pxtr , ok := t .pxclients [pid ]
338- t .pxcmu .RUnlock ()
339-
340367 same := pxtr != nil && pxtr .p .Handle () == p .Handle ()
341368 if ok && same {
342- return pxtr .c3 , pxtr .c
369+ c = pxtr .c
370+ c3 = pxtr .c3
343371 }
372+ t .pxcmu .RUnlock ()
344373
345374 pdial := p .Dialer ().Dial
375+ if c != nil {
376+ if c3 == nil {
377+ if echcfg := t .getOrCreateEchConfigIfNeeded (); echcfg != nil {
378+ c3 = new (http.Client )
379+ c3 .Transport = h2 (pdial , echcfg )
380+ t .updateHttpClientsFor (p , c , c3 )
381+ }
382+ }
383+ return c3 , c // c3 may be nil
384+ }
346385
347386 var client http.Client
348387 var client3 * http.Client
349388 client .Transport = h2 (pdial , t .tlsconfig )
350- if t .echconfig != nil {
389+ if echcfg := t .echconfig . Load (); echcfg != nil {
351390 client3 = new (http.Client )
352- client3 .Transport = h2 (pdial , t . echconfig )
391+ client3 .Transport = h2 (pdial , echcfg )
353392 }
354393
355394 // last writer wins
356- t .pxcmu .Lock ()
357- t .pxclients [pid ] = & proxytransport {
358- p : p ,
359- c : & client ,
360- c3 : client3 , // may be nil
361- }
362- t .pxcmu .Unlock ()
395+ t .updateHttpClientsFor (p , & client , client3 )
363396
364397 // check if other proxies need to be purged
365398 go t .purgeProxyClients ()
366399
367400 return client3 , & client
368401}
369402
403+ // updateHttpClientsFor only updates non-nil http clients dialing via Proxy p.
404+ func (t * transport ) updateHttpClientsFor (p ipn.Proxy , c , c3 * http.Client ) {
405+ if c == nil && c3 == nil {
406+ log .E ("doh: %s cannot set/update, all clients nil" , t .ID ())
407+ return
408+ }
409+
410+ pid := p .ID ().V ()
411+
412+ t .pxcmu .Lock ()
413+ defer t .pxcmu .Unlock ()
414+
415+ if pt := t .pxclients [pid ]; pt != nil {
416+ if c != nil {
417+ pt .c = c
418+ }
419+ if c3 != nil {
420+ pt .c3 = c3
421+ }
422+ } else {
423+ if c == nil {
424+ log .E ("doh: %s cannot set http client to nil" , t .ID ())
425+ return
426+ }
427+ t .pxclients [pid ] = & proxytransport {
428+ p : p ,
429+ c : c ,
430+ c3 : c3 , // may be nil
431+ }
432+ }
433+ }
434+
370435// Given a raw DNS query (including the query ID), this function sends the
371436// query. If the query is successful, it returns the response and a nil qerr. Otherwise,
372437// it returns a SERVFAIL response and a qerr with a status value indicating the cause.
@@ -426,7 +491,7 @@ func (t *transport) fetch(pid string, req *http.Request) (*http.Response, string
426491 r , rpid , echdialer , err := t .multifetch (req , pid )
427492 if err != nil {
428493 log .W ("doh: fetch: %s, mayech? %t / echdialer? %t, err: %v" ,
429- ustr , t .echconfig != nil , echdialer , err )
494+ ustr , t .echconfig . Load () != nil , echdialer , err )
430495 return r , rpid , echdialer , uerr (err )
431496 }
432497 return r , rpid , echdialer , nil
@@ -462,15 +527,20 @@ func (t *transport) multifetch(req *http.Request, pid string) (res *http.Respons
462527 if eerr := new (tls.ECHRejectionError ); errors .As (err , & eerr ) {
463528 cont = true
464529 ech := eerr .RetryConfigList
465- useech := t .echconfig != nil
530+
531+ echcfg := t .echconfig .Load ()
532+ useech := echcfg != nil
466533 if len (ech ) <= 0 && useech {
467534 ech = t .ech () // todo: use t.echconfig.ServerName?
468535 log .I ("doh: fetch #%d: err %v; grab new ech? %t" ,
469536 i , eerr , len (ech ) > 0 )
470537 }
471538 if len (ech ) > 0 && useech {
472- t .echconfig .EncryptedClientHelloConfigList = ech
473- c .Transport = h2 (px .Dialer ().Dial , t .echconfig )
539+ echcfg .EncryptedClientHelloConfigList = ech
540+ c .Transport = h2 (px .Dialer ().Dial , echcfg )
541+ t .echconfig .Store (echcfg ) // update ech config
542+ t .echlastattempt .Store (time .Now ())
543+ t .updateHttpClientsFor (px , nil , c ) // update c3
474544 }
475545 n := t .echrejects .Add (1 )
476546 log .I ("doh: fetch #%d: ech rejected; retry? %t, ech? %t; total rejects: %d" ,
@@ -496,7 +566,7 @@ func (t *transport) prepare(pid string) (px ipn.Proxy, err error) {
496566 userelay := len (t .relay ) > 0
497567 hasproxy := t .proxies != nil
498568 useproxy := len (pid ) != 0 // if pid == dnsx.NetNoProxy, then px is ipn.Block
499- useech := t .echconfig != nil
569+ useech := t .echconfig . Load () != nil
500570
501571 if userelay || useproxy {
502572 if userelay { // relay takes precedence
@@ -813,3 +883,10 @@ func (t *transport) Stop() error {
813883 t .done ()
814884 return nil
815885}
886+
887+ func logeif (cond bool ) log.LogFn {
888+ if cond {
889+ return log .E
890+ }
891+ return log .D
892+ }
0 commit comments