Browse Source

net/packet/checksum: copy the gvisor checksum, remove the dep

As part of making Tailscale's gvisor dependency optional for small builds,
this was one of the last places left that depended on gvisor. Just copy
the couple functions were were using.

Updates #17283

Change-Id: Id2bc07ba12039afe4c8a3f0b68f4d76d1863bbfe
Signed-off-by: Brad Fitzpatrick <[email protected]>
Brad Fitzpatrick 5 months ago
parent
commit
c95fdb0f8a
2 changed files with 113 additions and 18 deletions
  1. 3 6
      cmd/tailscaled/depaware-minbox.txt
  2. 110 12
      net/packet/checksum/checksum.go

+ 3 - 6
cmd/tailscaled/depaware-minbox.txt

@@ -14,7 +14,6 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         github.com/go-json-experiment/json/internal/jsonwire         from github.com/go-json-experiment/json+
         github.com/go-json-experiment/json/jsontext                  from github.com/go-json-experiment/json+
         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+
@@ -56,7 +55,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
         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/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
@@ -66,10 +65,8 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
      💣 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/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/header                           from tailscale.com/net/packet/checksum
-        gvisor.dev/gvisor/pkg/tcpip/seqnum                           from gvisor.dev/gvisor/pkg/tcpip/header
+        gvisor.dev/gvisor/pkg/tcpip                                  from tailscale.com/ipn/ipnlocal
+     💣 gvisor.dev/gvisor/pkg/tcpip/checksum                         from gvisor.dev/gvisor/pkg/buffer
         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

+ 110 - 12
net/packet/checksum/checksum.go

@@ -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)
+}