mirror of
https://github.com/slackhq/nebula.git
synced 2026-08-15 11:17:01 +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
|
||||
// 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.
|
||||
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:
|
||||
return GSOProtoTCP, nil
|
||||
case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
|
||||
|
||||
@@ -19,6 +19,36 @@ import (
|
||||
// worst-case 64 KiB superpacket plus replicated per-segment headers).
|
||||
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
|
||||
// with a folded pseudo-header sum, equals all-ones (valid).
|
||||
func verifyChecksum(b []byte, pseudo uint16) bool {
|
||||
|
||||
Reference in New Issue
Block a user