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

@@ -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"
)
@@ -88,13 +86,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) < minUDPSize {
// 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) < minTCPSize {
// Not enough space for a TCP header.
return
}
@@ -112,34 +110,60 @@ func updateV4PacketChecksums(p *packet.Parsed, old, new netip.Addr) {
}
}
const (
minUDPSize = 8
minTCPSize = 20
minICMPv6Size = 8
minIPv6Header = 40
offsetICMPv6Checksum = 2
offsetUDPChecksum = 6
offsetTCPChecksum = 16
)
// updateV6PacketChecksums updates the checksums in the packet buffer.
// 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 {
if len(p.Buffer()) < minIPv6Header {
// Not enough space for an IPv6 header.
return
}
o6, n6 := tcpip.AddrFrom16Slice(old.AsSlice()), tcpip.AddrFrom16Slice(new.AsSlice())
o6, n6 := old.As16(), new.As16()
// Now update the transport layer checksums, where applicable.
tr := p.Transport()
switch p.IPProto {
case ipproto.ICMPv6:
if len(tr) < header.ICMPv6MinimumSize {
if len(tr) < minICMPv6Size {
return
}
header.ICMPv6(tr).UpdateChecksumPseudoHeaderAddress(o6, n6)
ss := tr[offsetICMPv6Checksum:]
xsum := binary.BigEndian.Uint16(ss)
binary.BigEndian.PutUint16(ss,
^checksumUpdate2ByteAlignedAddress(^xsum, o6, n6))
case ipproto.UDP, ipproto.DCCP:
if len(tr) < header.UDPMinimumSize {
if len(tr) < minUDPSize {
return
}
header.UDP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
ss := tr[offsetUDPChecksum:]
xsum := binary.BigEndian.Uint16(ss)
xsum = ^xsum
xsum = checksumUpdate2ByteAlignedAddress(xsum, o6, n6)
xsum = ^xsum
binary.BigEndian.PutUint16(ss, xsum)
case ipproto.TCP:
if len(tr) < header.TCPMinimumSize {
if len(tr) < minTCPSize {
return
}
header.TCP(tr).UpdateChecksumPseudoHeaderAddress(o6, n6, true)
ss := tr[offsetTCPChecksum:]
xsum := binary.BigEndian.Uint16(ss)
xsum = ^xsum
xsum = checksumUpdate2ByteAlignedAddress(xsum, o6, n6)
xsum = ^xsum
binary.BigEndian.PutUint16(ss, xsum)
case ipproto.SCTP:
// No transport layer update required.
}
@@ -195,3 +219,77 @@ func updateV4Checksum(oldSum, old, new []byte) {
hcPrime := ^uint16(cPrime)
binary.BigEndian.PutUint16(oldSum, hcPrime)
}
// checksumUpdate2ByteAlignedAddress updates an address in a calculated
// checksum.
//
// The addresses must have the same length and must contain an even number
// of bytes. The address MUST begin at a 2-byte boundary in the original buffer.
//
// This implementation is copied from gVisor, but updated to use [16]byte.
func checksumUpdate2ByteAlignedAddress(xsum uint16, old, new [16]byte) uint16 {
const uint16Bytes = 2
oldAddr := old[:]
newAddr := new[:]
// As per RFC 1071 page 4,
// (4) Incremental Update
//
// ...
//
// To update the checksum, simply add the differences of the
// sixteen bit integers that have been changed. To see why this
// works, observe that every 16-bit integer has an additive inverse
// and that addition is associative. From this it follows that
// given the original value m, the new value m', and the old
// checksum C, the new checksum C' is:
//
// C' = C + (-m) + m' = C + (m' - m)
for len(oldAddr) != 0 {
// Convert the 2 byte sequences to uint16 values then apply the increment
// update.
xsum = checksumUpdate2ByteAlignedUint16(xsum, (uint16(oldAddr[0])<<8)+uint16(oldAddr[1]), (uint16(newAddr[0])<<8)+uint16(newAddr[1]))
oldAddr = oldAddr[uint16Bytes:]
newAddr = newAddr[uint16Bytes:]
}
return xsum
}
// checksumUpdate2ByteAlignedUint16 updates a uint16 value in a calculated
// checksum.
//
// The value MUST begin at a 2-byte boundary in the original buffer.
//
// This implementation is copied from gVisor.
func checksumUpdate2ByteAlignedUint16(xsum, old, new uint16) uint16 {
// As per RFC 1071 page 4,
// (4) Incremental Update
//
// ...
//
// To update the checksum, simply add the differences of the
// sixteen bit integers that have been changed. To see why this
// works, observe that every 16-bit integer has an additive inverse
// and that addition is associative. From this it follows that
// given the original value m, the new value m', and the old
// checksum C, the new checksum C' is:
//
// C' = C + (-m) + m' = C + (m' - m)
if old == new {
return xsum
}
return checksumCombine(xsum, checksumCombine(new, ^old))
}
// checksumCombine combines the two uint16 to form their checksum. This is done
// by adding them and the carry.
//
// Note that checksum a must have been computed on an even number of bytes.
//
// This implementation is copied from gVisor.
func checksumCombine(a, b uint16) uint16 {
v := uint32(a) + uint32(b)
return uint16(v + v>>16)
}

View File

@@ -24,6 +24,33 @@ const (
GeneveProtocolWireGuard uint16 = 0x7A12
)
// VirtualNetworkID is a Geneve header (RFC8926) 3-byte virtual network
// identifier. Its methods are NOT thread-safe.
type VirtualNetworkID struct {
_vni uint32
}
const (
vniSetMask uint32 = 0xFF000000
vniGetMask uint32 = ^vniSetMask
)
// IsSet returns true if Set() had been called previously, otherwise false.
func (v *VirtualNetworkID) IsSet() bool {
return v._vni&vniSetMask != 0
}
// Set sets the provided VNI. If VNI exceeds the 3-byte storage it will be
// clamped.
func (v *VirtualNetworkID) Set(vni uint32) {
v._vni = vni | vniSetMask
}
// Get returns the VNI value.
func (v *VirtualNetworkID) Get() uint32 {
return v._vni & vniGetMask
}
// GeneveHeader represents the fixed size Geneve header from RFC8926.
// TLVs/options are not implemented/supported.
//
@@ -51,7 +78,7 @@ type GeneveHeader struct {
// decisions or MAY be used as a mechanism to distinguish between
// overlapping address spaces contained in the encapsulated packet when load
// balancing across CPUs.
VNI uint32
VNI VirtualNetworkID
// O (1 bit): Control packet. This packet contains a control message.
// Control messages are sent between tunnel endpoints. Tunnel endpoints MUST
@@ -65,12 +92,18 @@ type GeneveHeader struct {
Control bool
}
// Encode encodes GeneveHeader into b. If len(b) < GeneveFixedHeaderLength an
// io.ErrShortBuffer error is returned.
var ErrGeneveVNIUnset = errors.New("VNI is unset")
// Encode encodes GeneveHeader into b. If len(b) < [GeneveFixedHeaderLength] an
// [io.ErrShortBuffer] error is returned. If !h.VNI.IsSet() then an
// [ErrGeneveVNIUnset] error is returned.
func (h *GeneveHeader) Encode(b []byte) error {
if len(b) < GeneveFixedHeaderLength {
return io.ErrShortBuffer
}
if !h.VNI.IsSet() {
return ErrGeneveVNIUnset
}
if h.Version > 3 {
return errors.New("version must be <= 3")
}
@@ -81,15 +114,12 @@ func (h *GeneveHeader) Encode(b []byte) error {
b[1] |= 0x80
}
binary.BigEndian.PutUint16(b[2:], h.Protocol)
if h.VNI > 1<<24-1 {
return errors.New("VNI must be <= 2^24-1")
}
binary.BigEndian.PutUint32(b[4:], h.VNI<<8)
binary.BigEndian.PutUint32(b[4:], h.VNI.Get()<<8)
return nil
}
// Decode decodes GeneveHeader from b. If len(b) < GeneveFixedHeaderLength an
// io.ErrShortBuffer error is returned.
// Decode decodes GeneveHeader from b. If len(b) < [GeneveFixedHeaderLength] an
// [io.ErrShortBuffer] error is returned.
func (h *GeneveHeader) Decode(b []byte) error {
if len(b) < GeneveFixedHeaderLength {
return io.ErrShortBuffer
@@ -99,6 +129,6 @@ func (h *GeneveHeader) Decode(b []byte) error {
h.Control = true
}
h.Protocol = binary.BigEndian.Uint16(b[2:])
h.VNI = binary.BigEndian.Uint32(b[4:]) >> 8
h.VNI.Set(binary.BigEndian.Uint32(b[4:]) >> 8)
return nil
}

View File

@@ -8,6 +8,7 @@ import (
"math"
)
const igmpHeaderLength = 8
const tcpHeaderLength = 20
const sctpHeaderLength = 12

View File

@@ -51,10 +51,11 @@ type Parsed struct {
IPVersion uint8
// IPProto is the IP subprotocol (UDP, TCP, etc.). Valid iff IPVersion != 0.
IPProto ipproto.Proto
// SrcIP4 is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP.
// Src is the source address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP || IPProto == SCTP.
Src netip.AddrPort
// DstIP4 is the destination address. Family matches IPVersion.
// Dst is the destination address. Family matches IPVersion. Port is
// valid iff IPProto == TCP || IPProto == UDP || IPProto == SCTP.
Dst netip.AddrPort
// TCPFlags is the packet's TCP flag bits. Valid iff IPProto == TCP.
TCPFlags TCPFlag
@@ -160,14 +161,8 @@ func (q *Parsed) decode4(b []byte) {
if fragOfs == 0 {
// This is the first fragment
if moreFrags && len(sub) < minFragBlks {
// Suspiciously short first fragment, dump it.
q.IPProto = unknown
return
}
// otherwise, this is either non-fragmented (the usual case)
// or a big enough initial fragment that we can read the
// whole subprotocol header.
// Every protocol below MUST check that it has at least one entire
// transport header in order to protect against fragment confusion.
switch q.IPProto {
case ipproto.ICMPv4:
if len(sub) < icmp4HeaderLength {
@@ -179,6 +174,10 @@ func (q *Parsed) decode4(b []byte) {
q.dataofs = q.subofs + icmp4HeaderLength
return
case ipproto.IGMP:
if len(sub) < igmpHeaderLength {
q.IPProto = unknown
return
}
// Keep IPProto, but don't parse anything else
// out.
return
@@ -211,6 +210,15 @@ func (q *Parsed) decode4(b []byte) {
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
return
case ipproto.TSMP:
// Strictly disallow fragmented TSMP
if moreFrags {
q.IPProto = unknown
return
}
if len(sub) < minTSMPSize {
q.IPProto = unknown
return
}
// Inter-tailscale messages.
q.dataofs = q.subofs
return
@@ -223,8 +231,11 @@ func (q *Parsed) decode4(b []byte) {
} else {
// This is a fragment other than the first one.
if fragOfs < minFragBlks {
// First frag was suspiciously short, so we can't
// trust the followup either.
// disallow fragment offsets that are potentially inside of a
// transport header. This is notably asymmetric with the
// first-packet limit, that may allow a first-packet that requires a
// shorter offset than this limit, but without state to tie this
// to the first fragment we can not allow shorter packets.
q.IPProto = unknown
return
}
@@ -314,6 +325,10 @@ func (q *Parsed) decode6(b []byte) {
q.Dst = withPort(q.Dst, binary.BigEndian.Uint16(sub[2:4]))
return
case ipproto.TSMP:
if len(sub) < minTSMPSize {
q.IPProto = unknown
return
}
// Inter-tailscale messages.
q.dataofs = q.subofs
return

View File

@@ -15,10 +15,13 @@ import (
"fmt"
"net/netip"
"tailscale.com/net/flowtrack"
"go4.org/mem"
"tailscale.com/types/ipproto"
"tailscale.com/types/key"
)
const minTSMPSize = 7 // the rejected body is 7 bytes
// TailscaleRejectedHeader is a TSMP message that says that one
// Tailscale node has rejected the connection from another. Unlike a
// TCP RST, this includes a reason.
@@ -56,10 +59,6 @@ type TailscaleRejectedHeader struct {
const rejectFlagBitMaybeBroken = 0x1
func (rh TailscaleRejectedHeader) Flow() flowtrack.Tuple {
return flowtrack.MakeTuple(rh.Proto, rh.Src, rh.Dst)
}
func (rh TailscaleRejectedHeader) String() string {
return fmt.Sprintf("TSMP-reject-flow{%s %s > %s}: %s", rh.Proto, rh.Src, rh.Dst, rh.Reason)
}
@@ -75,6 +74,9 @@ const (
// TSMPTypePong is the type byte for a TailscalePongResponse.
TSMPTypePong TSMPType = 'o'
// TSPMTypeDiscoAdvertisement is the type byte for sending disco keys
TSMPTypeDiscoAdvertisement TSMPType = 'a'
)
type TailscaleRejectReason byte
@@ -262,3 +264,54 @@ func (h TSMPPongReply) Marshal(buf []byte) error {
binary.BigEndian.PutUint16(buf[9:11], h.PeerAPIPort)
return nil
}
// TSMPDiscoKeyAdvertisement is a TSMP message that's used for distributing Disco Keys.
//
// On the wire, after the IP header, it's currently 33 bytes:
// - 'a' (TSMPTypeDiscoAdvertisement)
// - 32 disco key bytes
type TSMPDiscoKeyAdvertisement struct {
Src, Dst netip.Addr // Src and Dst are set from the parent IP Header when parsing.
Key key.DiscoPublic
}
func (ka *TSMPDiscoKeyAdvertisement) Marshal() ([]byte, error) {
var iph Header
if ka.Src.Is4() {
iph = IP4Header{
IPProto: ipproto.TSMP,
Src: ka.Src,
Dst: ka.Dst,
}
} else {
iph = IP6Header{
IPProto: ipproto.TSMP,
Src: ka.Src,
Dst: ka.Dst,
}
}
payload := make([]byte, 0, 33)
payload = append(payload, byte(TSMPTypeDiscoAdvertisement))
payload = ka.Key.AppendTo(payload)
if len(payload) != 33 {
// Mostly to safeguard against ourselves changing this in the future.
return []byte{}, fmt.Errorf("expected payload length 33, got %d", len(payload))
}
return Generate(iph, payload[:]), nil
}
func (pp *Parsed) AsTSMPDiscoAdvertisement() (tka TSMPDiscoKeyAdvertisement, ok bool) {
if pp.IPProto != ipproto.TSMP {
return
}
p := pp.Payload()
if len(p) < 33 || p[0] != byte(TSMPTypeDiscoAdvertisement) {
return
}
tka.Src = pp.Src.Addr()
tka.Dst = pp.Dst.Addr()
tka.Key = key.DiscoPublicFromRaw32(mem.B(p[1:33]))
return tka, true
}