diff --git a/hostinfo/hostinfo.go b/hostinfo/hostinfo.go index 959450c70..160728001 100644 --- a/hostinfo/hostinfo.go +++ b/hostinfo/hostinfo.go @@ -28,7 +28,7 @@ func New() *tailcfg.Hostinfo { IPNVersion: version.Long, Hostname: hostname, OS: version.OS(), - OSVersion: getOSVersion(), + OSVersion: GetOSVersion(), Package: packageType(), GoArch: runtime.GOARCH, DeviceModel: deviceModel(), @@ -37,7 +37,8 @@ func New() *tailcfg.Hostinfo { var osVersion func() string // non-nil on some platforms -func getOSVersion() string { +// GetOSVersion returns the OSVersion of current host if available. +func GetOSVersion() string { if s, _ := osVersionAtomic.Load().(string); s != "" { return s } diff --git a/net/dns/resolver/forwarder.go b/net/dns/resolver/forwarder.go index 2dde7040c..41ac11227 100644 --- a/net/dns/resolver/forwarder.go +++ b/net/dns/resolver/forwarder.go @@ -17,12 +17,14 @@ import ( "net/http" "runtime" "sort" + "strconv" "strings" "sync" "time" dns "golang.org/x/net/dns/dnsmessage" "inet.af/netaddr" + "tailscale.com/hostinfo" "tailscale.com/net/netns" "tailscale.com/types/dnstype" "tailscale.com/types/logger" @@ -179,19 +181,37 @@ func init() { rand.Seed(time.Now().UnixNano()) } -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 +func maxDoHInFlight(goos string) int { + if goos != "ios" { + return 1000 // effectively unlimited + } + // iOS < 15 limits the memory to 15MB for NetworkExtensions. + // iOS >= 15 gives us 50MB. + // See: https://tailscale.com/blog/go-linker/ + ver := hostinfo.GetOSVersion() + if ver == "" { + // Unknown iOS version, be cautious. + return 10 + } + idx := strings.Index(ver, ".") + if idx == -1 { + // Unknown iOS version, be cautious. + return 10 + } + major := ver[:idx] + if m, err := strconv.Atoi(major); err != nil || m < 15 { + return 10 } + return 1000 +} + +func newForwarder(logf logger.Logf, responses chan packet, linkMon *monitor.Mon, linkSel ForwardLinkSelector) *forwarder { f := &forwarder{ logf: logger.WithPrefix(logf, "forward: "), linkMon: linkMon, linkSel: linkSel, responses: responses, - dohSem: make(chan struct{}, maxDoHInFlight), + dohSem: make(chan struct{}, maxDoHInFlight(runtime.GOOS)), } f.ctx, f.ctxCancel = context.WithCancel(context.Background()) return f diff --git a/net/dns/resolver/forwarder_test.go b/net/dns/resolver/forwarder_test.go index 8d14e23aa..c632f1d47 100644 --- a/net/dns/resolver/forwarder_test.go +++ b/net/dns/resolver/forwarder_test.go @@ -13,6 +13,7 @@ import ( "time" dns "golang.org/x/net/dns/dnsmessage" + "tailscale.com/hostinfo" "tailscale.com/types/dnstype" ) @@ -140,3 +141,30 @@ func TestGetRCode(t *testing.T) { }) } } + +func TestMaxDoHInFlight(t *testing.T) { + tests := []struct { + goos string + ver string + want int + }{ + {"ios", "", 10}, + {"ios", "1532", 10}, + {"ios", "9.3.2", 10}, + {"ios", "14.3.2", 10}, + {"ios", "15.3.2", 1000}, + {"ios", "20.3.2", 1000}, + {"android", "", 1000}, + {"darwin", "", 1000}, + {"linux", "", 1000}, + } + for _, tc := range tests { + t.Run(fmt.Sprintf("%s-%s", tc.goos, tc.ver), func(t *testing.T) { + hostinfo.SetOSVersion(tc.ver) + got := maxDoHInFlight(tc.goos) + if got != tc.want { + t.Errorf("got %d; want %d", got, tc.want) + } + }) + } +}