From 2968893add8848534a043cc87ce5636116042101 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Thu, 22 Jul 2021 10:14:46 -0700 Subject: [PATCH] net/dns/resolver: bound DoH usage on iOS Updates tailscale/corp#2238 Signed-off-by: Brad Fitzpatrick --- net/dns/resolver/forwarder.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 557f4522f..2069af95c 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -16,6 +16,7 @@ "math/rand" "net" "net/http" + "runtime" "strings" "sync" "time" @@ -159,6 +160,7 @@ type forwarder struct { logf logger.Logf linkMon *monitor.Mon linkSel ForwardLinkSelector + dohSem chan struct{} ctx context.Context // good until Close ctxCancel context.CancelFunc // closes ctx @@ -180,11 +182,18 @@ func init() { } func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *forwarder { + maxDoHInFlight := 1000 // effectively unlimited + if runtime.GOOS == "ios" { + // No HTTP/2 on iOS yet (for size reasons), so DoH is + // pricier. + maxDoHInFlight = 10 + } f := &forwarder{ logf: logger.WithPrefix(logf, "forward: "), linkMon: linkMon, linkSel: linkSel, responses: responses, + dohSem: make(chan struct{}, maxDoHInFlight), } f.ctx, f.ctxCancel = context.WithCancel(context.Background()) return f @@ -262,7 +271,22 @@ func (f *forwarder) getDoHClient(ip netaddr.IP) (urlBase string, c *http.Client, const dohType = "application/dns-message" +func (f *forwarder) releaseDoHSem() { <-f.dohSem } + func (f *forwarder) sendDoH(ctx context.Context, urlBase string, c *http.Client, packet []byte) ([]byte, error) { + // Bound the number of HTTP requests in flight. This primarily + // matters for iOS where we're very memory constrained and + // HTTP requests are heavier on iOS where we don't include + // HTTP/2 for binary size reasons (as binaries on iOS linked + // with Go code cost memory proportional to the binary size, + // for reasons not fully understood). + select { + case f.dohSem <- struct{}{}: + case <-ctx.Done(): + return nil, ctx.Err() + } + defer f.releaseDoHSem() + req, err := http.NewRequestWithContext(ctx, "POST", urlBase, bytes.NewReader(packet)) if err != nil { return nil, err