Update
This commit is contained in:
1 parent
3530afb854
commit
6903fcff27
2146 files changed
+112256
-190703
No files matched your search
+11
-3
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ts_omit_netstack
|
||||
@@ -25,11 +25,14 @@ import (
|
||||
// !stack.PacketBuffer.RXChecksumValidated, i.e. it satisfies
|
||||
// stack.CapabilityRXChecksumOffload. Other protocols with checksum fields,
|
||||
// e.g. ICMP{v6}, are still validated by gVisor regardless of rx checksum
|
||||
// offloading capabilities.
|
||||
// offloading capabilities. IPv4 fragments cannot have their L4 checksums
|
||||
// validated before reassembly, so only their IPv4 header checksum is validated
|
||||
// here.
|
||||
func RXChecksumOffload(p *packet.Parsed) *stack.PacketBuffer {
|
||||
var (
|
||||
pn tcpip.NetworkProtocolNumber
|
||||
csumStart int
|
||||
fragment bool
|
||||
)
|
||||
buf := p.Buffer()
|
||||
|
||||
@@ -45,6 +48,11 @@ func RXChecksumOffload(p *packet.Parsed) *stack.PacketBuffer {
|
||||
if ^tun.Checksum(buf[:csumStart], 0) != 0 {
|
||||
return nil
|
||||
}
|
||||
// Non-first fragments (FragmentOffset != 0) arrive here with
|
||||
// p.IPProto == ipproto.Fragment (set by packet.Parsed.Decode), so
|
||||
// they already skip the L4 checksum check below. We only need to
|
||||
// catch the first fragment, which still has its real IPProto.
|
||||
fragment = header.IPv4(buf).More()
|
||||
pn = header.IPv4ProtocolNumber
|
||||
case 6:
|
||||
if len(buf) < header.IPv6FixedHeaderSize {
|
||||
@@ -80,7 +88,7 @@ func RXChecksumOffload(p *packet.Parsed) *stack.PacketBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
if p.IPProto == ipproto.TCP || p.IPProto == ipproto.UDP {
|
||||
if !fragment && (p.IPProto == ipproto.TCP || p.IPProto == ipproto.UDP) {
|
||||
lenForPseudo := len(buf) - csumStart
|
||||
csum := tun.PseudoHeaderChecksum(
|
||||
uint8(p.IPProto),
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !ts_omit_gro
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios || ts_omit_gro
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ts_omit_netstack
|
||||
|
||||
+125
-28
@@ -1,12 +1,14 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netstack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
@@ -97,31 +99,65 @@ const (
|
||||
tcpGROSupported
|
||||
)
|
||||
|
||||
// linkEndpoint implements stack.LinkEndpoint and stack.GSOEndpoint. Outbound
|
||||
// packets written by gVisor towards Tailscale are stored in a channel.
|
||||
// Inbound is fed to gVisor via injectInbound or gro. This is loosely
|
||||
// modeled after gvisor.dev/pkg/tcpip/link/channel.Endpoint.
|
||||
// outboundQueueRouter is a function that takes an input packet and returns an
|
||||
// associated [outboundQueue] decision.
|
||||
type outboundQueueRouter func(*stack.PacketBuffer) outboundQueue
|
||||
|
||||
// outboundQueue describes outbound [queue]'s attached to [linkEndpoint].
|
||||
type outboundQueue int
|
||||
|
||||
const (
|
||||
// outboundDrop indicates that the packet should be discarded. It does not
|
||||
// correspond to a real queue.
|
||||
outboundDrop outboundQueue = iota
|
||||
// outboundToWireGuard is the outboundQueue for packets that should be
|
||||
// delivered to WireGuard for potential transmission to a remote peer.
|
||||
outboundToWireGuard
|
||||
// outboundToHost is the outboundQueue for packets that should be delivered
|
||||
// to the local host networking stack.
|
||||
outboundToHost
|
||||
// outboundLoopback is the outboundQueue for packets that should be looped
|
||||
// back into gVisor.
|
||||
outboundLoopback
|
||||
// outboundQueueLimit is the exclusive upper bound of outboundQueue values.
|
||||
outboundQueueLimit
|
||||
)
|
||||
|
||||
// linkEndpoint implements [stack.LinkEndpoint] and [stack.GSOEndpoint]. Outbound
|
||||
// packets written by gVisor towards Tailscale are stored in one of several
|
||||
// outbound [queue]'s. Inbound is fed to gVisor via [linkEndpoint.injectInbound]
|
||||
// or [linkEndpoint.gro]. linkEndpoint is loosely modeled after
|
||||
// [gvisor.dev/pkg/tcpip/link/channel.Endpoint].
|
||||
type linkEndpoint struct {
|
||||
SupportedGSOKind stack.SupportedGSO
|
||||
supportedGRO supportedGRO
|
||||
SupportedGSOKind stack.SupportedGSO
|
||||
supportedGRO supportedGRO
|
||||
outboundQueueRouter outboundQueueRouter
|
||||
|
||||
mu sync.RWMutex // mu guards the following fields
|
||||
dispatcher stack.NetworkDispatcher
|
||||
linkAddr tcpip.LinkAddress
|
||||
mtu uint32
|
||||
|
||||
q *queue // outbound
|
||||
outboundQueues [outboundQueueLimit]*queue // outbound
|
||||
}
|
||||
|
||||
func newLinkEndpoint(size int, mtu uint32, linkAddr tcpip.LinkAddress, supportedGRO supportedGRO) *linkEndpoint {
|
||||
// newLinkEndpoint constructs a [*linkEndpoint]. size is applied independently
|
||||
// to each outbound [queue], so total size = num outbound queues * size.
|
||||
func newLinkEndpoint(size int, mtu uint32, linkAddr tcpip.LinkAddress, supportedGRO supportedGRO, outboundQueueRouter outboundQueueRouter) *linkEndpoint {
|
||||
le := &linkEndpoint{
|
||||
supportedGRO: supportedGRO,
|
||||
q: &queue{
|
||||
supportedGRO: supportedGRO,
|
||||
outboundQueueRouter: outboundQueueRouter,
|
||||
mtu: mtu,
|
||||
linkAddr: linkAddr,
|
||||
}
|
||||
for n := range le.outboundQueues {
|
||||
if n == int(outboundDrop) {
|
||||
continue
|
||||
}
|
||||
le.outboundQueues[n] = &queue{
|
||||
c: make(chan *stack.PacketBuffer, size),
|
||||
closedCh: make(chan struct{}),
|
||||
},
|
||||
mtu: mtu,
|
||||
linkAddr: linkAddr,
|
||||
}
|
||||
}
|
||||
return le
|
||||
}
|
||||
@@ -151,36 +187,52 @@ func (ep *linkEndpoint) gro(p *packet.Parsed, g *gro.GRO) *gro.GRO {
|
||||
return g
|
||||
}
|
||||
|
||||
// Close closes l. Further packet injections will return an error, and all
|
||||
// Close closes ep. Further packet injections will return an error, and all
|
||||
// pending packets are discarded. Close may be called concurrently with
|
||||
// WritePackets.
|
||||
func (ep *linkEndpoint) Close() {
|
||||
ep.mu.Lock()
|
||||
ep.dispatcher = nil
|
||||
ep.mu.Unlock()
|
||||
ep.q.Close()
|
||||
for _, q := range ep.outboundQueues {
|
||||
if q != nil {
|
||||
q.Close()
|
||||
}
|
||||
}
|
||||
ep.Drain()
|
||||
}
|
||||
|
||||
// Read does non-blocking read one packet from the outbound packet queue.
|
||||
func (ep *linkEndpoint) Read() *stack.PacketBuffer {
|
||||
return ep.q.Read()
|
||||
// Read reads from the provided [outboundQueue] in non-blocking fashion.
|
||||
func (ep *linkEndpoint) Read(queue outboundQueue) *stack.PacketBuffer {
|
||||
return ep.outboundQueues[queue].Read()
|
||||
}
|
||||
|
||||
// ReadContext does blocking read for one packet from the outbound packet queue.
|
||||
// It can be cancelled by ctx, and in this case, it returns nil.
|
||||
func (ep *linkEndpoint) ReadContext(ctx context.Context) *stack.PacketBuffer {
|
||||
return ep.q.ReadContext(ctx)
|
||||
// ReadContext reads from the provided [outboundQueue] in blocking fashion. It
|
||||
// can be canceled by ctx, in which case it will return nil.
|
||||
func (ep *linkEndpoint) ReadContext(ctx context.Context, queue outboundQueue) *stack.PacketBuffer {
|
||||
return ep.outboundQueues[queue].ReadContext(ctx)
|
||||
}
|
||||
|
||||
// Drain removes all outbound packets from the channel and counts them.
|
||||
// Drain drains all packets from outbound queues and returns the number drained.
|
||||
func (ep *linkEndpoint) Drain() int {
|
||||
return ep.q.Drain()
|
||||
var total int
|
||||
for _, q := range ep.outboundQueues {
|
||||
if q != nil {
|
||||
total += q.Drain()
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
// NumQueued returns the number of packets queued for outbound.
|
||||
func (ep *linkEndpoint) NumQueued() int {
|
||||
return ep.q.Num()
|
||||
var total int
|
||||
for _, q := range ep.outboundQueues {
|
||||
if q != nil {
|
||||
total += q.Num()
|
||||
}
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
func (ep *linkEndpoint) injectInbound(p *packet.Parsed) {
|
||||
@@ -198,6 +250,43 @@ func (ep *linkEndpoint) injectInbound(p *packet.Parsed) {
|
||||
pkt.DecRef()
|
||||
}
|
||||
|
||||
// DeliverLoopback delivers pkt back into gVisor's network stack as if it
|
||||
// arrived from the network, for self-addressed (loopback) packets. It takes
|
||||
// ownership of one reference count on pkt. The caller must not use pkt after
|
||||
// calling this method. It returns false if the dispatcher is not attached.
|
||||
//
|
||||
// Outbound packets from gVisor have their headers already parsed into separate
|
||||
// views (NetworkHeader, TransportHeader, Data). DeliverNetworkPacket expects
|
||||
// a raw unparsed packet, so we must re-serialize the packet into a new
|
||||
// PacketBuffer with all bytes in the payload for gVisor to parse on inbound.
|
||||
func (ep *linkEndpoint) DeliverLoopback(pkt *stack.PacketBuffer) bool {
|
||||
ep.mu.RLock()
|
||||
d := ep.dispatcher
|
||||
ep.mu.RUnlock()
|
||||
if d == nil {
|
||||
pkt.DecRef()
|
||||
return false
|
||||
}
|
||||
|
||||
// Serialize the outbound packet back to raw bytes.
|
||||
raw := stack.PayloadSince(pkt.NetworkHeader()).AsSlice()
|
||||
proto := pkt.NetworkProtocolNumber
|
||||
|
||||
// We're done with the original outbound packet.
|
||||
pkt.DecRef()
|
||||
|
||||
// Create a new PacketBuffer from the raw bytes for inbound delivery.
|
||||
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Payload: buffer.MakeWithData(raw),
|
||||
})
|
||||
newPkt.NetworkProtocolNumber = proto
|
||||
newPkt.RXChecksumValidated = true
|
||||
|
||||
d.DeliverNetworkPacket(proto, newPkt)
|
||||
newPkt.DecRef()
|
||||
return true
|
||||
}
|
||||
|
||||
// Attach saves the stack network-layer dispatcher for use later when packets
|
||||
// are injected.
|
||||
func (ep *linkEndpoint) Attach(dispatcher stack.NetworkDispatcher) {
|
||||
@@ -266,7 +355,7 @@ func (ep *linkEndpoint) SetLinkAddress(addr tcpip.LinkAddress) {
|
||||
ep.linkAddr = addr
|
||||
}
|
||||
|
||||
// WritePackets stores outbound packets into the channel.
|
||||
// WritePackets routes outbound packets into the appropriate outbound [queue].
|
||||
// Multiple concurrent calls are permitted.
|
||||
func (ep *linkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Error) {
|
||||
n := 0
|
||||
@@ -278,7 +367,15 @@ func (ep *linkEndpoint) WritePackets(pkts stack.PacketBufferList) (int, tcpip.Er
|
||||
// control MTU (and by effect TCP MSS in gVisor) we *shouldn't* expect to
|
||||
// ever overflow 128 slots (see wireguard-go/tun.ErrTooManySegments usage).
|
||||
for _, pkt := range pkts.AsSlice() {
|
||||
if err := ep.q.Write(pkt); err != nil {
|
||||
q := ep.outboundQueueRouter(pkt)
|
||||
if q == outboundDrop {
|
||||
n++
|
||||
continue
|
||||
}
|
||||
if q < 0 || q >= outboundQueueLimit {
|
||||
panic(fmt.Sprintf("linkEndpoint.outboundQueueRouter returned %v which is outside outboundQueueLimit(%v)", q, outboundQueueLimit))
|
||||
}
|
||||
if err := ep.outboundQueues[q].Write(pkt); err != nil {
|
||||
if _, ok := err.(*tcpip.ErrNoBufferSpace); !ok && n == 0 {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
+566
-185
File diff suppressed because it is too large.
Load diff
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package netstack
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !darwin && !ios
|
||||
|
||||
+17
-20
@@ -1,4 +1,4 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build darwin || ios
|
||||
@@ -6,33 +6,30 @@
|
||||
package netstack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
probing "github.com/prometheus-community/pro-bing"
|
||||
"tailscale.com/net/ping"
|
||||
)
|
||||
|
||||
// sendOutboundUserPing sends a non-privileged ICMP (or ICMPv6) ping to dstIP with the given timeout.
|
||||
func (ns *Impl) sendOutboundUserPing(dstIP netip.Addr, timeout time.Duration) error {
|
||||
p, err := probing.NewPinger(dstIP.String())
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
p := ping.New(ctx, ns.logf, nil)
|
||||
p.Unprivileged = true
|
||||
defer p.Close()
|
||||
|
||||
dst := &net.IPAddr{IP: dstIP.AsSlice(), Zone: dstIP.Zone()}
|
||||
ns.logf("sendOutboundUserPing: forwarding ping to %s", dstIP)
|
||||
d, err := p.Send(ctx, dst, []byte("tailscale-userping"))
|
||||
if err != nil {
|
||||
ns.logf("sendICMPPingToIP failed to create pinger: %v", err)
|
||||
ns.logf("sendOutboundUserPing: ping to %s failed: %v", dstIP, err)
|
||||
return err
|
||||
}
|
||||
|
||||
p.Timeout = timeout
|
||||
p.Count = 1
|
||||
p.SetPrivileged(false)
|
||||
|
||||
p.OnSend = func(pkt *probing.Packet) {
|
||||
ns.logf("sendICMPPingToIP: forwarding ping to %s:", p.Addr())
|
||||
}
|
||||
p.OnRecv = func(pkt *probing.Packet) {
|
||||
ns.logf("sendICMPPingToIP: %d bytes pong from %s: icmp_seq=%d time=%v", pkt.Nbytes, pkt.IPAddr, pkt.Seq, pkt.Rtt)
|
||||
}
|
||||
p.OnFinish = func(stats *probing.Statistics) {
|
||||
ns.logf("sendICMPPingToIP: done, %d replies received", stats.PacketsRecv)
|
||||
}
|
||||
|
||||
return p.Run()
|
||||
ns.logf("sendOutboundUserPing: pong from %s in %v", dstIP, d)
|
||||
return nil
|
||||
}
|
||||
Reference in new issue
Block a user