mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 12:36:58 +02:00
checkpt
This commit is contained in:
+3
-2
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user