mirror of
https://github.com/tailscale/tailscale.git
synced 2025-07-31 16:23:44 +00:00
lanscaping: butcher out gvisor; 16MB
Change-Id: I8185f5bdfe080e33910a8224de66c97f10e2ed40 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
parent
515d9689d5
commit
30d682899b
4
Makefile
4
Makefile
@ -27,8 +27,8 @@ updatedeps: ## Update depaware deps
|
||||
MIN_OMITS ?= ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_netstack
|
||||
|
||||
min:
|
||||
./tool/go build -o $HOME/bin/tailscaled.min -ldflags "-w -s" --tags=${MIN_OMITS} ./cmd/tailscaled
|
||||
ls -lh $HOME/bin/tailscaled.min
|
||||
./tool/go build -o $$HOME/bin/tailscaled.min -ldflags "-w -s" --tags=${MIN_OMITS} ./cmd/tailscaled
|
||||
ls -lh $$HOME/bin/tailscaled.min
|
||||
|
||||
updatemindeps: min
|
||||
PATH="$$(./tool/go env GOROOT)/bin:$$PATH" ./tool/go run github.com/tailscale/depaware --file=depaware-minlinux.txt --goos=linux --tags=${MIN_OMITS} --update \
|
||||
|
@ -18,7 +18,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/go-json-experiment/json/jsontext from github.com/go-json-experiment/json+
|
||||
💣 github.com/godbus/dbus/v5 from tailscale.com/net/dns+
|
||||
github.com/golang/groupcache/lru from tailscale.com/net/dnscache
|
||||
github.com/google/btree from gvisor.dev/gvisor/pkg/tcpip/header
|
||||
github.com/google/nftables from tailscale.com/util/linuxfw
|
||||
💣 github.com/google/nftables/alignedbuff from github.com/google/nftables/xt
|
||||
💣 github.com/google/nftables/binaryutil from github.com/google/nftables+
|
||||
@ -83,31 +82,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
github.com/x448/float16 from github.com/fxamacker/cbor/v2
|
||||
💣 go4.org/mem from tailscale.com/client/tailscale+
|
||||
go4.org/netipx from tailscale.com/ipn/ipnlocal+
|
||||
gvisor.dev/gvisor/pkg/atomicbitops from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/bits from gvisor.dev/gvisor/pkg/buffer
|
||||
💣 gvisor.dev/gvisor/pkg/buffer from gvisor.dev/gvisor/pkg/tcpip+
|
||||
gvisor.dev/gvisor/pkg/context from gvisor.dev/gvisor/pkg/refs
|
||||
💣 gvisor.dev/gvisor/pkg/gohacks from gvisor.dev/gvisor/pkg/state/wire+
|
||||
gvisor.dev/gvisor/pkg/linewriter from gvisor.dev/gvisor/pkg/log
|
||||
gvisor.dev/gvisor/pkg/log from gvisor.dev/gvisor/pkg/context+
|
||||
gvisor.dev/gvisor/pkg/rand from gvisor.dev/gvisor/pkg/tcpip/ports+
|
||||
gvisor.dev/gvisor/pkg/refs from gvisor.dev/gvisor/pkg/buffer+
|
||||
💣 gvisor.dev/gvisor/pkg/state from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
gvisor.dev/gvisor/pkg/state/wire from gvisor.dev/gvisor/pkg/state
|
||||
💣 gvisor.dev/gvisor/pkg/sync from gvisor.dev/gvisor/pkg/atomicbitops+
|
||||
💣 gvisor.dev/gvisor/pkg/sync/locking from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/checksum from gvisor.dev/gvisor/pkg/buffer+
|
||||
gvisor.dev/gvisor/pkg/tcpip/hash/jenkins from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip/header from gvisor.dev/gvisor/pkg/tcpip/header/parse+
|
||||
gvisor.dev/gvisor/pkg/tcpip/header/parse from tailscale.com/wgengine/netstack/gro
|
||||
gvisor.dev/gvisor/pkg/tcpip/internal/tcp from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip/ports from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/tcpip/seqnum from gvisor.dev/gvisor/pkg/tcpip/header+
|
||||
💣 gvisor.dev/gvisor/pkg/tcpip/stack from gvisor.dev/gvisor/pkg/tcpip/header/parse+
|
||||
gvisor.dev/gvisor/pkg/tcpip/stack/gro from tailscale.com/wgengine/netstack/gro
|
||||
gvisor.dev/gvisor/pkg/tcpip/transport/tcpconntrack from gvisor.dev/gvisor/pkg/tcpip/stack
|
||||
gvisor.dev/gvisor/pkg/waiter from gvisor.dev/gvisor/pkg/context+
|
||||
tailscale.com from tailscale.com/version
|
||||
tailscale.com/appc from tailscale.com/ipn/ipnlocal
|
||||
tailscale.com/atomicfile from tailscale.com/ipn+
|
||||
@ -289,7 +263,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/wgengine/filter/filtertype from tailscale.com/types/netmap+
|
||||
💣 tailscale.com/wgengine/magicsock from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/wgengine/netlog from tailscale.com/wgengine
|
||||
tailscale.com/wgengine/netstack/gro from tailscale.com/net/tstun+
|
||||
tailscale.com/wgengine/router from tailscale.com/cmd/tailscaled+
|
||||
tailscale.com/wgengine/wgcfg from tailscale.com/ipn/ipnlocal+
|
||||
tailscale.com/wgengine/wgcfg/nmcfg from tailscale.com/ipn/ipnlocal
|
||||
@ -334,7 +307,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
golang.org/x/text/transform from golang.org/x/text/secure/bidirule+
|
||||
golang.org/x/text/unicode/bidi from golang.org/x/net/idna+
|
||||
golang.org/x/text/unicode/norm from golang.org/x/net/idna
|
||||
golang.org/x/time/rate from gvisor.dev/gvisor/pkg/log+
|
||||
golang.org/x/time/rate from tailscale.com/derp+
|
||||
archive/tar from tailscale.com/clientupdate
|
||||
bufio from compress/flate+
|
||||
bytes from archive/tar+
|
||||
|
70
cmd/tailscaled/netstack.go
Normal file
70
cmd/tailscaled/netstack.go
Normal file
@ -0,0 +1,70 @@
|
||||
//go:build !ts_omit_netstack
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"expvar"
|
||||
"net"
|
||||
"net/netip"
|
||||
|
||||
"tailscale.com/ipn/ipnlocal"
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/logger"
|
||||
"tailscale.com/wgengine/netstack"
|
||||
)
|
||||
|
||||
func newNetstack(logf logger.Logf, sys *tsd.System, onlyNetstack, handleSubnetsInNetstack bool) (start func(localBackend any) error, err error) {
|
||||
ns, err := netstack.Create(logf,
|
||||
sys.Tun.Get(),
|
||||
sys.Engine.Get(),
|
||||
sys.MagicSock.Get(),
|
||||
sys.Dialer.Get(),
|
||||
sys.DNSManager.Get(),
|
||||
sys.ProxyMapper(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Only register debug info if we have a debug mux
|
||||
if debugMux != nil {
|
||||
expvar.Publish("netstack", ns.ExpVar())
|
||||
}
|
||||
sys.Set(ns)
|
||||
ns.ProcessLocalIPs = onlyNetstack
|
||||
ns.ProcessSubnets = onlyNetstack || handleSubnetsInNetstack
|
||||
|
||||
dialer := sys.Dialer.Get()
|
||||
|
||||
if onlyNetstack {
|
||||
e := sys.Engine.Get()
|
||||
dialer.UseNetstackForIP = func(ip netip.Addr) bool {
|
||||
_, ok := e.PeerForIP(ip)
|
||||
return ok
|
||||
}
|
||||
dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
|
||||
// Note: don't just return ns.DialContextTCP or we'll return
|
||||
// *gonet.TCPConn(nil) instead of a nil interface which trips up
|
||||
// callers.
|
||||
tcpConn, err := ns.DialContextTCP(ctx, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return tcpConn, nil
|
||||
}
|
||||
dialer.NetstackDialUDP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) {
|
||||
// Note: don't just return ns.DialContextUDP or we'll return
|
||||
// *gonet.UDPConn(nil) instead of a nil interface which trips up
|
||||
// callers.
|
||||
udpConn, err := ns.DialContextUDP(ctx, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return udpConn, nil
|
||||
}
|
||||
}
|
||||
|
||||
return func(lbAny any) error {
|
||||
return ns.Start(lbAny.(*ipnlocal.LocalBackend))
|
||||
}, nil
|
||||
}
|
12
cmd/tailscaled/netstack_disabled.go
Normal file
12
cmd/tailscaled/netstack_disabled.go
Normal file
@ -0,0 +1,12 @@
|
||||
//go:build ts_omit_netstack
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"tailscale.com/tsd"
|
||||
"tailscale.com/types/logger"
|
||||
)
|
||||
|
||||
func newNetstack(logf logger.Logf, sys *tsd.System, onlyNetstack, handleSubnetsInNetstack bool) (start func(localBackend any) error, err error) {
|
||||
return func(any) error { return nil }, nil
|
||||
}
|
@ -40,7 +40,6 @@ import (
|
||||
"go4.org/mem"
|
||||
"go4.org/netipx"
|
||||
"golang.org/x/net/dns/dnsmessage"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"tailscale.com/appc"
|
||||
"tailscale.com/client/tailscale/apitype"
|
||||
"tailscale.com/clientupdate"
|
||||
@ -4103,58 +4102,6 @@ var (
|
||||
magicDNSIPv6 = tsaddr.TailscaleServiceIPv6()
|
||||
)
|
||||
|
||||
// TCPHandlerForDst returns a TCP handler for connections to dst, or nil if
|
||||
// no handler is needed. It also returns a list of TCP socket options to
|
||||
// apply to the socket before calling the handler.
|
||||
// TCPHandlerForDst is called both for connections to our node's local IP
|
||||
// as well as to the service IP (quad 100).
|
||||
func (b *LocalBackend) TCPHandlerForDst(src, dst netip.AddrPort) (handler func(c net.Conn) error, opts []tcpip.SettableSocketOption) {
|
||||
// First handle internal connections to the service IP
|
||||
hittingServiceIP := dst.Addr() == magicDNSIP || dst.Addr() == magicDNSIPv6
|
||||
if hittingServiceIP {
|
||||
switch dst.Port() {
|
||||
case 80:
|
||||
// TODO(mpminardi): do we want to show an error message if the web client
|
||||
// has been disabled instead of the more "basic" web UI?
|
||||
if b.ShouldRunWebClient() {
|
||||
return b.handleWebClientConn, opts
|
||||
}
|
||||
return b.HandleQuad100Port80Conn, opts
|
||||
case DriveLocalPort:
|
||||
return b.handleDriveConn, opts
|
||||
}
|
||||
}
|
||||
|
||||
// Then handle external connections to the local IP.
|
||||
if !b.isLocalIP(dst.Addr()) {
|
||||
return nil, nil
|
||||
}
|
||||
if dst.Port() == 22 && b.ShouldRunSSH() {
|
||||
// Use a higher keepalive idle time for SSH connections, as they are
|
||||
// typically long lived and idle connections are more likely to be
|
||||
// intentional. Ideally we would turn this off entirely, but we can't
|
||||
// tell the difference between a long lived connection that is idle
|
||||
// vs a connection that is dead because the peer has gone away.
|
||||
// We pick 72h as that is typically sufficient for a long weekend.
|
||||
opts = append(opts, ptr.To(tcpip.KeepaliveIdleOption(72*time.Hour)))
|
||||
return b.handleSSHConn, opts
|
||||
}
|
||||
// TODO(will,sonia): allow customizing web client port ?
|
||||
if dst.Port() == webClientPort && b.ShouldExposeRemoteWebClient() {
|
||||
return b.handleWebClientConn, opts
|
||||
}
|
||||
if port, ok := b.GetPeerAPIPort(dst.Addr()); ok && dst.Port() == port {
|
||||
return func(c net.Conn) error {
|
||||
b.handlePeerAPIConn(src, dst, c)
|
||||
return nil
|
||||
}, opts
|
||||
}
|
||||
if handler := b.tcpHandlerForServe(dst.Port(), src, nil); handler != nil {
|
||||
return handler, opts
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (b *LocalBackend) handleDriveConn(conn net.Conn) error {
|
||||
fs, ok := b.sys.DriveForLocal.GetOK()
|
||||
if !ok || !b.DriveAccessEnabled() {
|
||||
|
@ -15,4 +15,7 @@ Already up to date.
|
||||
bradfitz@bradm4 tailscale.com % go install ./cmd/tailscaled && ls -lh ~/go/bin/tailscaled
|
||||
-rwxr-xr-x@ 1 bradfitz staff 29M Jan 10 19:01 /Users/bradfitz/go/bin/tailscaledbradfitz@bradm4 tailscale.com % go build -o $HOME/bin/tailscaled.min -ldflags "-w -s" --tags=ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion ./cmd/tailscaled
|
||||
bradfitz@bradm4 tailscale.com % ls -lh $HOME/bin/tailscaled.min
|
||||
-rwxr-xr-x@ 1 bradfitz staff 20M Jan 10 19:05 /Users/bradfitz/bin/tailscaled.min
|
||||
-rwxr-xr-x@ 1 bradfitz staff 20M Jan 10 19:05 /Users/bradfitz/bin/tailscaled.minbradfitz@bradm4 tailscale.com % make min
|
||||
./tool/go build -o OME/bin/tailscaled.min -ldflags "-w -s" --tags=ts_omit_aws,ts_omit_bird,ts_omit_tap,ts_omit_kube,ts_omit_completion,ts_omit_netstack ./cmd/tailscaled
|
||||
ls -lh OME/bin/tailscaled.min
|
||||
-rwxr-xr-x@ 1 bradfitz staff 16M Jan 10 20:03 OME/bin/tailscaled.min
|
@ -8,8 +8,6 @@ import (
|
||||
"encoding/binary"
|
||||
"net/netip"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/types/ipproto"
|
||||
)
|
||||
@ -19,27 +17,7 @@ import (
|
||||
// is supported. It panics if provided with an address in a different
|
||||
// family to the parsed packet.
|
||||
func UpdateSrcAddr(q *packet.Parsed, src netip.Addr) {
|
||||
if src.Is6() && q.IPVersion != 6 {
|
||||
panic("UpdateSrcAddr: cannot write IPv6 address to v4 packet")
|
||||
} else if src.Is4() && q.IPVersion != 4 {
|
||||
panic("UpdateSrcAddr: cannot write IPv4 address to v6 packet")
|
||||
}
|
||||
q.CaptureMeta.DidSNAT = true
|
||||
q.CaptureMeta.OriginalSrc = q.Src
|
||||
|
||||
old := q.Src.Addr()
|
||||
q.Src = netip.AddrPortFrom(src, q.Src.Port())
|
||||
|
||||
b := q.Buffer()
|
||||
if src.Is6() {
|
||||
v6 := src.As16()
|
||||
copy(b[8:24], v6[:])
|
||||
updateV6PacketChecksums(q, old, src)
|
||||
} else {
|
||||
v4 := src.As4()
|
||||
copy(b[12:16], v4[:])
|
||||
updateV4PacketChecksums(q, old, src)
|
||||
}
|
||||
panic("lanscaping")
|
||||
}
|
||||
|
||||
// UpdateDstAddr updates the destination address in the packet buffer (e.g. during
|
||||
@ -47,29 +25,15 @@ func UpdateSrcAddr(q *packet.Parsed, src netip.Addr) {
|
||||
// is supported. It panics if provided with an address in a different
|
||||
// family to the parsed packet.
|
||||
func UpdateDstAddr(q *packet.Parsed, dst netip.Addr) {
|
||||
if dst.Is6() && q.IPVersion != 6 {
|
||||
panic("UpdateDstAddr: cannot write IPv6 address to v4 packet")
|
||||
} else if dst.Is4() && q.IPVersion != 4 {
|
||||
panic("UpdateDstAddr: cannot write IPv4 address to v6 packet")
|
||||
}
|
||||
q.CaptureMeta.DidDNAT = true
|
||||
q.CaptureMeta.OriginalDst = q.Dst
|
||||
|
||||
old := q.Dst.Addr()
|
||||
q.Dst = netip.AddrPortFrom(dst, q.Dst.Port())
|
||||
|
||||
b := q.Buffer()
|
||||
if dst.Is6() {
|
||||
v6 := dst.As16()
|
||||
copy(b[24:40], v6[:])
|
||||
updateV6PacketChecksums(q, old, dst)
|
||||
} else {
|
||||
v4 := dst.As4()
|
||||
copy(b[16:20], v4[:])
|
||||
updateV4PacketChecksums(q, old, dst)
|
||||
}
|
||||
panic("lanscaping")
|
||||
}
|
||||
|
||||
const (
|
||||
headerUDPMinimumSize = 8 // header.UDPMinimumSize
|
||||
headerTCPMinimumSize = 20 // header.TCPMinimumSize
|
||||
headerICMPv6MinimumSize = 8 // header.ICMPv6MinimumSize
|
||||
)
|
||||
|
||||
// updateV4PacketChecksums updates the checksums in the packet buffer.
|
||||
// Currently (2023-03-01) only TCP/UDP/ICMP over IPv4 is supported.
|
||||
// p is modified in place.
|
||||
@ -88,13 +52,13 @@ func updateV4PacketChecksums(p *packet.Parsed, old, new netip.Addr) {
|
||||
tr := p.Transport()
|
||||
switch p.IPProto {
|
||||
case ipproto.UDP, ipproto.DCCP:
|
||||
if len(tr) < header.UDPMinimumSize {
|
||||
if len(tr) < headerUDPMinimumSize {
|
||||
// Not enough space for a UDP header.
|
||||
return
|
||||
}
|
||||
updateV4Checksum(tr[6:8], o4[:], n4[:])
|
||||
case ipproto.TCP:
|
||||
if len(tr) < header.TCPMinimumSize {
|
||||
if len(tr) < headerTCPMinimumSize {
|
||||
// Not enough space for a TCP header.
|
||||
return
|
||||
}
|
||||
@ -116,33 +80,7 @@ func updateV4PacketChecksums(p *packet.Parsed, old, new netip.Addr) {
|
||||
// p is modified in place.
|
||||
// If p.IPProto is unknown, no checksums are updated.
|
||||
func updateV6PacketChecksums(p *packet.Parsed, old, new netip.Addr) {
|
||||
if len(p.Buffer()) < 40 {
|
||||
// Not enough space for an IPv6 header.
|
||||
return
|
||||
}
|
||||
o6, n6 := tcpip.AddrFrom16Slice(old.AsSlice()), tcpip.AddrFrom16Slice(new.AsSlice())
|
||||
|
||||
// Now update the transport layer checksums, where applicable.
|
||||
tr := p.Transport()
|
||||
switch p.IPProto {
|
||||
case ipproto.ICMPv6:
|
||||
if len(tr) < header.ICMPv6MinimumSize {
|
||||
return
|
||||
}
|
||||
header.ICMPv6(tr).UpdateChecksumPseudoHeaderAddress(o6, n6)
|
||||
case ipproto.UDP, ipproto.DCCP:
|
||||
if len(tr) < header.UDPMinimumSize {
|
||||
return
|
||||
}
|
||||
header.UDP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
|
||||
case ipproto.TCP:
|
||||
if len(tr) < header.TCPMinimumSize {
|
||||
return
|
||||
}
|
||||
header.TCP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
|
||||
case ipproto.SCTP:
|
||||
// No transport layer update required.
|
||||
}
|
||||
panic("lanscaping")
|
||||
}
|
||||
|
||||
// updateV4Checksum calculates and updates the checksum in the packet buffer for
|
||||
|
@ -22,7 +22,6 @@ 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"
|
||||
@ -38,7 +37,6 @@ import (
|
||||
"tailscale.com/util/usermetric"
|
||||
"tailscale.com/wgengine/capture"
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/netstack/gro"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
)
|
||||
|
||||
@ -77,16 +75,6 @@ var parsedPacketPool = sync.Pool{New: func() any { return new(packet.Parsed) }}
|
||||
// It must not hold onto the packet struct, as its backing storage will be reused.
|
||||
type FilterFunc func(*packet.Parsed, *Wrapper) filter.Response
|
||||
|
||||
// GROFilterFunc is a FilterFunc extended with a *gro.GRO, enabling increased
|
||||
// throughput where GRO is supported by a packet.Parsed interceptor, e.g.
|
||||
// netstack/gVisor, and we are handling a vector of packets. Callers must pass a
|
||||
// nil g for the first packet in a given vector, and continue passing the
|
||||
// returned *gro.GRO for all remaining packets in said vector. If the returned
|
||||
// *gro.GRO is non-nil after the last packet for a given vector is passed
|
||||
// through the GROFilterFunc, the caller must also call Flush() on it to deliver
|
||||
// any previously Enqueue()'d packets.
|
||||
type GROFilterFunc func(p *packet.Parsed, w *Wrapper, g *gro.GRO) (filter.Response, *gro.GRO)
|
||||
|
||||
// Wrapper augments a tun.Device with packet filtering and injection.
|
||||
//
|
||||
// A Wrapper starts in a "corked" mode where Read calls are blocked
|
||||
@ -171,13 +159,7 @@ type Wrapper struct {
|
||||
// PreFilterPacketInboundFromWireGuard is the inbound filter function that runs before the main filter
|
||||
// and therefore sees the packets that may be later dropped by it.
|
||||
PreFilterPacketInboundFromWireGuard FilterFunc
|
||||
// PostFilterPacketInboundFromWireGuard is the inbound filter function that runs after the main filter.
|
||||
PostFilterPacketInboundFromWireGuard GROFilterFunc
|
||||
// PreFilterPacketOutboundToWireGuardNetstackIntercept is a filter function that runs before the main filter
|
||||
// for packets from the local system. This filter is populated by netstack to hook
|
||||
// packets that should be handled by netstack. If set, this filter runs before
|
||||
// PreFilterFromTunToEngine.
|
||||
PreFilterPacketOutboundToWireGuardNetstackIntercept GROFilterFunc
|
||||
|
||||
// PreFilterPacketOutboundToWireGuardEngineIntercept is a filter function that runs before the main filter
|
||||
// for packets from the local system. This filter is populated by wgengine to hook
|
||||
// packets which it handles internally. If both this and PreFilterFromTunToNetstack
|
||||
@ -226,10 +208,7 @@ func registerMetrics(reg *usermetric.Registry) *metrics {
|
||||
|
||||
// tunInjectedRead is an injected packet pretending to be a tun.Read().
|
||||
type tunInjectedRead struct {
|
||||
// Only one of packet or data should be set, and are read in that order of
|
||||
// precedence.
|
||||
packet *stack.PacketBuffer
|
||||
data []byte
|
||||
data []byte
|
||||
}
|
||||
|
||||
// tunVectorReadResult is the result of a tun.Read(), or an injected packet
|
||||
@ -266,10 +245,11 @@ func Wrap(logf logger.Logf, tdev tun.Device, m *usermetric.Registry) *Wrapper {
|
||||
func wrap(logf logger.Logf, tdev tun.Device, isTAP bool, m *usermetric.Registry) *Wrapper {
|
||||
logf = logger.WithPrefix(logf, "tstun: ")
|
||||
w := &Wrapper{
|
||||
logf: logf,
|
||||
limitedLogf: logger.RateLimitedFn(logf, 1*time.Minute, 2, 10),
|
||||
isTAP: isTAP,
|
||||
tdev: tdev,
|
||||
disableFilter: true, // lanscaping
|
||||
logf: logf,
|
||||
limitedLogf: logger.RateLimitedFn(logf, 1*time.Minute, 2, 10),
|
||||
isTAP: isTAP,
|
||||
tdev: tdev,
|
||||
// bufferConsumed is conceptually a condition variable:
|
||||
// a goroutine should not block when setting it, even with no listeners.
|
||||
bufferConsumed: make(chan struct{}, 1),
|
||||
@ -816,81 +796,6 @@ var (
|
||||
magicDNSIPPortv6 = netip.AddrPortFrom(tsaddr.TailscaleServiceIPv6(), 0)
|
||||
)
|
||||
|
||||
func (t *Wrapper) filterPacketOutboundToWireGuard(p *packet.Parsed, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||
// Fake ICMP echo responses to MagicDNS (100.100.100.100).
|
||||
if p.IsEchoRequest() {
|
||||
switch p.Dst {
|
||||
case magicDNSIPPort:
|
||||
header := p.ICMP4Header()
|
||||
header.ToResponse()
|
||||
outp := packet.Generate(&header, p.Payload())
|
||||
t.InjectInboundCopy(outp)
|
||||
return filter.DropSilently, gro // don't pass on to OS; already handled
|
||||
case magicDNSIPPortv6:
|
||||
header := p.ICMP6Header()
|
||||
header.ToResponse()
|
||||
outp := packet.Generate(&header, p.Payload())
|
||||
t.InjectInboundCopy(outp)
|
||||
return filter.DropSilently, gro // don't pass on to OS; already handled
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1526 workaround: if we sent disco packets over
|
||||
// Tailscale from ourselves, then drop them, as that shouldn't
|
||||
// happen unless a networking stack is confused, as it seems
|
||||
// macOS in Network Extension mode might be.
|
||||
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
|
||||
t.isSelfDisco(p) {
|
||||
t.limitedLogf("[unexpected] received self disco out packet over tstun; dropping")
|
||||
metricPacketOutDropSelfDisco.Add(1)
|
||||
return filter.DropSilently, gro
|
||||
}
|
||||
|
||||
if t.PreFilterPacketOutboundToWireGuardNetstackIntercept != nil {
|
||||
var res filter.Response
|
||||
res, gro = t.PreFilterPacketOutboundToWireGuardNetstackIntercept(p, t, gro)
|
||||
if res.IsDrop() {
|
||||
// Handled by netstack.Impl.handleLocalPackets (quad-100 DNS primarily)
|
||||
return res, gro
|
||||
}
|
||||
}
|
||||
if t.PreFilterPacketOutboundToWireGuardEngineIntercept != nil {
|
||||
if res := t.PreFilterPacketOutboundToWireGuardEngineIntercept(p, t); res.IsDrop() {
|
||||
// Handled by userspaceEngine.handleLocalPackets (primarily handles
|
||||
// quad-100 if netstack is not installed).
|
||||
return res, gro
|
||||
}
|
||||
}
|
||||
|
||||
// If the outbound packet is to a jailed peer, use our jailed peer
|
||||
// packet filter.
|
||||
var filt *filter.Filter
|
||||
if pc.outboundPacketIsJailed(p) {
|
||||
filt = t.jailedFilter.Load()
|
||||
} else {
|
||||
filt = t.filter.Load()
|
||||
}
|
||||
if filt == nil {
|
||||
return filter.Drop, gro
|
||||
}
|
||||
|
||||
if filt.RunOut(p, t.filterFlags) != filter.Accept {
|
||||
metricPacketOutDropFilter.Add(1)
|
||||
// TODO(#14280): increment a t.metrics.outboundDroppedPacketsTotal here
|
||||
// once we figure out & document what labels to use for multicast,
|
||||
// link-local-unicast, IP fragments, etc. But they're not
|
||||
// usermetric.ReasonACL.
|
||||
return filter.Drop, gro
|
||||
}
|
||||
|
||||
if t.PostFilterPacketOutboundToWireGuard != nil {
|
||||
if res := t.PostFilterPacketOutboundToWireGuard(p, t); res.IsDrop() {
|
||||
return res, gro
|
||||
}
|
||||
}
|
||||
return filter.Accept, gro
|
||||
}
|
||||
|
||||
// noteActivity records that there was a read or write at the current time.
|
||||
func (t *Wrapper) noteActivity() {
|
||||
t.lastActivityAtomic.StoreAtomic(mono.Now())
|
||||
@ -917,7 +822,7 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
return 0, res.err
|
||||
}
|
||||
if res.data == nil {
|
||||
return t.injectedRead(res.injected, buffs, sizes, offset)
|
||||
panic("unreachable; lanscaping")
|
||||
}
|
||||
|
||||
metricPacketOut.Add(int64(len(res.data)))
|
||||
@ -927,7 +832,6 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
defer parsedPacketPool.Put(p)
|
||||
captHook := t.captureHook.Load()
|
||||
pc := t.peerConfig.Load()
|
||||
var buffsGRO *gro.GRO
|
||||
for _, data := range res.data {
|
||||
p.Decode(data[res.dataOffset:])
|
||||
|
||||
@ -939,14 +843,6 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
if captHook != nil {
|
||||
captHook(capture.FromLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
if !t.disableFilter {
|
||||
var response filter.Response
|
||||
response, buffsGRO = t.filterPacketOutboundToWireGuard(p, pc, buffsGRO)
|
||||
if response != filter.Accept {
|
||||
metricPacketOutDrop.Add(1)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure to do SNAT after filtering, so that any flow tracking in
|
||||
// the filter sees the original source address. See #12133.
|
||||
@ -961,9 +857,6 @@ func (t *Wrapper) Read(buffs [][]byte, sizes []int, offset int) (int, error) {
|
||||
}
|
||||
buffsPos++
|
||||
}
|
||||
if buffsGRO != nil {
|
||||
buffsGRO.Flush()
|
||||
}
|
||||
|
||||
// t.vectorBuffer has a fixed location in memory.
|
||||
// TODO(raggi): add an explicit field and possibly method to the tunVectorReadResult
|
||||
@ -981,214 +874,6 @@ const (
|
||||
minTCPHeaderSize = 20
|
||||
)
|
||||
|
||||
func stackGSOToTunGSO(pkt []byte, gso stack.GSO) (tun.GSOOptions, error) {
|
||||
options := tun.GSOOptions{
|
||||
CsumStart: gso.L3HdrLen,
|
||||
CsumOffset: gso.CsumOffset,
|
||||
GSOSize: gso.MSS,
|
||||
NeedsCsum: gso.NeedsCsum,
|
||||
}
|
||||
switch gso.Type {
|
||||
case stack.GSONone:
|
||||
options.GSOType = tun.GSONone
|
||||
return options, nil
|
||||
case stack.GSOTCPv4:
|
||||
options.GSOType = tun.GSOTCPv4
|
||||
case stack.GSOTCPv6:
|
||||
options.GSOType = tun.GSOTCPv6
|
||||
default:
|
||||
return tun.GSOOptions{}, fmt.Errorf("unsupported gVisor GSOType: %v", gso.Type)
|
||||
}
|
||||
// options.HdrLen is both layer 3 and 4 together, whereas gVisor only
|
||||
// gives us layer 3 length. We have to gather TCP header length
|
||||
// ourselves.
|
||||
if len(pkt) < int(gso.L3HdrLen)+minTCPHeaderSize {
|
||||
return tun.GSOOptions{}, errors.New("gVisor GSOTCP packet length too short")
|
||||
}
|
||||
tcphLen := uint16(pkt[int(gso.L3HdrLen)+12] >> 4 * 4)
|
||||
options.HdrLen = gso.L3HdrLen + tcphLen
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// invertGSOChecksum inverts the transport layer checksum in pkt if gVisor
|
||||
// handed us a segment with a partial checksum. A partial checksum is not a
|
||||
// ones' complement of the sum, and incremental checksum updating is not yet
|
||||
// partial checksum aware. This may be called twice for a single packet,
|
||||
// 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) {
|
||||
if gso.NeedsCsum != true {
|
||||
return
|
||||
}
|
||||
at := int(gso.L3HdrLen + gso.CsumOffset)
|
||||
if at+1 > len(pkt)-1 {
|
||||
return
|
||||
}
|
||||
pkt[at] = ^pkt[at]
|
||||
pkt[at+1] = ^pkt[at+1]
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
pkt := outBuffs[0][offset:]
|
||||
if res.packet != nil {
|
||||
bufN := copy(pkt, res.packet.NetworkHeader().Slice())
|
||||
bufN += copy(pkt[bufN:], res.packet.TransportHeader().Slice())
|
||||
bufN += copy(pkt[bufN:], res.packet.Data().AsRange().ToSlice())
|
||||
gso = res.packet.GSOOptions
|
||||
pkt = pkt[:bufN]
|
||||
defer res.packet.DecRef() // defer DecRef so we may continue to reference it
|
||||
} else {
|
||||
sizes[0] = copy(pkt, res.data)
|
||||
pkt = pkt[:sizes[0]]
|
||||
n = 1
|
||||
}
|
||||
|
||||
pc := t.peerConfig.Load()
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(pkt)
|
||||
|
||||
invertGSOChecksum(pkt, gso)
|
||||
pc.snat(p)
|
||||
invertGSOChecksum(pkt, gso)
|
||||
|
||||
if m := t.destIPActivity.Load(); m != nil {
|
||||
if fn := m[p.Dst.Addr()]; fn != nil {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
if res.packet != nil {
|
||||
var gsoOptions tun.GSOOptions
|
||||
gsoOptions, err = stackGSOToTunGSO(pkt, gso)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
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]])
|
||||
}
|
||||
}
|
||||
|
||||
t.noteActivity()
|
||||
metricPacketOut.Add(int64(n))
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (t *Wrapper) filterPacketInboundFromWireGuard(p *packet.Parsed, captHook capture.Callback, pc *peerConfigTable, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||
if captHook != nil {
|
||||
captHook(capture.FromPeer, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
|
||||
if p.IPProto == ipproto.TSMP {
|
||||
if pingReq, ok := p.AsTSMPPing(); ok {
|
||||
t.noteActivity()
|
||||
t.injectOutboundPong(p, pingReq)
|
||||
return filter.DropSilently, gro
|
||||
} else if data, ok := p.AsTSMPPong(); ok {
|
||||
if f := t.OnTSMPPongReceived; f != nil {
|
||||
f(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if p.IsEchoResponse() {
|
||||
if f := t.OnICMPEchoResponseReceived; f != nil && f(p) {
|
||||
// Note: this looks dropped in metrics, even though it was
|
||||
// handled internally.
|
||||
return filter.DropSilently, gro
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1526 workaround: if we see disco packets over
|
||||
// Tailscale from ourselves, then drop them, as that shouldn't
|
||||
// happen unless a networking stack is confused, as it seems
|
||||
// macOS in Network Extension mode might be.
|
||||
if p.IPProto == ipproto.UDP && // disco is over UDP; avoid isSelfDisco call for TCP/etc
|
||||
t.isSelfDisco(p) {
|
||||
t.limitedLogf("[unexpected] received self disco in packet over tstun; dropping")
|
||||
metricPacketInDropSelfDisco.Add(1)
|
||||
return filter.DropSilently, gro
|
||||
}
|
||||
|
||||
if t.PreFilterPacketInboundFromWireGuard != nil {
|
||||
if res := t.PreFilterPacketInboundFromWireGuard(p, t); res.IsDrop() {
|
||||
return res, gro
|
||||
}
|
||||
}
|
||||
|
||||
var filt *filter.Filter
|
||||
if pc.inboundPacketIsJailed(p) {
|
||||
filt = t.jailedFilter.Load()
|
||||
} else {
|
||||
filt = t.filter.Load()
|
||||
}
|
||||
if filt == nil {
|
||||
return filter.Drop, gro
|
||||
}
|
||||
outcome := filt.RunIn(p, t.filterFlags)
|
||||
|
||||
// Let peerapi through the filter; its ACLs are handled at L7,
|
||||
// not at the packet level.
|
||||
if outcome != filter.Accept &&
|
||||
p.IPProto == ipproto.TCP &&
|
||||
p.TCPFlags&packet.TCPSyn != 0 &&
|
||||
t.PeerAPIPort != nil {
|
||||
if port, ok := t.PeerAPIPort(p.Dst.Addr()); ok && port == p.Dst.Port() {
|
||||
outcome = filter.Accept
|
||||
}
|
||||
}
|
||||
|
||||
if outcome != filter.Accept {
|
||||
metricPacketInDropFilter.Add(1)
|
||||
t.metrics.inboundDroppedPacketsTotal.Add(usermetric.DropLabels{
|
||||
Reason: usermetric.ReasonACL,
|
||||
}, 1)
|
||||
|
||||
// Tell them, via TSMP, we're dropping them due to the ACL.
|
||||
// Their host networking stack can translate this into ICMP
|
||||
// or whatnot as required. But notably, their GUI or tailscale CLI
|
||||
// can show them a rejection history with reasons.
|
||||
if p.IPVersion == 4 && p.IPProto == ipproto.TCP && p.TCPFlags&packet.TCPSyn != 0 && !t.disableTSMPRejected {
|
||||
rj := packet.TailscaleRejectedHeader{
|
||||
IPSrc: p.Dst.Addr(),
|
||||
IPDst: p.Src.Addr(),
|
||||
Src: p.Src,
|
||||
Dst: p.Dst,
|
||||
Proto: p.IPProto,
|
||||
Reason: packet.RejectedDueToACLs,
|
||||
}
|
||||
if filt.ShieldsUp() {
|
||||
rj.Reason = packet.RejectedDueToShieldsUp
|
||||
}
|
||||
pkt := packet.Generate(rj, nil)
|
||||
t.InjectOutbound(pkt)
|
||||
|
||||
// TODO(bradfitz): also send a TCP RST, after the TSMP message.
|
||||
}
|
||||
|
||||
return filter.Drop, gro
|
||||
}
|
||||
|
||||
if t.PostFilterPacketInboundFromWireGuard != nil {
|
||||
var res filter.Response
|
||||
res, gro = t.PostFilterPacketInboundFromWireGuard(p, t, gro)
|
||||
if res.IsDrop() {
|
||||
return res, gro
|
||||
}
|
||||
}
|
||||
|
||||
return filter.Accept, gro
|
||||
}
|
||||
|
||||
// Write accepts incoming packets. The packets begin at buffs[:][offset:],
|
||||
// like wireguard-go/tun.Device.Write. Write is called per-peer via
|
||||
// wireguard-go/device.Peer.RoutineSequentialReceiver, so it MUST be
|
||||
@ -1198,28 +883,10 @@ func (t *Wrapper) Write(buffs [][]byte, offset int) (int, error) {
|
||||
i := 0
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
captHook := t.captureHook.Load()
|
||||
pc := t.peerConfig.Load()
|
||||
var buffsGRO *gro.GRO
|
||||
for _, buff := range buffs {
|
||||
p.Decode(buff[offset:])
|
||||
pc.dnat(p)
|
||||
if !t.disableFilter {
|
||||
var res filter.Response
|
||||
// TODO(jwhited): name and document this filter code path
|
||||
// appropriately. It is not only responsible for filtering, it
|
||||
// also routes packets towards gVisor/netstack.
|
||||
res, buffsGRO = t.filterPacketInboundFromWireGuard(p, captHook, pc, buffsGRO)
|
||||
if res != filter.Accept {
|
||||
metricPacketInDrop.Add(1)
|
||||
} else {
|
||||
buffs[i] = buff
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
if buffsGRO != nil {
|
||||
buffsGRO.Flush()
|
||||
}
|
||||
if t.disableFilter {
|
||||
i = len(buffs)
|
||||
@ -1264,76 +931,6 @@ func (t *Wrapper) SetJailedFilter(filt *filter.Filter) {
|
||||
t.jailedFilter.Store(filt)
|
||||
}
|
||||
|
||||
// InjectInboundPacketBuffer makes the Wrapper device behave as if a packet
|
||||
// (pkt) with the given contents was received from the network.
|
||||
// It takes ownership of one reference count on pkt. The injected
|
||||
// packet will not pass through inbound filters.
|
||||
//
|
||||
// pkt will be copied into buffs before writing to the underlying tun.Device.
|
||||
// Therefore, callers must allocate and pass a buffs slice that is sized
|
||||
// appropriately for holding pkt.Size() + PacketStartOffset as a single
|
||||
// element (buffs[0]) and split across multiple elements if the originating
|
||||
// stack supports GSO. sizes must be sized with similar consideration,
|
||||
// len(buffs) should be equal to len(sizes). If any len(buffs[<index>]) was
|
||||
// mutated by InjectInboundPacketBuffer it will be reset to cap(buffs[<index>])
|
||||
// before returning.
|
||||
//
|
||||
// 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 {
|
||||
buf := buffs[0][PacketStartOffset:]
|
||||
|
||||
bufN := copy(buf, pkt.NetworkHeader().Slice())
|
||||
bufN += copy(buf[bufN:], pkt.TransportHeader().Slice())
|
||||
bufN += copy(buf[bufN:], pkt.Data().AsRange().ToSlice())
|
||||
if bufN != pkt.Size() {
|
||||
panic("unexpected packet size after copy")
|
||||
}
|
||||
buf = buf[:bufN]
|
||||
defer pkt.DecRef()
|
||||
|
||||
pc := t.peerConfig.Load()
|
||||
|
||||
p := parsedPacketPool.Get().(*packet.Parsed)
|
||||
defer parsedPacketPool.Put(p)
|
||||
p.Decode(buf)
|
||||
captHook := t.captureHook.Load()
|
||||
if captHook != nil {
|
||||
captHook(capture.SynthesizedToLocal, t.now(), p.Buffer(), p.CaptureMeta)
|
||||
}
|
||||
|
||||
invertGSOChecksum(buf, pkt.GSOOptions)
|
||||
pc.dnat(p)
|
||||
invertGSOChecksum(buf, pkt.GSOOptions)
|
||||
|
||||
gso, err := stackGSOToTunGSO(buf, pkt.GSOOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(jwhited): support GSO passthrough to t.tdev. If t.tdev supports
|
||||
// GSO we don't need to split here and coalesce inside wireguard-go,
|
||||
// we can pass a coalesced segment all the way through.
|
||||
n, err := tun.GSOSplit(buf, gso, buffs, sizes, PacketStartOffset)
|
||||
if err != nil {
|
||||
if errors.Is(err, tun.ErrTooManySegments) {
|
||||
t.limitedLogf("InjectInboundPacketBuffer: GSO split overflows buffs")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
buffs[i] = buffs[i][:PacketStartOffset+sizes[i]]
|
||||
}
|
||||
defer func() {
|
||||
for i := 0; i < n; i++ {
|
||||
buffs[i] = buffs[i][:cap(buffs[i])]
|
||||
}
|
||||
}()
|
||||
_, err = t.tdevWrite(buffs[:n], PacketStartOffset)
|
||||
return err
|
||||
}
|
||||
|
||||
// InjectInboundDirect makes the Wrapper device behave as if a packet
|
||||
// with the given contents was received from the network.
|
||||
// It blocks and does not take ownership of the packet.
|
||||
@ -1358,25 +955,6 @@ func (t *Wrapper) InjectInboundDirect(buf []byte, offset int) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// InjectInboundCopy takes a packet without leading space,
|
||||
// reallocates it to conform to the InjectInboundDirect interface
|
||||
// and calls InjectInboundDirect on it. Injecting a nil packet is a no-op.
|
||||
func (t *Wrapper) InjectInboundCopy(packet []byte) error {
|
||||
// We duplicate this check from InjectInboundDirect here
|
||||
// to avoid wasting an allocation on an oversized packet.
|
||||
if len(packet) > MaxPacketSize {
|
||||
return errPacketTooBig
|
||||
}
|
||||
if len(packet) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
buf := make([]byte, PacketStartOffset+len(packet))
|
||||
copy(buf[PacketStartOffset:], packet)
|
||||
|
||||
return t.InjectInboundDirect(buf, PacketStartOffset)
|
||||
}
|
||||
|
||||
func (t *Wrapper) injectOutboundPong(pp *packet.Parsed, req packet.TSMPPingRequest) {
|
||||
pong := packet.TSMPPongReply{
|
||||
Data: req.Data,
|
||||
@ -1416,28 +994,6 @@ func (t *Wrapper) InjectOutbound(pkt []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
size := pkt.Size()
|
||||
if size > MaxPacketSize {
|
||||
pkt.DecRef()
|
||||
return errPacketTooBig
|
||||
}
|
||||
if size == 0 {
|
||||
pkt.DecRef()
|
||||
return nil
|
||||
}
|
||||
if capt := t.captureHook.Load(); capt != nil {
|
||||
b := pkt.ToBuffer()
|
||||
capt(capture.SynthesizedToPeer, t.now(), b.Flatten(), packet.CaptureMeta{})
|
||||
}
|
||||
|
||||
t.injectOutbound(tunInjectedRead{packet: pkt})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Wrapper) BatchSize() int {
|
||||
if runtime.GOOS == "linux" {
|
||||
// Always setup Linux to handle vectors, even in the very rare case that
|
||||
|
@ -4,17 +4,7 @@
|
||||
package tstun
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/netip"
|
||||
"runtime"
|
||||
|
||||
"github.com/tailscale/wireguard-go/tun"
|
||||
"golang.org/x/sys/unix"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checksum"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"tailscale.com/envknob"
|
||||
"tailscale.com/net/tsaddr"
|
||||
)
|
||||
|
||||
// SetLinkFeaturesPostUp configures link features on t based on select TS_TUN_
|
||||
@ -24,59 +14,4 @@ func (t *Wrapper) SetLinkFeaturesPostUp() {
|
||||
if t.isTAP || runtime.GOOS == "android" {
|
||||
return
|
||||
}
|
||||
if groDev, ok := t.tdev.(tun.GRODevice); ok {
|
||||
if envknob.Bool("TS_TUN_DISABLE_UDP_GRO") {
|
||||
groDev.DisableUDPGRO()
|
||||
}
|
||||
if envknob.Bool("TS_TUN_DISABLE_TCP_GRO") {
|
||||
groDev.DisableTCPGRO()
|
||||
}
|
||||
err := probeTCPGRO(groDev)
|
||||
if errors.Is(err, unix.EINVAL) {
|
||||
groDev.DisableTCPGRO()
|
||||
groDev.DisableUDPGRO()
|
||||
t.logf("disabled TUN TCP & UDP GRO due to GRO probe error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func probeTCPGRO(dev tun.GRODevice) error {
|
||||
ipPort := netip.MustParseAddrPort(tsaddr.TailscaleServiceIPString + ":0")
|
||||
fingerprint := []byte("tailscale-probe-tun-gro")
|
||||
segmentSize := len(fingerprint)
|
||||
iphLen := 20
|
||||
tcphLen := 20
|
||||
totalLen := iphLen + tcphLen + segmentSize
|
||||
ipAs4 := ipPort.Addr().As4()
|
||||
bufs := make([][]byte, 2)
|
||||
for i := range bufs {
|
||||
bufs[i] = make([]byte, PacketStartOffset+totalLen, PacketStartOffset+(totalLen*2))
|
||||
ipv4H := header.IPv4(bufs[i][PacketStartOffset:])
|
||||
ipv4H.Encode(&header.IPv4Fields{
|
||||
SrcAddr: tcpip.AddrFromSlice(ipAs4[:]),
|
||||
DstAddr: tcpip.AddrFromSlice(ipAs4[:]),
|
||||
Protocol: unix.IPPROTO_TCP,
|
||||
// Use a zero value TTL as best effort means to reduce chance of
|
||||
// probe packet leaking further than it needs to.
|
||||
TTL: 0,
|
||||
TotalLength: uint16(totalLen),
|
||||
})
|
||||
tcpH := header.TCP(bufs[i][PacketStartOffset+iphLen:])
|
||||
tcpH.Encode(&header.TCPFields{
|
||||
SrcPort: ipPort.Port(),
|
||||
DstPort: ipPort.Port(),
|
||||
SeqNum: 1 + uint32(i*segmentSize),
|
||||
AckNum: 1,
|
||||
DataOffset: 20,
|
||||
Flags: header.TCPFlagAck,
|
||||
WindowSize: 3000,
|
||||
})
|
||||
copy(bufs[i][PacketStartOffset+iphLen+tcphLen:], fingerprint)
|
||||
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
||||
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(tcphLen+segmentSize))
|
||||
pseudoCsum = checksum.Checksum(bufs[i][PacketStartOffset+iphLen+tcphLen:], pseudoCsum)
|
||||
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
||||
}
|
||||
_, err := dev.Write(bufs, PacketStartOffset)
|
||||
return err
|
||||
}
|
||||
|
@ -22,13 +22,10 @@ import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/tailscale/wireguard-go/tun/tuntest"
|
||||
"go4.org/mem"
|
||||
"go4.org/netipx"
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
"tailscale.com/disco"
|
||||
"tailscale.com/net/connstats"
|
||||
"tailscale.com/net/netaddr"
|
||||
"tailscale.com/net/packet"
|
||||
"tailscale.com/tstest"
|
||||
"tailscale.com/tstime/mono"
|
||||
@ -521,124 +518,6 @@ func TestAtomic64Alignment(t *testing.T) {
|
||||
c.lastActivityAtomic.StoreAtomic(mono.Now())
|
||||
}
|
||||
|
||||
func TestPeerAPIBypass(t *testing.T) {
|
||||
reg := new(usermetric.Registry)
|
||||
wrapperWithPeerAPI := &Wrapper{
|
||||
PeerAPIPort: func(ip netip.Addr) (port uint16, ok bool) {
|
||||
if ip == netip.MustParseAddr("100.64.1.2") {
|
||||
return 60000, true
|
||||
}
|
||||
return
|
||||
},
|
||||
metrics: registerMetrics(reg),
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
w *Wrapper
|
||||
filter *filter.Filter
|
||||
pkt []byte
|
||||
want filter.Response
|
||||
}{
|
||||
{
|
||||
name: "reject_nil_filter",
|
||||
w: &Wrapper{
|
||||
PeerAPIPort: func(netip.Addr) (port uint16, ok bool) {
|
||||
return 60000, true
|
||||
},
|
||||
metrics: registerMetrics(reg),
|
||||
},
|
||||
pkt: tcp4syn("1.2.3.4", "100.64.1.2", 1234, 60000),
|
||||
want: filter.Drop,
|
||||
},
|
||||
{
|
||||
name: "reject_with_filter",
|
||||
w: &Wrapper{
|
||||
metrics: registerMetrics(reg),
|
||||
},
|
||||
filter: filter.NewAllowNone(logger.Discard, new(netipx.IPSet)),
|
||||
pkt: tcp4syn("1.2.3.4", "100.64.1.2", 1234, 60000),
|
||||
want: filter.Drop,
|
||||
},
|
||||
{
|
||||
name: "peerapi_bypass_filter",
|
||||
w: wrapperWithPeerAPI,
|
||||
filter: filter.NewAllowNone(logger.Discard, new(netipx.IPSet)),
|
||||
pkt: tcp4syn("1.2.3.4", "100.64.1.2", 1234, 60000),
|
||||
want: filter.Accept,
|
||||
},
|
||||
{
|
||||
name: "peerapi_dont_bypass_filter_wrong_port",
|
||||
w: wrapperWithPeerAPI,
|
||||
filter: filter.NewAllowNone(logger.Discard, new(netipx.IPSet)),
|
||||
pkt: tcp4syn("1.2.3.4", "100.64.1.2", 1234, 60001),
|
||||
want: filter.Drop,
|
||||
},
|
||||
{
|
||||
name: "peerapi_dont_bypass_filter_wrong_dst_ip",
|
||||
w: wrapperWithPeerAPI,
|
||||
filter: filter.NewAllowNone(logger.Discard, new(netipx.IPSet)),
|
||||
pkt: tcp4syn("1.2.3.4", "100.64.1.3", 1234, 60000),
|
||||
want: filter.Drop,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := new(packet.Parsed)
|
||||
p.Decode(tt.pkt)
|
||||
tt.w.SetFilter(tt.filter)
|
||||
tt.w.disableTSMPRejected = true
|
||||
tt.w.logf = t.Logf
|
||||
if got, _ := tt.w.filterPacketInboundFromWireGuard(p, nil, nil, nil); got != tt.want {
|
||||
t.Errorf("got = %v; want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1526: drop disco frames from ourselves.
|
||||
func TestFilterDiscoLoop(t *testing.T) {
|
||||
var memLog tstest.MemLogger
|
||||
discoPub := key.DiscoPublicFromRaw32(mem.B([]byte{1: 1, 2: 2, 31: 0}))
|
||||
tw := &Wrapper{logf: memLog.Logf, limitedLogf: memLog.Logf}
|
||||
tw.SetDiscoKey(discoPub)
|
||||
uh := packet.UDP4Header{
|
||||
IP4Header: packet.IP4Header{
|
||||
IPProto: ipproto.UDP,
|
||||
Src: netaddr.IPv4(1, 2, 3, 4),
|
||||
Dst: netaddr.IPv4(5, 6, 7, 8),
|
||||
},
|
||||
SrcPort: 9,
|
||||
DstPort: 10,
|
||||
}
|
||||
discobs := discoPub.Raw32()
|
||||
discoPayload := fmt.Sprintf("%s%s%s", disco.Magic, discobs[:], [disco.NonceLen]byte{})
|
||||
pkt := make([]byte, uh.Len()+len(discoPayload))
|
||||
uh.Marshal(pkt)
|
||||
copy(pkt[uh.Len():], discoPayload)
|
||||
|
||||
p := new(packet.Parsed)
|
||||
p.Decode(pkt)
|
||||
got, _ := tw.filterPacketInboundFromWireGuard(p, nil, nil, nil)
|
||||
if got != filter.DropSilently {
|
||||
t.Errorf("got %v; want DropSilently", got)
|
||||
}
|
||||
if got, want := memLog.String(), "[unexpected] received self disco in packet over tstun; dropping\n"; got != want {
|
||||
t.Errorf("log output mismatch\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
|
||||
memLog.Reset()
|
||||
pp := new(packet.Parsed)
|
||||
pp.Decode(pkt)
|
||||
got, _ = tw.filterPacketOutboundToWireGuard(pp, nil, nil)
|
||||
if got != filter.DropSilently {
|
||||
t.Errorf("got %v; want DropSilently", got)
|
||||
}
|
||||
if got, want := memLog.String(), "[unexpected] received self disco out packet over tstun; dropping\n"; got != want {
|
||||
t.Errorf("log output mismatch\n got: %q\nwant: %q\n", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(andrew-d): refactor this test to no longer use addrFam, after #11945
|
||||
// removed it in peerConfigFromWGConfig
|
||||
func TestPeerCfg_NAT(t *testing.T) {
|
||||
|
@ -1,76 +0,0 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios
|
||||
|
||||
package gro
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
nsgro "gvisor.dev/gvisor/pkg/tcpip/stack/gro"
|
||||
"tailscale.com/net/packet"
|
||||
)
|
||||
|
||||
var (
|
||||
groPool sync.Pool
|
||||
)
|
||||
|
||||
func init() {
|
||||
groPool.New = func() any {
|
||||
g := &GRO{}
|
||||
g.gro.Init(true)
|
||||
return g
|
||||
}
|
||||
}
|
||||
|
||||
// GRO coalesces incoming packets to increase throughput. It is NOT thread-safe.
|
||||
type GRO struct {
|
||||
gro nsgro.GRO
|
||||
maybeEnqueued bool
|
||||
}
|
||||
|
||||
// NewGRO returns a new instance of *GRO from a sync.Pool. It can be returned to
|
||||
// the pool with GRO.Flush().
|
||||
func NewGRO() *GRO {
|
||||
return groPool.Get().(*GRO)
|
||||
}
|
||||
|
||||
// SetDispatcher sets the underlying stack.NetworkDispatcher where packets are
|
||||
// delivered.
|
||||
func (g *GRO) SetDispatcher(d stack.NetworkDispatcher) {
|
||||
g.gro.Dispatcher = d
|
||||
}
|
||||
|
||||
// Enqueue enqueues the provided packet for GRO. It may immediately deliver
|
||||
// it to the underlying stack.NetworkDispatcher depending on its contents. To
|
||||
// explicitly flush previously enqueued packets see Flush().
|
||||
func (g *GRO) Enqueue(p *packet.Parsed) {
|
||||
if g.gro.Dispatcher == nil {
|
||||
return
|
||||
}
|
||||
pkt := RXChecksumOffload(p)
|
||||
if pkt == nil {
|
||||
return
|
||||
}
|
||||
// TODO(jwhited): g.gro.Enqueue() duplicates a lot of p.Decode().
|
||||
// We may want to push stack.PacketBuffer further up as a
|
||||
// replacement for packet.Parsed, or inversely push packet.Parsed
|
||||
// down into refactored GRO logic.
|
||||
g.gro.Enqueue(pkt)
|
||||
g.maybeEnqueued = true
|
||||
pkt.DecRef()
|
||||
}
|
||||
|
||||
// Flush flushes previously enqueued packets to the underlying
|
||||
// stack.NetworkDispatcher, and returns GRO to a pool for later re-use. Callers
|
||||
// MUST NOT use GRO once it has been Flush()'d.
|
||||
func (g *GRO) Flush() {
|
||||
if g.gro.Dispatcher != nil && g.maybeEnqueued {
|
||||
g.gro.Flush()
|
||||
}
|
||||
g.gro.Dispatcher = nil
|
||||
g.maybeEnqueued = false
|
||||
groPool.Put(g)
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios
|
||||
|
||||
package gro
|
||||
|
||||
import (
|
||||
@ -13,7 +11,7 @@ import (
|
||||
type GRO struct{}
|
||||
|
||||
func NewGRO() *GRO {
|
||||
panic("unsupported on iOS")
|
||||
panic("unsupported; removed")
|
||||
}
|
||||
|
||||
func (g *GRO) SetDispatcher(_ stack.NetworkDispatcher) {}
|
||||
|
@ -55,7 +55,6 @@ import (
|
||||
"tailscale.com/wgengine/filter"
|
||||
"tailscale.com/wgengine/magicsock"
|
||||
"tailscale.com/wgengine/netlog"
|
||||
"tailscale.com/wgengine/netstack/gro"
|
||||
"tailscale.com/wgengine/router"
|
||||
"tailscale.com/wgengine/wgcfg"
|
||||
"tailscale.com/wgengine/wgint"
|
||||
@ -415,7 +414,7 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
tsTUNDev.SetDiscoKey(e.magicConn.DiscoPublicKey())
|
||||
|
||||
if conf.RespondToPing {
|
||||
e.tundev.PostFilterPacketInboundFromWireGuard = echoRespondToAll
|
||||
// lanscaping
|
||||
}
|
||||
e.tundev.PreFilterPacketOutboundToWireGuardEngineIntercept = e.handleLocalPackets
|
||||
|
||||
@ -532,44 +531,11 @@ func NewUserspaceEngine(logf logger.Logf, conf Config) (_ Engine, reterr error)
|
||||
return e, nil
|
||||
}
|
||||
|
||||
// echoRespondToAll is an inbound post-filter responding to all echo requests.
|
||||
func echoRespondToAll(p *packet.Parsed, t *tstun.Wrapper, gro *gro.GRO) (filter.Response, *gro.GRO) {
|
||||
if p.IsEchoRequest() {
|
||||
header := p.ICMP4Header()
|
||||
header.ToResponse()
|
||||
outp := packet.Generate(&header, p.Payload())
|
||||
t.InjectOutbound(outp)
|
||||
// We already responded to it, but it's not an error.
|
||||
// Proceed with regular delivery. (Since this code is only
|
||||
// used in fake mode, regular delivery just means throwing
|
||||
// it away. If this ever gets run in non-fake mode, you'll
|
||||
// get double responses to pings, which is an indicator you
|
||||
// shouldn't be doing that I guess.)
|
||||
return filter.Accept, gro
|
||||
}
|
||||
return filter.Accept, gro
|
||||
}
|
||||
|
||||
// handleLocalPackets inspects packets coming from the local network
|
||||
// stack, and intercepts any packets that should be handled by
|
||||
// tailscaled directly. Other packets are allowed to proceed into the
|
||||
// main ACL filter.
|
||||
func (e *userspaceEngine) handleLocalPackets(p *packet.Parsed, t *tstun.Wrapper) filter.Response {
|
||||
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
|
||||
isLocalAddr, ok := e.isLocalAddr.LoadOk()
|
||||
if !ok {
|
||||
e.logf("[unexpected] e.isLocalAddr was nil, can't check for loopback packet")
|
||||
} else if isLocalAddr(p.Dst.Addr()) {
|
||||
// macOS NetworkExtension directs packets destined to the
|
||||
// tunnel's local IP address into the tunnel, instead of
|
||||
// looping back within the kernel network stack. We have to
|
||||
// notice that an outbound packet is actually destined for
|
||||
// ourselves, and loop it back into macOS.
|
||||
t.InjectInboundCopy(p.Buffer())
|
||||
metricReflectToOS.Add(1)
|
||||
return filter.Drop
|
||||
}
|
||||
}
|
||||
|
||||
return filter.Accept
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user