Skip to content

Commit 9227176

Browse files
committed
doh: retry fetching ech configs every 8hrs
1 parent 906193a commit 9227176

1 file changed

Lines changed: 126 additions & 49 deletions

File tree

intra/doh/doh.go

Lines changed: 126 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@ const maxEOFTries = uint8(2)
6363

6464
const purgethreshold = 1 * time.Minute
6565

66+
const echRetryPeriod = 8 * time.Hour
67+
6668
var errNoClient error = errors.New("no doh client")
6769

6870
type 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+
334363
func (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

Comments
 (0)