This commit is contained in:
2026-02-19 10:07:43 +00:00
parent 007438e372
commit 6e637ecf77
1763 changed files with 60820 additions and 279516 deletions

View File

@@ -22,10 +22,8 @@ import (
"github.com/tailscale/wireguard-go/device"
"github.com/tailscale/wireguard-go/tun"
"go4.org/mem"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"tailscale.com/disco"
tsmetrics "tailscale.com/metrics"
"tailscale.com/net/connstats"
"tailscale.com/feature/buildfeatures"
"tailscale.com/net/packet"
"tailscale.com/net/packet/checksum"
"tailscale.com/net/tsaddr"
@@ -34,7 +32,9 @@ import (
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
"tailscale.com/types/logger"
"tailscale.com/types/netlogfunc"
"tailscale.com/util/clientmetric"
"tailscale.com/util/eventbus"
"tailscale.com/util/usermetric"
"tailscale.com/wgengine/filter"
"tailscale.com/wgengine/netstack/gro"
@@ -204,17 +204,20 @@ type Wrapper struct {
// disableTSMPRejected disables TSMP rejected responses. For tests.
disableTSMPRejected bool
// stats maintains per-connection counters.
stats atomic.Pointer[connstats.Statistics]
// connCounter maintains per-connection counters.
connCounter syncs.AtomicValue[netlogfunc.ConnectionCounter]
captureHook syncs.AtomicValue[packet.CaptureCallback]
metrics *metrics
eventClient *eventbus.Client
discoKeyAdvertisementPub *eventbus.Publisher[DiscoKeyAdvertisement]
}
type metrics struct {
inboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[usermetric.DropLabels]
outboundDroppedPacketsTotal *tsmetrics.MultiLabelMap[usermetric.DropLabels]
inboundDroppedPacketsTotal *usermetric.MultiLabelMap[usermetric.DropLabels]
outboundDroppedPacketsTotal *usermetric.MultiLabelMap[usermetric.DropLabels]
}
func registerMetrics(reg *usermetric.Registry) *metrics {
@@ -228,7 +231,7 @@ func registerMetrics(reg *usermetric.Registry) *metrics {
type tunInjectedRead struct {
// Only one of packet or data should be set, and are read in that order of
// precedence.
packet *stack.PacketBuffer
packet *netstack_PacketBuffer
data []byte
}
@@ -255,15 +258,15 @@ func (w *Wrapper) Start() {
close(w.startCh)
}
func WrapTAP(logf logger.Logf, tdev tun.Device, m *usermetric.Registry) *Wrapper {
return wrap(logf, tdev, true, m)
func WrapTAP(logf logger.Logf, tdev tun.Device, m *usermetric.Registry, bus *eventbus.Bus) *Wrapper {
return wrap(logf, tdev, true, m, bus)
}
func Wrap(logf logger.Logf, tdev tun.Device, m *usermetric.Registry) *Wrapper {
return wrap(logf, tdev, false, m)
func Wrap(logf logger.Logf, tdev tun.Device, m *usermetric.Registry, bus *eventbus.Bus) *Wrapper {
return wrap(logf, tdev, false, m, bus)
}
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool, m *usermetric.Registry) *Wrapper {
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool, m *usermetric.Registry, bus *eventbus.Bus) *Wrapper {
logf = logger.WithPrefix(logf, "tstun: ")
w := &Wrapper{
logf: logf,
@@ -284,6 +287,9 @@ func wrap(logf logger.Logf, tdev tun.Device, isTAP bool, m *usermetric.Registry)
metrics: registerMetrics(m),
}
w.eventClient = bus.Client("net.tstun")
w.discoKeyAdvertisementPub = eventbus.Publish[DiscoKeyAdvertisement](w.eventClient)
w.vectorBuffer = make([][]byte, tdev.BatchSize())
for i := range w.vectorBuffer {
w.vectorBuffer[i] = make([]byte, maxBufferSize)
@@ -312,7 +318,9 @@ func (t *Wrapper) now() time.Time {
//
// The map ownership passes to the Wrapper. It must be non-nil.
func (t *Wrapper) SetDestIPActivityFuncs(m map[netip.Addr]func()) {
t.destIPActivity.Store(m)
if buildfeatures.HasLazyWG {
t.destIPActivity.Store(m)
}
}
// SetDiscoKey sets the current discovery key.
@@ -356,6 +364,7 @@ func (t *Wrapper) Close() error {
close(t.vectorOutbound)
t.outboundMu.Unlock()
err = t.tdev.Close()
t.eventClient.Close()
})
return err
}
@@ -948,12 +957,14 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
for _, data := range res.data {
p.Decode(data[res.dataOffset:])
if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil {
fn()
if buildfeatures.HasLazyWG {
if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil {
fn()
}
}
}
if captHook != nil {
if buildfeatures.HasCapture && captHook != nil {
captHook(packet.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
}
if !t.disableFilter {
@@ -964,6 +975,11 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
continue
}
}
if buildfeatures.HasNetLog {
if update := t.connCounter.Load(); update != nil {
updateConnCounter(update, p.Buffer(), false)
}
}
// Make sure to do SNAT after filtering, so that any flow tracking in
// the filter sees the original source address. See #12133.
@@ -973,9 +989,6 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
panic(fmt.Sprintf("short copy: %d != %d", n, len(data)-res.dataOffset))
}
sizes[buffsPos] = n
if stats := t.stats.Load(); stats != nil {
stats.UpdateTxVirtual(p.Buffer())
}
buffsPos++
}
if buffsGRO != nil {
@@ -998,7 +1011,10 @@ const (
minTCPHeaderSize = 20
)
func stackGSOToTunGSO(pkt []byte, gso stack.GSO) (tun.GSOOptions, error) {
func stackGSOToTunGSO(pkt []byte, gso netstack_GSO) (tun.GSOOptions, error) {
if !buildfeatures.HasNetstack {
panic("unreachable")
}
options := tun.GSOOptions{
CsumStart: gso.L3HdrLen,
CsumOffset: gso.CsumOffset,
@@ -1006,12 +1022,12 @@ func stackGSOToTunGSO(pkt []byte, gso stack.GSO) (tun.GSOOptions, error) {
NeedsCsum: gso.NeedsCsum,
}
switch gso.Type {
case stack.GSONone:
case netstack_GSONone:
options.GSOType = tun.GSONone
return options, nil
case stack.GSOTCPv4:
case netstack_GSOTCPv4:
options.GSOType = tun.GSOTCPv4
case stack.GSOTCPv6:
case netstack_GSOTCPv6:
options.GSOType = tun.GSOTCPv6
default:
return tun.GSOOptions{}, fmt.Errorf("unsupported gVisor GSOType: %v", gso.Type)
@@ -1034,7 +1050,10 @@ func stackGSOToTunGSO(pkt []byte, gso stack.GSO) (tun.GSOOptions, error) {
// both before and after partial checksum updates where later checksum
// offloading still expects a partial checksum.
// TODO(jwhited): plumb partial checksum awareness into net/packet/checksum.
func invertGSOChecksum(pkt []byte, gso stack.GSO) {
func invertGSOChecksum(pkt []byte, gso netstack_GSO) {
if !buildfeatures.HasNetstack {
panic("unreachable")
}
if gso.NeedsCsum != true {
return
}
@@ -1048,10 +1067,13 @@ func invertGSOChecksum(pkt []byte, gso stack.GSO) {
// injectedRead handles injected reads, which bypass filters.
func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []int, offset int) (n int, err error) {
var gso stack.GSO
var gso netstack_GSO
pkt := outBuffs[0][offset:]
if res.packet != nil {
if !buildfeatures.HasNetstack {
panic("unreachable")
}
bufN := copy(pkt, res.packet.NetworkHeader().Slice())
bufN += copy(pkt[bufN:], res.packet.TransportHeader().Slice())
bufN += copy(pkt[bufN:], res.packet.Data().AsRange().ToSlice())
@@ -1074,9 +1096,11 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
pc.snat(p)
invertGSOChecksum(pkt, gso)
if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil {
fn()
if buildfeatures.HasLazyWG {
if m := t.destIPActivity.Load(); m != nil {
if fn := m[p.Dst.Addr()]; fn != nil {
fn()
}
}
}
@@ -1089,9 +1113,11 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
n, err = tun.GSOSplit(pkt, gsoOptions, outBuffs, sizes, offset)
}
if stats := t.stats.Load(); stats != nil {
for i := 0; i < n; i++ {
stats.UpdateTxVirtual(outBuffs[i][offset : offset+sizes[i]])
if buildfeatures.HasNetLog {
if update := t.connCounter.Load(); update != nil {
for i := 0; i < n; i++ {
updateConnCounter(update, outBuffs[i][offset:offset+sizes[i]], false)
}
}
}
@@ -1100,6 +1126,13 @@ func (t *Wrapper) injectedRead(res tunInjectedRead, outBuffs [][]byte, sizes []i
return n, err
}
// DiscoKeyAdvertisement is a TSMP message used for distributing disco keys.
// This struct is used an an event on the [eventbus.Bus].
type DiscoKeyAdvertisement struct {
Src netip.Addr // Src field is populated by the IP header of the packet, not from the payload itself.
Key key.DiscoPublic
}
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook packet.CaptureCallback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
if captHook != nil {
captHook(packet.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
@@ -1110,6 +1143,12 @@ func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook pa
t.noteActivity()
t.injectOutboundPong(p, pingReq)
return filter.DropSilently, gro
} else if discoKeyAdvert, ok := p.AsTSMPDiscoAdvertisement(); ok {
t.discoKeyAdvertisementPub.Publish(DiscoKeyAdvertisement{
Src: discoKeyAdvert.Src,
Key: discoKeyAdvert.Key,
})
return filter.DropSilently, gro
} else if data, ok := p.AsTSMPPong(); ok {
if f := t.OnTSMPPongReceived; f != nil {
f(data)
@@ -1257,9 +1296,11 @@ func (t *Wrapper) Write(buffs [][]byte, offset int) (int, error) {
}
func (t *Wrapper) tdevWrite(buffs [][]byte, offset int) (int, error) {
if stats := t.stats.Load(); stats != nil {
for i := range buffs {
stats.UpdateRxVirtual((buffs)[i][offset:])
if buildfeatures.HasNetLog {
if update := t.connCounter.Load(); update != nil {
for i := range buffs {
updateConnCounter(update, buffs[i][offset:], true)
}
}
}
return t.tdev.Write(buffs, offset)
@@ -1297,7 +1338,10 @@ func (t *Wrapper) SetJailedFilter(filt *filter.Filter) {
//
// This path is typically used to deliver synthesized packets to the
// host networking stack.
func (t *Wrapper) InjectInboundPacketBuffer(pkt *stack.PacketBuffer, buffs [][]byte, sizes []int) error {
func (t *Wrapper) InjectInboundPacketBuffer(pkt *netstack_PacketBuffer, buffs [][]byte, sizes []int) error {
if !buildfeatures.HasNetstack {
panic("unreachable")
}
buf := buffs[0][PacketStartOffset:]
bufN := copy(buf, pkt.NetworkHeader().Slice())
@@ -1436,7 +1480,10 @@ func (t *Wrapper) InjectOutbound(pkt []byte) error {
// InjectOutboundPacketBuffer logically behaves as InjectOutbound. It takes ownership of one
// reference count on the packet, and the packet may be mutated. The packet refcount will be
// decremented after the injected buffer has been read.
func (t *Wrapper) InjectOutboundPacketBuffer(pkt *stack.PacketBuffer) error {
func (t *Wrapper) InjectOutboundPacketBuffer(pkt *netstack_PacketBuffer) error {
if !buildfeatures.HasNetstack {
panic("unreachable")
}
size := pkt.Size()
if size > MaxPacketSize {
pkt.DecRef()
@@ -1472,10 +1519,12 @@ func (t *Wrapper) Unwrap() tun.Device {
return t.tdev
}
// SetStatistics specifies a per-connection statistics aggregator.
// SetConnectionCounter specifies a per-connection statistics aggregator.
// Nil may be specified to disable statistics gathering.
func (t *Wrapper) SetStatistics(stats *connstats.Statistics) {
t.stats.Store(stats)
func (t *Wrapper) SetConnectionCounter(fn netlogfunc.ConnectionCounter) {
if buildfeatures.HasNetLog {
t.connCounter.Store(fn)
}
}
var (
@@ -1491,5 +1540,18 @@ var (
)
func (t *Wrapper) InstallCaptureHook(cb packet.CaptureCallback) {
if !buildfeatures.HasCapture {
return
}
t.captureHook.Store(cb)
}
func updateConnCounter(update netlogfunc.ConnectionCounter, b []byte, receive bool) {
var p packet.Parsed
p.Decode(b)
if receive {
update(p.IPProto, p.Dst, p.Src, 1, len(b), true)
} else {
update(p.IPProto, p.Src, p.Dst, 1, len(b), false)
}
}