mirror of
https://github.com/tailscale/tailscale.git
synced 2026-09-14 23:11:39 -04:00
Static size checks (iossize) catch binary dirty-page growth but nothing covered runtime heap cost, which is what actually consumes the iOS Network Extension's 50 MiB jetsam budget. Bring up a tsnet backend (with the full condregister feature set, matching shipping clients) against an in-process testcontrol server and assert live post-GC heap budgets for (a) backend startup with zero peers and (b) marginal cost per netmap peer. The startup test measures 1.3 MiB today and fails loudly on the conn25 flow-table pre-allocation regression (17 MiB) that jetsam-killed the iOS extension on large tailnets. Budgets are deliberately generous (6-12x current measurements) to stay flake-free while still catching the multi-MiB regressions that matter for mobile. A new debugknob enables us to constrain the GSO/GRO batch size to 1 for these tests so as to avoid the memory allocation associated with those buffers, which are a known issue with their own work stream. Updates tailscale/corp#46408 Updates tailscale/corp#18514 Signed-off-by: James Tucker <james@tailscale.com>
75 lines
3.0 KiB
Go
75 lines
3.0 KiB
Go
// Copyright (c) Tailscale Inc & contributors
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
// Package batching implements a socket optimized for increased throughput.
|
||
package batching
|
||
|
||
import (
|
||
"net/netip"
|
||
|
||
"golang.org/x/net/ipv4"
|
||
"golang.org/x/net/ipv6"
|
||
"tailscale.com/envknob"
|
||
"tailscale.com/net/packet"
|
||
"tailscale.com/types/nettype"
|
||
)
|
||
|
||
// BatchSizeFromEnv returns ideal, unless the TS_DEBUG_WG_BATCH_SIZE
|
||
// environment variable is set to a positive integer, in which case it returns
|
||
// that value clamped to [1, ideal].
|
||
//
|
||
// Batch size determines how much packet memory wireguard-go pins per reader
|
||
// goroutine (batch size × 64 KiB message buffers per reader, ~32 MiB total at
|
||
// the Linux default of 128), so memory-budget tests set the env var to 1 to
|
||
// approximate the configuration used on memory-constrained (mobile)
|
||
// platforms, where batch size is always 1.
|
||
func BatchSizeFromEnv(ideal int) int {
|
||
if v, ok := envknob.LookupInt("TS_DEBUG_WG_BATCH_SIZE"); ok && v > 0 {
|
||
return min(v, ideal)
|
||
}
|
||
return ideal
|
||
}
|
||
|
||
var (
|
||
// This acts as a compile-time check for our usage of ipv6.Message in
|
||
// [Conn] for both IPv6 and IPv4 operations.
|
||
_ ipv6.Message = ipv4.Message{}
|
||
)
|
||
|
||
// Conn is a [nettype.PacketConn] that provides batched i/o using
|
||
// platform-specific optimizations, e.g. {recv,send}mmsg & UDP GSO/GRO.
|
||
//
|
||
// Conn does not support single packet reads (see ReadFromUDPAddrPort docs). It
|
||
// is the caller's responsibility to use the appropriate read API where a
|
||
// [nettype.PacketConn] has been upgraded to support batched i/o.
|
||
//
|
||
// Conn originated from (and is still used by) magicsock where its API was
|
||
// strongly influenced by [wireguard-go/conn.Bind] constraints, namely
|
||
// wireguard-go's ownership of packet memory.
|
||
type Conn interface {
|
||
nettype.PacketConn
|
||
// ReadFromUDPAddrPort always returns an error, as UDP GRO is incompatible
|
||
// with single packet reads. A single datagram may be multiple, coalesced
|
||
// datagrams, and this API lacks the ability to pass that context.
|
||
//
|
||
// TODO: consider detaching Conn from [nettype.PacketConn]
|
||
ReadFromUDPAddrPort([]byte) (int, netip.AddrPort, error)
|
||
// ReadBatch reads messages from [Conn] into msgs. It returns the number of
|
||
// messages the caller should evaluate for nonzero len, as a zero len
|
||
// message may fall on either side of a nonzero.
|
||
//
|
||
// Each [ipv6.Message.OOB] must be sized to at least MinControlMessageSize().
|
||
ReadBatch(msgs []ipv6.Message, flags int) (n int, err error)
|
||
// WriteBatchTo writes buffs to addr.
|
||
//
|
||
// If geneve.VNI.IsSet(), then geneve is encoded into the space preceding
|
||
// offset, and offset must equal [packet.GeneveFixedHeaderLength]. If
|
||
// !geneve.VNI.IsSet() then the space preceding offset is ignored.
|
||
//
|
||
// len(buffs) must be <= batchSize supplied in TryUpgradeToConn().
|
||
//
|
||
// WriteBatchTo may return a [neterror.ErrUDPGSODisabled] error if UDP GSO
|
||
// was disabled as a result of a send error.
|
||
WriteBatchTo(buffs [][]byte, addr netip.AddrPort, geneve packet.GeneveHeader, offset int) error
|
||
}
|