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

@@ -33,11 +33,13 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
"gvisor.dev/gvisor/pkg/waiter"
"tailscale.com/envknob"
"tailscale.com/feature/buildfeatures"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/metrics"
"tailscale.com/net/dns"
"tailscale.com/net/ipset"
"tailscale.com/net/netaddr"
"tailscale.com/net/netx"
"tailscale.com/net/packet"
"tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
@@ -208,7 +210,7 @@ type Impl struct {
// TCP connection to another host (e.g. in subnet router mode).
//
// This is currently only used in tests.
forwardDialFunc func(context.Context, string, string) (net.Conn, error)
forwardDialFunc netx.DialFunc
// forwardInFlightPerClientDropped is a metric that tracks how many
// in-flight TCP forward requests were dropped due to the per-client
@@ -326,10 +328,15 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
if tcpipErr != nil {
return nil, fmt.Errorf("could not disable TCP RACK: %v", tcpipErr)
}
cubicOpt := tcpip.CongestionControlOption("cubic")
tcpipErr = ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &cubicOpt)
// gVisor defaults to reno at the time of writing. We explicitly set reno
// congestion control in order to prevent unexpected changes. Netstack
// has an int overflow in sender congestion window arithmetic that is more
// prone to trigger with cubic congestion control.
// See https://github.com/google/gvisor/issues/11632
renoOpt := tcpip.CongestionControlOption("reno")
tcpipErr = ipstack.SetTransportProtocolOption(tcp.ProtocolNumber, &renoOpt)
if tcpipErr != nil {
return nil, fmt.Errorf("could not set cubic congestion control: %v", tcpipErr)
return nil, fmt.Errorf("could not set reno congestion control: %v", tcpipErr)
}
err := setTCPBufSizes(ipstack)
if err != nil {
@@ -337,7 +344,7 @@ func Create(logf logger.Logf, tundev *tstun.Wrapper, e wgengine.Engine, mc *magi
}
supportedGSOKind := stack.GSONotSupported
supportedGROKind := groNotSupported
if runtime.GOOS == "linux" {
if runtime.GOOS == "linux" && buildfeatures.HasGRO {
// TODO(jwhited): add Windows support https://github.com/tailscale/corp/issues/21874
supportedGROKind = tcpGROSupported
supportedGSOKind = stack.HostGSOSupported
@@ -571,9 +578,16 @@ func (ns *Impl) decrementInFlightTCPForward(tei stack.TransportEndpointID, remot
}
}
// LocalBackend is a fake name for *ipnlocal.LocalBackend to avoid an import cycle.
type LocalBackend = any
// Start sets up all the handlers so netstack can start working. Implements
// wgengine.FakeImpl.
func (ns *Impl) Start(lb *ipnlocal.LocalBackend) error {
func (ns *Impl) Start(b LocalBackend) error {
if b == nil {
panic("nil LocalBackend interface")
}
lb := b.(*ipnlocal.LocalBackend)
if lb == nil {
panic("nil LocalBackend")
}
@@ -637,13 +651,15 @@ func (ns *Impl) UpdateNetstackIPs(nm *netmap.NetworkMap) {
var selfNode tailcfg.NodeView
var serviceAddrSet set.Set[netip.Addr]
if nm != nil {
vipServiceIPMap := nm.GetVIPServiceIPMap()
serviceAddrSet = make(set.Set[netip.Addr], len(vipServiceIPMap)*2)
for _, addrs := range vipServiceIPMap {
serviceAddrSet.AddSlice(addrs)
}
ns.atomicIsLocalIPFunc.Store(ipset.NewContainsIPFunc(nm.GetAddresses()))
ns.atomicIsVIPServiceIPFunc.Store(serviceAddrSet.Contains)
if buildfeatures.HasServe {
vipServiceIPMap := nm.GetVIPServiceIPMap()
serviceAddrSet = make(set.Set[netip.Addr], len(vipServiceIPMap)*2)
for _, addrs := range vipServiceIPMap {
serviceAddrSet.AddSlice(addrs)
}
ns.atomicIsVIPServiceIPFunc.Store(serviceAddrSet.Contains)
}
selfNode = nm.SelfNode
} else {
ns.atomicIsLocalIPFunc.Store(ipset.FalseContainsIPFunc())
@@ -1026,6 +1042,9 @@ func (ns *Impl) isLocalIP(ip netip.Addr) bool {
// isVIPServiceIP reports whether ip is an IP address that's
// assigned to a VIP service.
func (ns *Impl) isVIPServiceIP(ip netip.Addr) bool {
if !buildfeatures.HasServe {
return false
}
return ns.atomicIsVIPServiceIPFunc.Load()(ip)
}
@@ -1068,7 +1087,7 @@ func (ns *Impl) shouldProcessInbound(p *packet.Parsed, t *tstun.Wrapper) bool {
return true
}
}
if isService {
if buildfeatures.HasServe && isService {
if p.IsEchoRequest() {
return true
}
@@ -1429,6 +1448,13 @@ func (ns *Impl) acceptTCP(r *tcp.ForwarderRequest) {
}
}
// tcpCloser is an interface to abstract around various TCPConn types that
// allow closing of the read and write streams independently of each other.
type tcpCloser interface {
CloseRead() error
CloseWrite() error
}
func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet.TCPConn, clientRemoteIP netip.Addr, wq *waiter.Queue, dialAddr netip.AddrPort) (handled bool) {
dialAddrStr := dialAddr.String()
if debugNetstack() {
@@ -1457,7 +1483,7 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet.
}()
// Attempt to dial the outbound connection before we accept the inbound one.
var dialFunc func(context.Context, string, string) (net.Conn, error)
var dialFunc netx.DialFunc
if ns.forwardDialFunc != nil {
dialFunc = ns.forwardDialFunc
} else {
@@ -1495,18 +1521,48 @@ func (ns *Impl) forwardTCP(getClient func(...tcpip.SettableSocketOption) *gonet.
}
defer client.Close()
// As of 2025-07-03, backend is always either a net.TCPConn
// from stdDialer.DialContext (which has the requisite functions),
// or nil from hangDialer in tests (in which case we would have
// errored out by now), so this conversion should always succeed.
backendTCPCloser, backendIsTCPCloser := backend.(tcpCloser)
connClosed := make(chan error, 2)
go func() {
_, err := io.Copy(backend, client)
if err != nil {
err = fmt.Errorf("client -> backend: %w", err)
}
connClosed <- err
err = nil
if backendIsTCPCloser {
err = backendTCPCloser.CloseWrite()
}
err = errors.Join(err, client.CloseRead())
if err != nil {
ns.logf("client -> backend close connection: %v", err)
}
}()
go func() {
_, err := io.Copy(client, backend)
if err != nil {
err = fmt.Errorf("backend -> client: %w", err)
}
connClosed <- err
err = nil
if backendIsTCPCloser {
err = backendTCPCloser.CloseRead()
}
err = errors.Join(err, client.CloseWrite())
if err != nil {
ns.logf("backend -> client close connection: %v", err)
}
}()
err = <-connClosed
if err != nil {
ns.logf("proxy connection closed with error: %v", err)
// Wait for both ends of the connection to close.
for range 2 {
err = <-connClosed
if err != nil {
ns.logf("proxy connection closed with error: %v", err)
}
}
ns.logf("[v2] netstack: forwarder connection to %s closed", dialAddrStr)
return
@@ -1849,7 +1905,6 @@ func (ns *Impl) ExpVar() expvar.Var {
{"option_unknown_received", ipStats.OptionUnknownReceived},
}
for _, metric := range ipMetrics {
metric := metric
m.Set("counter_ip_"+metric.name, expvar.Func(func() any {
return readStatCounter(metric.field)
}))
@@ -1876,7 +1931,6 @@ func (ns *Impl) ExpVar() expvar.Var {
{"errors", fwdStats.Errors},
}
for _, metric := range fwdMetrics {
metric := metric
m.Set("counter_ip_forward_"+metric.name, expvar.Func(func() any {
return readStatCounter(metric.field)
}))
@@ -1920,7 +1974,6 @@ func (ns *Impl) ExpVar() expvar.Var {
{"forward_max_in_flight_drop", tcpStats.ForwardMaxInFlightDrop},
}
for _, metric := range tcpMetrics {
metric := metric
m.Set("counter_tcp_"+metric.name, expvar.Func(func() any {
return readStatCounter(metric.field)
}))
@@ -1947,7 +2000,6 @@ func (ns *Impl) ExpVar() expvar.Var {
{"checksum_errors", udpStats.ChecksumErrors},
}
for _, metric := range udpMetrics {
metric := metric
m.Set("counter_udp_"+metric.name, expvar.Func(func() any {
return readStatCounter(metric.field)
}))