mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-16 00:47:02 +02:00
tio: accept VIRTIO_NET_HDR_GSO_ECN-qualified superpackets
TUN_F_TSO_ECN is negotiated, so once ECN feedback flows the kernel hands us TSO superpackets typed TCPV4|GSO_ECN (CWR set). protoFromGSOType treated the qualifier bit as an unknown type and the read path dropped every such superpacket - a latent bug that only fires when a congested hop CE-marks the flow, exactly when drops hurt most. The segmenter already handles CWR (first segment only); just mask the bit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,8 +14,15 @@ import (
|
|||||||
// 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 {
|
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:
|
||||||
return GSOProtoTCP, nil
|
return GSOProtoTCP, nil
|
||||||
case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
|
case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
|
||||||
|
|||||||
@@ -19,6 +19,36 @@ import (
|
|||||||
// worst-case 64 KiB superpacket plus replicated per-segment headers).
|
// worst-case 64 KiB superpacket plus replicated per-segment headers).
|
||||||
const testSegScratchSize = 192 * 1024
|
const testSegScratchSize = 192 * 1024
|
||||||
|
|
||||||
|
// TestProtoFromGSOTypeMasksECN guards the CWR-superpacket drop bug: the
|
||||||
|
// kernel qualifies a TSO superpacket whose TCP header carries CWR with
|
||||||
|
// VIRTIO_NET_HDR_GSO_ECN (we negotiate TUN_F_TSO_ECN, so it WILL send
|
||||||
|
// them once ECN feedback flows), and the decoder must mask that bit
|
||||||
|
// rather than reject the packet as an unknown type.
|
||||||
|
func TestProtoFromGSOTypeMasksECN(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
typ uint8
|
||||||
|
want GSOProto
|
||||||
|
}{
|
||||||
|
{unix.VIRTIO_NET_HDR_GSO_TCPV4, GSOProtoTCP},
|
||||||
|
{unix.VIRTIO_NET_HDR_GSO_TCPV4 | unix.VIRTIO_NET_HDR_GSO_ECN, GSOProtoTCP},
|
||||||
|
{unix.VIRTIO_NET_HDR_GSO_TCPV6, GSOProtoTCP},
|
||||||
|
{unix.VIRTIO_NET_HDR_GSO_TCPV6 | unix.VIRTIO_NET_HDR_GSO_ECN, GSOProtoTCP},
|
||||||
|
{unix.VIRTIO_NET_HDR_GSO_UDP_L4, GSOProtoUDP},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
got, err := protoFromGSOType(c.typ)
|
||||||
|
if err != nil || got != c.want {
|
||||||
|
t.Errorf("protoFromGSOType(%#x) = (%v, %v), want (%v, nil)", c.typ, got, err, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if _, err := protoFromGSOType(unix.VIRTIO_NET_HDR_GSO_NONE); err == nil {
|
||||||
|
t.Error("GSO_NONE must still be rejected")
|
||||||
|
}
|
||||||
|
if _, err := protoFromGSOType(unix.VIRTIO_NET_HDR_GSO_ECN); err == nil {
|
||||||
|
t.Error("a bare ECN bit with no base type must still be rejected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// verifyChecksum confirms that the one's-complement sum across `b`, seeded
|
// verifyChecksum confirms that the one's-complement sum across `b`, seeded
|
||||||
// with a folded pseudo-header sum, equals all-ones (valid).
|
// with a folded pseudo-header sum, equals all-ones (valid).
|
||||||
func verifyChecksum(b []byte, pseudo uint16) bool {
|
func verifyChecksum(b []byte, pseudo uint16) bool {
|
||||||
|
|||||||
Reference in New Issue
Block a user