Files
tailscale/net/batching/conn.go
James Tucker 0d66cde14f tstest/membudget: add live-heap budget regression tests
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>
2026-08-14 16:45:04 -07:00

75 lines
3.0 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}