net/dns/resolver: bound DoH usage on iOS
Updates tailscale/corp#2238
"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
