This commit is contained in:
JackDoan
2026-07-28 12:17:55 -05:00
parent 46a02b663e
commit db54e05bfe
7 changed files with 152 additions and 144 deletions
+3 -2
View File
@@ -119,7 +119,8 @@ func QueueCapabilities(q Queue) Capabilities {
type GSOProto uint8 type GSOProto uint8
const ( const (
GSOProtoTCP GSOProto = iota GSOProtoUnknown GSOProto = iota
GSOProtoTCP
GSOProtoUDP GSOProtoUDP
) )
@@ -138,7 +139,7 @@ const (
// full superpacket payload; they are read-only from the writer's // full superpacket payload; they are read-only from the writer's
// perspective and must remain valid until the call returns. Every segment // perspective and must remain valid until the call returns. Every segment
// in pays except possibly the last is exactly the same size. proto picks // in pays except possibly the last is exactly the same size. proto picks
// the L4 protocol so the writer knows which GSOType / CsumOffset to set. // the L4 protocol so the writer knows which gsoType / CsumOffset to set.
// //
// Callers should also consult CapsProvider (via SupportsGSO or // Callers should also consult CapsProvider (via SupportsGSO or
// QueueCapabilities) for the per-protocol negotiated capability; an // QueueCapabilities) for the per-protocol negotiated capability; an
+20 -20
View File
@@ -221,13 +221,11 @@ func (r *Offload) Read() ([]Packet, error) {
return r.pending, nil return r.pending, nil
} }
// decodeRead processes the packet sitting in rxBuf at rxOff (length // decodeRead processes the packet sitting in rxBuf at rxOff (length pktLen).
// pktLen). The bytes stay in rxBuf — for GSO_NONE we slice them as a // The bytes stay in rxBuf:
// regular IP datagram (running finishChecksum if NEEDS_CSUM is set); // * for GSO_NONE we slice them as a regular IP datagram (running finishChecksum if NEEDS_CSUM is set);
// for TSO/USO superpackets we attach the corrected GSO metadata so the // * for TSO/USO superpackets we attach the corrected GSO metadata, so the caller can segment lazily at encrypt time.
// caller can segment lazily at encrypt time. rxOff advances past the // rxOff advances by pktLen on success
// kernel-supplied body and nothing else, since segmentation no longer
// writes back into rxBuf.
func (r *Offload) decodeRead(pktLen int) error { func (r *Offload) decodeRead(pktLen int) error {
if pktLen <= 0 { if pktLen <= 0 {
return fmt.Errorf("short tun read: %d", pktLen) return fmt.Errorf("short tun read: %d", pktLen)
@@ -237,7 +235,7 @@ func (r *Offload) decodeRead(pktLen int) error {
body := r.rxBuf[r.rxOff : r.rxOff+pktLen] body := r.rxBuf[r.rxOff : r.rxOff+pktLen]
if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_NONE { if hdr.GSOType() == unix.VIRTIO_NET_HDR_GSO_NONE {
if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 { if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
if err := virtio.FinishChecksum(body, hdr); err != nil { if err := virtio.FinishChecksum(body, hdr); err != nil {
return err return err
@@ -258,7 +256,7 @@ func (r *Offload) decodeRead(pktLen int) error {
if err := virtio.CorrectHdrLen(body, &hdr); err != nil { if err := virtio.CorrectHdrLen(body, &hdr); err != nil {
return err return err
} }
proto, err := protoFromGSOType(hdr.GSOType) proto, err := protoFromGSOType(hdr.GSOType())
if err != nil { if err != nil {
return err return err
} }
@@ -384,24 +382,26 @@ func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto
if total > maxSuperpacketLen { if total > maxSuperpacketLen {
return fmt.Errorf("tio: WriteGSO superpacket %dB exceeds %d", total, maxSuperpacketLen) return fmt.Errorf("tio: WriteGSO superpacket %dB exceeds %d", total, maxSuperpacketLen)
} }
// GSOType and GSOSize stay zero (GSO_NONE, 0) for single-segment, or an unknown IP version. // gsoType and GSOSize stay zero (GSO_NONE, 0) for single-segment, or an unknown IP version.
vhdr := virtio.Hdr{ vhdr := virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
HdrLen: uint16(len(hdr) + len(transportHdr)), unix.VIRTIO_NET_HDR_GSO_NONE, /*gsoType*/
CsumStart: uint16(len(hdr)), uint16(len(hdr)+len(transportHdr)), /*hdrLen*/
CsumOffset: csumOff, 0, /*gsoSize*/
} uint16(len(hdr)), /*csumStart*/
csumOff, /*csumOffset*/
)
if segCount > 1 { if segCount > 1 {
ipVer := hdr[0] >> 4 ipVer := hdr[0] >> 4
switch { switch {
case proto == GSOProtoUDP && (ipVer == 4 || ipVer == 6): case proto == GSOProtoUDP && (ipVer == 4 || ipVer == 6):
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_UDP_L4 vhdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_UDP_L4)
case ipVer == 6: case ipVer == 6:
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV6 vhdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_TCPV6)
case ipVer == 4: case ipVer == 4:
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV4 vhdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_TCPV4)
} }
if vhdr.GSOType != unix.VIRTIO_NET_HDR_GSO_NONE { if vhdr.GSOType() != unix.VIRTIO_NET_HDR_GSO_NONE {
vhdr.GSOSize = uint16(segSize) vhdr.GSOSize = uint16(segSize)
} }
} }
+8 -20
View File
@@ -11,16 +11,9 @@ import (
"github.com/slackhq/nebula/overlay/tio/virtio" "github.com/slackhq/nebula/overlay/tio/virtio"
) )
// protoFromGSOType maps a virtio_net_hdr GSOType to the GSOProto value the // protoFromGSOType maps a virtio_net_hdr gsoType to the GSOProto value the
// segment-time helpers use. Returns an error for GSO_NONE or any unknown // segment-time helpers use. Returns an error for GSO_NONE or any unknown
// value — the caller should only invoke this on a confirmed superpacket. // value. The caller should only invoke this on a confirmed superpacket.
//
// VIRTIO_NET_HDR_GSO_ECN is a qualifier bit, not a type: it marks a TSO
// superpacket whose TCP header has CWR set (SKB_GSO_TCP_ECN) — we asked for
// these via TUN_F_TSO_ECN. The segmenter already emits CWR on the first
// segment only, so the bit just needs masking here. It only appears when
// ECN feedback is actually flowing (a congested hop CE-marked the flow),
// which is precisely when dropping the sender's superpackets hurts most.
func protoFromGSOType(t uint8) (GSOProto, error) { func protoFromGSOType(t uint8) (GSOProto, error) {
switch t &^ unix.VIRTIO_NET_HDR_GSO_ECN { switch t &^ unix.VIRTIO_NET_HDR_GSO_ECN {
case unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV6: case unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV6:
@@ -32,17 +25,12 @@ func protoFromGSOType(t uint8) (GSOProto, error) {
} }
} }
// SegmentSuperpacket invokes fn once per segment of pkt. For non-GSO pkts // SegmentSuperpacket invokes fn once per segment of pkt.
// fn is called once with pkt.Bytes (no segmentation, no copy). For GSO/USO // For non-GSO pkts fn is called once with pkt.Bytes.
// superpackets fn is called once per segment with a slice of pkt.Bytes // For GSO/USO superpackets, fn is called once per segment with a slice of pkt.Bytes holding that segment's plaintext
// holding that segment's plaintext (a freshly-patched L3+L4 header sliced // (a freshly-patched L3+L4 header sliced in front of the original payload chunk).
// in front of the original payload chunk). The slide is destructive: pkt is // This slicing is destructive: pkt is consumed by this call.
// consumed by this call and its bytes are in an undefined state when // Aborts and returns the first error from fn or from per-segment construction.
// SegmentSuperpacket returns. Callers must not retain pkt or any earlier
// seg slice past fn's return for that segment. The scratch parameter is
// unused on the destructive path and kept only for cross-platform
// signature compatibility. Aborts and returns the first error from fn or
// from per-segment construction.
func SegmentSuperpacket(pkt Packet, fn func(seg []byte) error) error { func SegmentSuperpacket(pkt Packet, fn func(seg []byte) error) error {
if !pkt.GSO.IsSuperpacket() { if !pkt.GSO.IsSuperpacket() {
return fn(pkt.Bytes) return fn(pkt.Bytes)
+54 -55
View File
@@ -63,7 +63,7 @@ func verifyChecksum(b []byte, pseudo uint16) bool {
// returns. Tests pre-set hdr.HdrLen correctly, so correctHdrLen is not // returns. Tests pre-set hdr.HdrLen correctly, so correctHdrLen is not
// invoked here. // invoked here.
func segmentForTest(pkt []byte, hdr virtio.Hdr, out *[][]byte, scratch []byte) error { func segmentForTest(pkt []byte, hdr virtio.Hdr, out *[][]byte, scratch []byte) error {
if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_NONE { if hdr.GSOType() == unix.VIRTIO_NET_HDR_GSO_NONE {
cp := append([]byte(nil), pkt...) cp := append([]byte(nil), pkt...)
if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 { if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
if err := virtio.FinishChecksum(cp, hdr); err != nil { if err := virtio.FinishChecksum(cp, hdr); err != nil {
@@ -73,7 +73,7 @@ func segmentForTest(pkt []byte, hdr virtio.Hdr, out *[][]byte, scratch []byte) e
*out = append(*out, cp) *out = append(*out, cp)
return nil return nil
} }
proto, err := protoFromGSOType(hdr.GSOType) proto, err := protoFromGSOType(hdr.GSOType())
if err != nil { if err != nil {
return err return err
} }
@@ -140,15 +140,14 @@ func buildTSOv4(t *testing.T, payLen, mss int) ([]byte, virtio.Hdr) {
for i := 0; i < payLen; i++ { for i := 0; i < payLen; i++ {
pkt[ipLen+tcpLen+i] = byte(i & 0xff) pkt[ipLen+tcpLen+i] = byte(i & 0xff)
} }
return pkt, virtio.NewHeader(
return pkt, virtio.Hdr{ unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4, uint16(ipLen+tcpLen), /*hdrLen*/
HdrLen: uint16(ipLen + tcpLen), uint16(mss), /*gsoSize*/
GSOSize: uint16(mss), uint16(ipLen), /*csumStart*/
CsumStart: uint16(ipLen), 16, /*csumOffset*/
CsumOffset: 16, )
}
} }
func TestSegmentTCPv4(t *testing.T) { func TestSegmentTCPv4(t *testing.T) {
@@ -262,14 +261,14 @@ func TestSegmentTCPv6(t *testing.T) {
pkt[ipLen+tcpLen+i] = byte(i) pkt[ipLen+tcpLen+i] = byte(i)
} }
hdr := virtio.Hdr{ hdr := virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6, unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
HdrLen: uint16(ipLen + tcpLen), uint16(ipLen+tcpLen), /*hdrLen*/
GSOSize: uint16(mss), uint16(mss), /*gsoSize*/
CsumStart: uint16(ipLen), uint16(ipLen), /*csumStart*/
CsumOffset: 16, 16, /*csumOffset*/
} )
scratch := make([]byte, testSegScratchSize) scratch := make([]byte, testSegScratchSize)
var out [][]byte var out [][]byte
@@ -311,7 +310,7 @@ func TestSegmentTCPv6(t *testing.T) {
func TestSegmentGSONonePassesThrough(t *testing.T) { func TestSegmentGSONonePassesThrough(t *testing.T) {
pkt, hdr := buildTSOv4(t, 100, 100) pkt, hdr := buildTSOv4(t, 100, 100)
hdr.GSOType = unix.VIRTIO_NET_HDR_GSO_NONE hdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_NONE)
hdr.Flags = 0 // no NEEDS_CSUM, leave packet untouched hdr.Flags = 0 // no NEEDS_CSUM, leave packet untouched
scratch := make([]byte, testSegScratchSize) scratch := make([]byte, testSegScratchSize)
@@ -330,7 +329,7 @@ func TestSegmentGSONonePassesThrough(t *testing.T) {
// TestSegmentRejectsLegacyUDPGSO ensures the legacy GSO_UDP (UFO) marker is // TestSegmentRejectsLegacyUDPGSO ensures the legacy GSO_UDP (UFO) marker is
// still rejected; only modern GSO_UDP_L4 (USO) is supported. // still rejected; only modern GSO_UDP_L4 (USO) is supported.
func TestSegmentRejectsLegacyUDPGSO(t *testing.T) { func TestSegmentRejectsLegacyUDPGSO(t *testing.T) {
hdr := virtio.Hdr{GSOType: unix.VIRTIO_NET_HDR_GSO_UDP} hdr := virtio.NewHeader(0, unix.VIRTIO_NET_HDR_GSO_UDP, 0, 0, 0, 0)
var out [][]byte var out [][]byte
if err := segmentForTest(nil, hdr, &out, nil); err == nil { if err := segmentForTest(nil, hdr, &out, nil); err == nil {
t.Fatalf("expected rejection for legacy UDP GSO") t.Fatalf("expected rejection for legacy UDP GSO")
@@ -362,14 +361,14 @@ func buildUSOv4(t *testing.T, payLen, gsoSize int) ([]byte, virtio.Hdr) {
pkt[ipLen+udpLen+i] = byte(i & 0xff) pkt[ipLen+udpLen+i] = byte(i & 0xff)
} }
return pkt, virtio.Hdr{ return pkt, virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4, unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
HdrLen: uint16(ipLen + udpLen), uint16(ipLen+udpLen), /*hdrLen*/
GSOSize: uint16(gsoSize), uint16(gsoSize), /*gsoSize*/
CsumStart: uint16(ipLen), uint16(ipLen), /*csumStart*/
CsumOffset: 6, 6, /*csumOffset*/
} )
} }
func TestSegmentUDPv4(t *testing.T) { func TestSegmentUDPv4(t *testing.T) {
@@ -471,14 +470,14 @@ func TestSegmentUDPv6(t *testing.T) {
pkt[ipLen+udpLen+i] = byte(i) pkt[ipLen+udpLen+i] = byte(i)
} }
hdr := virtio.Hdr{ hdr := virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4, unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
HdrLen: uint16(ipLen + udpLen), uint16(ipLen+udpLen), /*hdrLen*/
GSOSize: uint16(gso), uint16(gso), /*gsoSize*/
CsumStart: uint16(ipLen), uint16(ipLen), /*csumStart*/
CsumOffset: 6, 6, /*csumOffset*/
} )
scratch := make([]byte, testSegScratchSize) scratch := make([]byte, testSegScratchSize)
var out [][]byte var out [][]byte
@@ -610,14 +609,14 @@ func BenchmarkSegmentTCPv4(b *testing.B) {
for i := 0; i < sz.payLen; i++ { for i := 0; i < sz.payLen; i++ {
pkt[ipLen+tcpLen+i] = byte(i) pkt[ipLen+tcpLen+i] = byte(i)
} }
hdr := virtio.Hdr{ hdr := virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4, unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
HdrLen: uint16(ipLen + tcpLen), uint16(ipLen+tcpLen), /*hdrLen*/
GSOSize: uint16(sz.mss), uint16(sz.mss), /*gsoSize*/
CsumStart: uint16(ipLen), uint16(ipLen), /*csumStart*/
CsumOffset: 16, 16, /*csumOffset*/
} )
scratch := make([]byte, testSegScratchSize) scratch := make([]byte, testSegScratchSize)
out := make([][]byte, 0, 64) out := make([][]byte, 0, 64)
@@ -775,14 +774,14 @@ func TestDecodeReadFitsMaxTSOAtDrainThreshold(t *testing.T) {
copy(o.rxBuf[o.rxOff:], pkt) copy(o.rxBuf[o.rxOff:], pkt)
// Encode the matching virtio_net_hdr. // Encode the matching virtio_net_hdr.
hdr := virtio.Hdr{ hdr := virtio.NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6, unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
HdrLen: uint16(headerLen), uint16(headerLen), /*hdrLen*/
GSOSize: uint16(gsoSize), uint16(gsoSize), /*gsoSize*/
CsumStart: uint16(ipv6HdrLen), uint16(ipv6HdrLen), /*csumStart*/
CsumOffset: 16, 16, /*csumOffset*/
} )
hdr.Encode(o.readVnetScratch[:]) hdr.Encode(o.readVnetScratch[:])
startRxOff := o.rxOff startRxOff := o.rxOff
@@ -899,8 +898,8 @@ func TestWriteGSOLeadingEmptyFragmentGeometry(t *testing.T) {
} }
var vhdr virtio.Hdr var vhdr virtio.Hdr
vhdr.Decode(buf[:virtio.Size]) vhdr.Decode(buf[:virtio.Size])
if vhdr.GSOType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 { if vhdr.GSOType() != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
t.Errorf("GSOType=%d want UDP_L4", vhdr.GSOType) t.Errorf("gsoType=%d want UDP_L4", vhdr.GSOType())
} }
if vhdr.GSOSize != 1200 { if vhdr.GSOSize != 1200 {
t.Errorf("GSOSize=%d want 1200 (first non-empty fragment)", vhdr.GSOSize) t.Errorf("GSOSize=%d want 1200 (first non-empty fragment)", vhdr.GSOSize)
+32 -4
View File
@@ -3,7 +3,11 @@
package virtio package virtio
import "encoding/binary" import (
"encoding/binary"
"golang.org/x/sys/unix"
)
// Size is the on-wire length of struct virtio_net_hdr the kernel // Size is the on-wire length of struct virtio_net_hdr the kernel
// prepends/expects on a TUN opened with IFF_VNET_HDR (TUNSETVNETHDRSZ // prepends/expects on a TUN opened with IFF_VNET_HDR (TUNSETVNETHDRSZ
@@ -13,18 +17,29 @@ const Size = 10
// Hdr is the Go view of the legacy virtio_net_hdr. // Hdr is the Go view of the legacy virtio_net_hdr.
type Hdr struct { type Hdr struct {
Flags uint8 Flags uint8
GSOType uint8 gsoType uint8 //private to avoid mistakes wrt the 0x80 VIRTIO_NET_HDR_GSO_ECN flag, ORed with the other "GSO types"
HdrLen uint16 HdrLen uint16
GSOSize uint16 GSOSize uint16
CsumStart uint16 CsumStart uint16
CsumOffset uint16 CsumOffset uint16
} }
func NewHeader(flags, gsoType uint8, hdrLen, gsoSize, csumStart, csumOffset uint16) Hdr {
return Hdr{
Flags: flags,
gsoType: gsoType,
HdrLen: hdrLen,
GSOSize: gsoSize,
CsumStart: csumStart,
CsumOffset: csumOffset,
}
}
// Decode reads a virtio_net_hdr in host byte order (TUN default; we never // Decode reads a virtio_net_hdr in host byte order (TUN default; we never
// call TUNSETVNETLE so the kernel matches our endianness). // call TUNSETVNETLE so the kernel matches our endianness).
func (h *Hdr) Decode(b []byte) { func (h *Hdr) Decode(b []byte) {
h.Flags = b[0] h.Flags = b[0]
h.GSOType = b[1] h.gsoType = b[1]
h.HdrLen = binary.NativeEndian.Uint16(b[2:4]) h.HdrLen = binary.NativeEndian.Uint16(b[2:4])
h.GSOSize = binary.NativeEndian.Uint16(b[4:6]) h.GSOSize = binary.NativeEndian.Uint16(b[4:6])
h.CsumStart = binary.NativeEndian.Uint16(b[6:8]) h.CsumStart = binary.NativeEndian.Uint16(b[6:8])
@@ -35,9 +50,22 @@ func (h *Hdr) Decode(b []byte) {
// (must be at least Size bytes). Used to emit a TSO superpacket on egress. // (must be at least Size bytes). Used to emit a TSO superpacket on egress.
func (h *Hdr) Encode(b []byte) { func (h *Hdr) Encode(b []byte) {
b[0] = h.Flags b[0] = h.Flags
b[1] = h.GSOType b[1] = h.gsoType
binary.NativeEndian.PutUint16(b[2:4], h.HdrLen) binary.NativeEndian.PutUint16(b[2:4], h.HdrLen)
binary.NativeEndian.PutUint16(b[4:6], h.GSOSize) binary.NativeEndian.PutUint16(b[4:6], h.GSOSize)
binary.NativeEndian.PutUint16(b[6:8], h.CsumStart) binary.NativeEndian.PutUint16(b[6:8], h.CsumStart)
binary.NativeEndian.PutUint16(b[8:10], h.CsumOffset) binary.NativeEndian.PutUint16(b[8:10], h.CsumOffset)
} }
// GSOType returns gsoType with the ECN-flag masked out
func (h *Hdr) GSOType() uint8 {
return h.gsoType &^ unix.VIRTIO_NET_HDR_GSO_ECN
}
func (h *Hdr) HasECNFlag() bool {
return h.gsoType&unix.VIRTIO_NET_HDR_GSO_ECN != 0
}
func (h *Hdr) SetGSOType(x uint8) {
h.gsoType = x
}
+16 -26
View File
@@ -85,33 +85,27 @@ func CheckValid(pkt []byte, hdr Hdr) error {
} }
ipVersion := pkt[0] >> 4 ipVersion := pkt[0] >> 4
//mask out VIRTIO_NET_HDR_GSO_ECN, it's a qualifier, not a type gsoType := hdr.GSOType()
gsoType := hdr.GSOType &^ unix.VIRTIO_NET_HDR_GSO_ECN if hdr.HasECNFlag() && !(gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV4 || gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV6) {
// The ECN qualifier means CWR was set on a TSO superpacket, so it only return fmt.Errorf("virtio GSO_ECN qualifier on non-TCP GSO type %#x", hdr.gsoType)
// applies to the TCP types. The kernel's virtio_net_hdr_to_skb rejects
// it on anything else; mirror that instead of segmenting nonsense.
if hdr.GSOType&unix.VIRTIO_NET_HDR_GSO_ECN != 0 &&
gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 &&
gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 {
return fmt.Errorf("virtio GSO_ECN qualifier on non-TCP GSO type %#x", hdr.GSOType)
} }
switch gsoType { switch gsoType {
case unix.VIRTIO_NET_HDR_GSO_TCPV4: case unix.VIRTIO_NET_HDR_GSO_TCPV4:
if ipVersion != 4 { if ipVersion != 4 {
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType) return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.gsoType)
} }
case unix.VIRTIO_NET_HDR_GSO_TCPV6: case unix.VIRTIO_NET_HDR_GSO_TCPV6:
if ipVersion != 6 { if ipVersion != 6 {
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType) return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.gsoType)
} }
case unix.VIRTIO_NET_HDR_GSO_UDP_L4: case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
// USO carries either v4 or v6; the leading nibble disambiguates. // USO carries either v4 or v6; the leading nibble disambiguates.
if !(ipVersion == 4 || ipVersion == 6) { if !(ipVersion == 4 || ipVersion == 6) {
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType) return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.gsoType)
} }
default: default:
if !(ipVersion == 6 || ipVersion == 4) { if !(ipVersion == 6 || ipVersion == 4) {
return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.GSOType) return fmt.Errorf("invalid IP version %d for GSO type %d", ipVersion, hdr.gsoType)
} }
} }
@@ -128,7 +122,7 @@ func CorrectHdrLen(pkt []byte, hdr *Hdr) error {
// FORWARD path. Instead, parse the transport header length and add it onto // FORWARD path. Instead, parse the transport header length and add it onto
// csumStart, which is synonymous for IP header length. // csumStart, which is synonymous for IP header length.
if hdr.GSOType == unix.VIRTIO_NET_HDR_GSO_UDP_L4 { if hdr.GSOType() == unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
hdr.HdrLen = hdr.CsumStart + 8 hdr.HdrLen = hdr.CsumStart + 8
} else { } else {
if len(pkt) <= int(hdr.CsumStart+tcpDataOffOff) { if len(pkt) <= int(hdr.CsumStart+tcpDataOffOff) {
@@ -157,19 +151,15 @@ func CorrectHdrLen(pkt []byte, hdr *Hdr) error {
return nil return nil
} }
// SegmentTCP walks a TSO superpacket pkt, yielding each segment as a // SegmentTCP walks a TSO superpacket pkt, yielding each segment as a slice into pkt.
// slice into pkt itself. Per-segment plaintext is laid out by stamping a // Per-segment plaintext is laid out by stamping a copy of the original L3+L4 header into pkt at offset i*gsoSize,
// copy of the original L3+L4 header into pkt at offset i*gsoSize, where it // where it sits immediately before that segment's payload chunk in the original buffer.
// sits immediately before that segment's payload chunk in the original // The stamp is destructive: iter i's header write lands on pkt[i*G : i*G+hdrLen], which is the tail of seg_{i-1}'s payload (already
// buffer. The stamp is destructive but harmless: iter i's header write lands
// on pkt[i*G : i*G+hdrLen], which is the tail of seg_{i-1}'s payload (already
// consumed) and ends exactly where seg_i's payload begins, so it never clobbers // consumed) and ends exactly where seg_i's payload begins, so it never clobbers
// live payload — this holds even when gsoSize < hdrLen. The header bytes are // live payload — this holds even when gsoSize < hdrLen.
// sourced from a pristine snapshot taken before the loop (savedHdr), NOT from // The header bytes are sourced from a pristine snapshot taken before the loop (savedHdr), NOT from pkt[:hdrLen], because when gsoSize < hdrLen the stamps would otherwise
// pkt[:hdrLen], because when gsoSize < hdrLen the stamps would otherwise // overwrite the leading header in place and every stamp after the first would copy corrupted bytes.
// overwrite the leading header in place and every stamp after the first would // pkt is consumed by this call and must not be inspected by the caller after the final yield.
// copy corrupted bytes. pkt is consumed by this call and must not be inspected
// by the caller after the final yield.
func SegmentTCP(pkt []byte, hdrLenU, csumStartU, gsoSizeU uint16, yield func(seg []byte) error) error { func SegmentTCP(pkt []byte, hdrLenU, csumStartU, gsoSizeU uint16, yield func(seg []byte) error) error {
if gsoSizeU == 0 { if gsoSizeU == 0 {
return fmt.Errorf("gso_size is zero") return fmt.Errorf("gso_size is zero")
+19 -17
View File
@@ -226,13 +226,14 @@ func TestCorrectHdrLenChecksumBound(t *testing.T) {
// = 26) accepts. This case FAILS against the CsumStart+CsumStart regression. // = 26) accepts. This case FAILS against the CsumStart+CsumStart regression.
t.Run("valid-small-uso-accepted", func(t *testing.T) { t.Run("valid-small-uso-accepted", func(t *testing.T) {
pkt, _, csumStart := buildUDPv4Super(12) // total len 40 pkt, _, csumStart := buildUDPv4Super(12) // total len 40
hdr := Hdr{ hdr := NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4, unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
GSOSize: 6, // two 6-byte segments 0, /*hdrLen*/
CsumStart: csumStart, 6, /*gsoSize: two 6-byte segments*/
CsumOffset: 6, csumStart, /*csumStart*/
} 6, /*csumOffset*/
)
if err := CorrectHdrLen(pkt, &hdr); err != nil { if err := CorrectHdrLen(pkt, &hdr); err != nil {
t.Fatalf("CorrectHdrLen rejected a valid 40-byte USO superpacket: %v", err) t.Fatalf("CorrectHdrLen rejected a valid 40-byte USO superpacket: %v", err)
} }
@@ -247,13 +248,14 @@ func TestCorrectHdrLenChecksumBound(t *testing.T) {
t.Run("too-short-rejected", func(t *testing.T) { t.Run("too-short-rejected", func(t *testing.T) {
pkt := make([]byte, 25) pkt := make([]byte, 25)
pkt[0] = 0x45 // IPv4, IHL 5 pkt[0] = 0x45 // IPv4, IHL 5
hdr := Hdr{ hdr := NewHeader(
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4, unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
GSOSize: 6, 0, /*hdrLen*/
CsumStart: 20, 6, /*gsoSize*/
CsumOffset: 6, 20, /*csumStart*/
} 6, /*csumOffset*/
)
if err := CorrectHdrLen(pkt, &hdr); err == nil { if err := CorrectHdrLen(pkt, &hdr); err == nil {
t.Fatalf("CorrectHdrLen accepted a too-short (25-byte) packet") t.Fatalf("CorrectHdrLen accepted a too-short (25-byte) packet")
} }
@@ -355,7 +357,7 @@ func buildUDPv4Single(payload []byte) (pkt []byte, hdr Hdr) {
pseudo := pseudoHeaderIPv4(pkt[12:16], pkt[16:20], unix.IPPROTO_UDP, udpLen+len(payload)) pseudo := pseudoHeaderIPv4(pkt[12:16], pkt[16:20], unix.IPPROTO_UDP, udpLen+len(payload))
binary.BigEndian.PutUint16(pkt[ipLen+udpChecksumOff:ipLen+udpChecksumOff+2], pseudo) binary.BigEndian.PutUint16(pkt[ipLen+udpChecksumOff:ipLen+udpChecksumOff+2], pseudo)
return pkt, Hdr{Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, CsumStart: ipLen, CsumOffset: udpChecksumOff} return pkt, NewHeader(unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_GSO_NONE, 0, 0, ipLen, udpChecksumOff)
} }
// TestFinishChecksumUDPZeroStoresAllOnes pins RFC 768: a UDP checksum that computes to zero goes on the wire as // TestFinishChecksumUDPZeroStoresAllOnes pins RFC 768: a UDP checksum that computes to zero goes on the wire as
@@ -409,7 +411,7 @@ func TestFinishChecksumTCPZeroPreserved(t *testing.T) {
} }
binary.BigEndian.PutUint16(seg[cs+co:cs+co+2], partial) binary.BigEndian.PutUint16(seg[cs+co:cs+co+2], partial)
hdr := Hdr{Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, CsumStart: cs, CsumOffset: co} hdr := NewHeader(unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, unix.VIRTIO_NET_HDR_GSO_NONE, 0, 0, cs, co)
if err := FinishChecksum(seg, hdr); err != nil { if err := FinishChecksum(seg, hdr); err != nil {
t.Fatalf("FinishChecksum: %v", err) t.Fatalf("FinishChecksum: %v", err)
} }
@@ -457,7 +459,7 @@ func TestCheckValidMasksGSOECN(t *testing.T) {
} }
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
err := CheckValid(tc.pkt, Hdr{GSOType: tc.gsoType}) err := CheckValid(tc.pkt, NewHeader(0, tc.gsoType, 0, 0, 0, 0))
if tc.wantErr && err == nil { if tc.wantErr && err == nil {
t.Errorf("CheckValid(gsoType=%#x) = nil, want error", tc.gsoType) t.Errorf("CheckValid(gsoType=%#x) = nil, want error", tc.gsoType)
} }