mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 10:16:59 +02:00
checkpt
This commit is contained in:
+3
-2
@@ -119,7 +119,8 @@ func QueueCapabilities(q Queue) Capabilities {
|
||||
type GSOProto uint8
|
||||
|
||||
const (
|
||||
GSOProtoTCP GSOProto = iota
|
||||
GSOProtoUnknown GSOProto = iota
|
||||
GSOProtoTCP
|
||||
GSOProtoUDP
|
||||
)
|
||||
|
||||
@@ -138,7 +139,7 @@ const (
|
||||
// full superpacket payload; they are read-only from the writer's
|
||||
// perspective and must remain valid until the call returns. Every segment
|
||||
// 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
|
||||
// QueueCapabilities) for the per-protocol negotiated capability; an
|
||||
|
||||
@@ -221,13 +221,11 @@ func (r *Offload) Read() ([]Packet, error) {
|
||||
return r.pending, nil
|
||||
}
|
||||
|
||||
// decodeRead processes the packet sitting in rxBuf at rxOff (length
|
||||
// pktLen). The bytes stay in rxBuf — 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
|
||||
// caller can segment lazily at encrypt time. rxOff advances past the
|
||||
// kernel-supplied body and nothing else, since segmentation no longer
|
||||
// writes back into rxBuf.
|
||||
// decodeRead processes the packet sitting in rxBuf at rxOff (length pktLen).
|
||||
// The bytes stay in rxBuf:
|
||||
// * 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 caller can segment lazily at encrypt time.
|
||||
// rxOff advances by pktLen on success
|
||||
func (r *Offload) decodeRead(pktLen int) error {
|
||||
if pktLen <= 0 {
|
||||
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]
|
||||
|
||||
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 err := virtio.FinishChecksum(body, hdr); err != nil {
|
||||
return err
|
||||
@@ -258,7 +256,7 @@ func (r *Offload) decodeRead(pktLen int) error {
|
||||
if err := virtio.CorrectHdrLen(body, &hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
proto, err := protoFromGSOType(hdr.GSOType)
|
||||
proto, err := protoFromGSOType(hdr.GSOType())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -384,24 +382,26 @@ func (r *Offload) WriteGSO(hdr []byte, transportHdr []byte, pays [][]byte, proto
|
||||
if 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.
|
||||
vhdr := virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
HdrLen: uint16(len(hdr) + len(transportHdr)),
|
||||
CsumStart: uint16(len(hdr)),
|
||||
CsumOffset: csumOff,
|
||||
}
|
||||
// gsoType and GSOSize stay zero (GSO_NONE, 0) for single-segment, or an unknown IP version.
|
||||
vhdr := virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_NONE, /*gsoType*/
|
||||
uint16(len(hdr)+len(transportHdr)), /*hdrLen*/
|
||||
0, /*gsoSize*/
|
||||
uint16(len(hdr)), /*csumStart*/
|
||||
csumOff, /*csumOffset*/
|
||||
)
|
||||
if segCount > 1 {
|
||||
ipVer := hdr[0] >> 4
|
||||
switch {
|
||||
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:
|
||||
vhdr.GSOType = unix.VIRTIO_NET_HDR_GSO_TCPV6
|
||||
vhdr.SetGSOType(unix.VIRTIO_NET_HDR_GSO_TCPV6)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,9 @@ import (
|
||||
"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
|
||||
// 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.
|
||||
// value. The caller should only invoke this on a confirmed superpacket.
|
||||
func protoFromGSOType(t uint8) (GSOProto, error) {
|
||||
switch t &^ unix.VIRTIO_NET_HDR_GSO_ECN {
|
||||
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
|
||||
// fn is called once with pkt.Bytes (no segmentation, no copy). For GSO/USO
|
||||
// superpackets fn is called once per segment with a slice of pkt.Bytes
|
||||
// holding that segment's plaintext (a freshly-patched L3+L4 header sliced
|
||||
// in front of the original payload chunk). The slide is destructive: pkt is
|
||||
// consumed by this call and its bytes are in an undefined state when
|
||||
// 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.
|
||||
// SegmentSuperpacket invokes fn once per segment of pkt.
|
||||
// For non-GSO pkts fn is called once with pkt.Bytes.
|
||||
// For GSO/USO superpackets, fn is called once per segment with a slice of pkt.Bytes holding that segment's plaintext
|
||||
// (a freshly-patched L3+L4 header sliced in front of the original payload chunk).
|
||||
// This slicing is destructive: pkt is consumed by this call.
|
||||
// Aborts and returns the first error from fn or from per-segment construction.
|
||||
func SegmentSuperpacket(pkt Packet, fn func(seg []byte) error) error {
|
||||
if !pkt.GSO.IsSuperpacket() {
|
||||
return fn(pkt.Bytes)
|
||||
|
||||
@@ -63,7 +63,7 @@ func verifyChecksum(b []byte, pseudo uint16) bool {
|
||||
// returns. Tests pre-set hdr.HdrLen correctly, so correctHdrLen is not
|
||||
// invoked here.
|
||||
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...)
|
||||
if hdr.Flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
|
||||
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)
|
||||
return nil
|
||||
}
|
||||
proto, err := protoFromGSOType(hdr.GSOType)
|
||||
proto, err := protoFromGSOType(hdr.GSOType())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -140,15 +140,14 @@ func buildTSOv4(t *testing.T, payLen, mss int) ([]byte, virtio.Hdr) {
|
||||
for i := 0; i < payLen; i++ {
|
||||
pkt[ipLen+tcpLen+i] = byte(i & 0xff)
|
||||
}
|
||||
|
||||
return pkt, virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
|
||||
HdrLen: uint16(ipLen + tcpLen),
|
||||
GSOSize: uint16(mss),
|
||||
CsumStart: uint16(ipLen),
|
||||
CsumOffset: 16,
|
||||
}
|
||||
return pkt, virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
|
||||
uint16(ipLen+tcpLen), /*hdrLen*/
|
||||
uint16(mss), /*gsoSize*/
|
||||
uint16(ipLen), /*csumStart*/
|
||||
16, /*csumOffset*/
|
||||
)
|
||||
}
|
||||
|
||||
func TestSegmentTCPv4(t *testing.T) {
|
||||
@@ -262,14 +261,14 @@ func TestSegmentTCPv6(t *testing.T) {
|
||||
pkt[ipLen+tcpLen+i] = byte(i)
|
||||
}
|
||||
|
||||
hdr := virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
|
||||
HdrLen: uint16(ipLen + tcpLen),
|
||||
GSOSize: uint16(mss),
|
||||
CsumStart: uint16(ipLen),
|
||||
CsumOffset: 16,
|
||||
}
|
||||
hdr := virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
|
||||
uint16(ipLen+tcpLen), /*hdrLen*/
|
||||
uint16(mss), /*gsoSize*/
|
||||
uint16(ipLen), /*csumStart*/
|
||||
16, /*csumOffset*/
|
||||
)
|
||||
|
||||
scratch := make([]byte, testSegScratchSize)
|
||||
var out [][]byte
|
||||
@@ -311,7 +310,7 @@ func TestSegmentTCPv6(t *testing.T) {
|
||||
|
||||
func TestSegmentGSONonePassesThrough(t *testing.T) {
|
||||
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
|
||||
|
||||
scratch := make([]byte, testSegScratchSize)
|
||||
@@ -330,7 +329,7 @@ func TestSegmentGSONonePassesThrough(t *testing.T) {
|
||||
// TestSegmentRejectsLegacyUDPGSO ensures the legacy GSO_UDP (UFO) marker is
|
||||
// still rejected; only modern GSO_UDP_L4 (USO) is supported.
|
||||
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
|
||||
if err := segmentForTest(nil, hdr, &out, nil); err == nil {
|
||||
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)
|
||||
}
|
||||
|
||||
return pkt, virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
HdrLen: uint16(ipLen + udpLen),
|
||||
GSOSize: uint16(gsoSize),
|
||||
CsumStart: uint16(ipLen),
|
||||
CsumOffset: 6,
|
||||
}
|
||||
return pkt, virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
|
||||
uint16(ipLen+udpLen), /*hdrLen*/
|
||||
uint16(gsoSize), /*gsoSize*/
|
||||
uint16(ipLen), /*csumStart*/
|
||||
6, /*csumOffset*/
|
||||
)
|
||||
}
|
||||
|
||||
func TestSegmentUDPv4(t *testing.T) {
|
||||
@@ -471,14 +470,14 @@ func TestSegmentUDPv6(t *testing.T) {
|
||||
pkt[ipLen+udpLen+i] = byte(i)
|
||||
}
|
||||
|
||||
hdr := virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
HdrLen: uint16(ipLen + udpLen),
|
||||
GSOSize: uint16(gso),
|
||||
CsumStart: uint16(ipLen),
|
||||
CsumOffset: 6,
|
||||
}
|
||||
hdr := virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
|
||||
uint16(ipLen+udpLen), /*hdrLen*/
|
||||
uint16(gso), /*gsoSize*/
|
||||
uint16(ipLen), /*csumStart*/
|
||||
6, /*csumOffset*/
|
||||
)
|
||||
|
||||
scratch := make([]byte, testSegScratchSize)
|
||||
var out [][]byte
|
||||
@@ -610,14 +609,14 @@ func BenchmarkSegmentTCPv4(b *testing.B) {
|
||||
for i := 0; i < sz.payLen; i++ {
|
||||
pkt[ipLen+tcpLen+i] = byte(i)
|
||||
}
|
||||
hdr := virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
|
||||
HdrLen: uint16(ipLen + tcpLen),
|
||||
GSOSize: uint16(sz.mss),
|
||||
CsumStart: uint16(ipLen),
|
||||
CsumOffset: 16,
|
||||
}
|
||||
hdr := virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_TCPV4, /*gsoType*/
|
||||
uint16(ipLen+tcpLen), /*hdrLen*/
|
||||
uint16(sz.mss), /*gsoSize*/
|
||||
uint16(ipLen), /*csumStart*/
|
||||
16, /*csumOffset*/
|
||||
)
|
||||
|
||||
scratch := make([]byte, testSegScratchSize)
|
||||
out := make([][]byte, 0, 64)
|
||||
@@ -775,14 +774,14 @@ func TestDecodeReadFitsMaxTSOAtDrainThreshold(t *testing.T) {
|
||||
copy(o.rxBuf[o.rxOff:], pkt)
|
||||
|
||||
// Encode the matching virtio_net_hdr.
|
||||
hdr := virtio.Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
|
||||
HdrLen: uint16(headerLen),
|
||||
GSOSize: uint16(gsoSize),
|
||||
CsumStart: uint16(ipv6HdrLen),
|
||||
CsumOffset: 16,
|
||||
}
|
||||
hdr := virtio.NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_TCPV6, /*gsoType*/
|
||||
uint16(headerLen), /*hdrLen*/
|
||||
uint16(gsoSize), /*gsoSize*/
|
||||
uint16(ipv6HdrLen), /*csumStart*/
|
||||
16, /*csumOffset*/
|
||||
)
|
||||
hdr.Encode(o.readVnetScratch[:])
|
||||
|
||||
startRxOff := o.rxOff
|
||||
@@ -899,8 +898,8 @@ func TestWriteGSOLeadingEmptyFragmentGeometry(t *testing.T) {
|
||||
}
|
||||
var vhdr virtio.Hdr
|
||||
vhdr.Decode(buf[:virtio.Size])
|
||||
if vhdr.GSOType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
t.Errorf("GSOType=%d want UDP_L4", vhdr.GSOType)
|
||||
if vhdr.GSOType() != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
t.Errorf("gsoType=%d want UDP_L4", vhdr.GSOType())
|
||||
}
|
||||
if vhdr.GSOSize != 1200 {
|
||||
t.Errorf("GSOSize=%d want 1200 (first non-empty fragment)", vhdr.GSOSize)
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
|
||||
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
|
||||
// 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.
|
||||
type Hdr struct {
|
||||
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
|
||||
GSOSize uint16
|
||||
CsumStart 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
|
||||
// call TUNSETVNETLE so the kernel matches our endianness).
|
||||
func (h *Hdr) Decode(b []byte) {
|
||||
h.Flags = b[0]
|
||||
h.GSOType = b[1]
|
||||
h.gsoType = b[1]
|
||||
h.HdrLen = binary.NativeEndian.Uint16(b[2:4])
|
||||
h.GSOSize = binary.NativeEndian.Uint16(b[4:6])
|
||||
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.
|
||||
func (h *Hdr) Encode(b []byte) {
|
||||
b[0] = h.Flags
|
||||
b[1] = h.GSOType
|
||||
b[1] = h.gsoType
|
||||
binary.NativeEndian.PutUint16(b[2:4], h.HdrLen)
|
||||
binary.NativeEndian.PutUint16(b[4:6], h.GSOSize)
|
||||
binary.NativeEndian.PutUint16(b[6:8], h.CsumStart)
|
||||
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
|
||||
}
|
||||
|
||||
@@ -85,33 +85,27 @@ func CheckValid(pkt []byte, hdr Hdr) error {
|
||||
}
|
||||
ipVersion := pkt[0] >> 4
|
||||
|
||||
//mask out VIRTIO_NET_HDR_GSO_ECN, it's a qualifier, not a type
|
||||
gsoType := hdr.GSOType &^ unix.VIRTIO_NET_HDR_GSO_ECN
|
||||
// The ECN qualifier means CWR was set on a TSO superpacket, so it only
|
||||
// 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)
|
||||
gsoType := hdr.GSOType()
|
||||
if hdr.HasECNFlag() && !(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 {
|
||||
case unix.VIRTIO_NET_HDR_GSO_TCPV4:
|
||||
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:
|
||||
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:
|
||||
// USO carries either v4 or v6; the leading nibble disambiguates.
|
||||
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:
|
||||
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
|
||||
// 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
|
||||
} else {
|
||||
if len(pkt) <= int(hdr.CsumStart+tcpDataOffOff) {
|
||||
@@ -157,19 +151,15 @@ func CorrectHdrLen(pkt []byte, hdr *Hdr) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SegmentTCP walks a TSO superpacket pkt, yielding each segment as a
|
||||
// slice into pkt itself. Per-segment plaintext is laid out by stamping a
|
||||
// copy of the original L3+L4 header into pkt at offset i*gsoSize, where it
|
||||
// sits immediately before that segment's payload chunk in the original
|
||||
// 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
|
||||
// SegmentTCP walks a TSO superpacket pkt, yielding each segment as a slice into pkt.
|
||||
// Per-segment plaintext is laid out by stamping a copy of the original L3+L4 header into pkt at offset i*gsoSize,
|
||||
// where it sits immediately before that segment's payload chunk in the original buffer.
|
||||
// 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
|
||||
// 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
|
||||
// sourced from a pristine snapshot taken before the loop (savedHdr), NOT from
|
||||
// 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. pkt is consumed by this call and must not be inspected
|
||||
// by the caller after the final yield.
|
||||
// live payload — this holds even when gsoSize < hdrLen.
|
||||
// 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
|
||||
// overwrite the leading header in place and every stamp after the first would 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 {
|
||||
if gsoSizeU == 0 {
|
||||
return fmt.Errorf("gso_size is zero")
|
||||
|
||||
@@ -226,13 +226,14 @@ func TestCorrectHdrLenChecksumBound(t *testing.T) {
|
||||
// = 26) accepts. This case FAILS against the CsumStart+CsumStart regression.
|
||||
t.Run("valid-small-uso-accepted", func(t *testing.T) {
|
||||
pkt, _, csumStart := buildUDPv4Super(12) // total len 40
|
||||
hdr := Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
GSOSize: 6, // two 6-byte segments
|
||||
CsumStart: csumStart,
|
||||
CsumOffset: 6,
|
||||
}
|
||||
hdr := NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
|
||||
0, /*hdrLen*/
|
||||
6, /*gsoSize: two 6-byte segments*/
|
||||
csumStart, /*csumStart*/
|
||||
6, /*csumOffset*/
|
||||
)
|
||||
if err := CorrectHdrLen(pkt, &hdr); err != nil {
|
||||
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) {
|
||||
pkt := make([]byte, 25)
|
||||
pkt[0] = 0x45 // IPv4, IHL 5
|
||||
hdr := Hdr{
|
||||
Flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
||||
GSOType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
||||
GSOSize: 6,
|
||||
CsumStart: 20,
|
||||
CsumOffset: 6,
|
||||
}
|
||||
hdr := NewHeader(
|
||||
unix.VIRTIO_NET_HDR_F_NEEDS_CSUM, /*flags*/
|
||||
unix.VIRTIO_NET_HDR_GSO_UDP_L4, /*gsoType*/
|
||||
0, /*hdrLen*/
|
||||
6, /*gsoSize*/
|
||||
20, /*csumStart*/
|
||||
6, /*csumOffset*/
|
||||
)
|
||||
if err := CorrectHdrLen(pkt, &hdr); err == nil {
|
||||
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))
|
||||
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
|
||||
@@ -409,7 +411,7 @@ func TestFinishChecksumTCPZeroPreserved(t *testing.T) {
|
||||
}
|
||||
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 {
|
||||
t.Fatalf("FinishChecksum: %v", err)
|
||||
}
|
||||
@@ -457,7 +459,7 @@ func TestCheckValidMasksGSOECN(t *testing.T) {
|
||||
}
|
||||
for _, tc := range cases {
|
||||
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 {
|
||||
t.Errorf("CheckValid(gsoType=%#x) = nil, want error", tc.gsoType)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user