diff --git a/overlay/tio/virtio/segment_linux.go b/overlay/tio/virtio/segment_linux.go index de63ef8b..4577c7d5 100644 --- a/overlay/tio/virtio/segment_linux.go +++ b/overlay/tio/virtio/segment_linux.go @@ -77,10 +77,6 @@ const tcpCwrFlag = 0x80 // cause a downstream miscompute. The TUN should never emit RSC_INFO and // the GSO type must agree with the IP version nibble. func CheckValid(pkt []byte, hdr Hdr) error { - // When RSC_INFO is set the csum_start/csum_offset fields are repurposed to - // carry coalescing info rather than checksum offsets. A TUN writing via - // IFF_VNET_HDR should never emit this, but if it did we would silently - // miscompute the segment checksums — refuse the packet instead. if hdr.Flags&unix.VIRTIO_NET_HDR_F_RSC_INFO != 0 { return fmt.Errorf("virtio RSC_INFO flag not supported on TUN reads") } @@ -88,7 +84,10 @@ func CheckValid(pkt []byte, hdr Hdr) error { return fmt.Errorf("packet too short") } ipVersion := pkt[0] >> 4 - switch hdr.GSOType { + + //mask out VIRTIO_NET_HDR_GSO_ECN, it's a qualifier, not a type + gsoType := hdr.GSOType &^ unix.VIRTIO_NET_HDR_GSO_ECN + 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) diff --git a/overlay/tio/virtio/segment_linux_test.go b/overlay/tio/virtio/segment_linux_test.go index 56e58875..d47fed88 100644 --- a/overlay/tio/virtio/segment_linux_test.go +++ b/overlay/tio/virtio/segment_linux_test.go @@ -430,3 +430,40 @@ func TestFinishChecksumUDPValidates(t *testing.T) { t.Fatal("completed UDP checksum does not validate") } } + +// TestCheckValidMasksGSOECN: GSO_ECN is a qualifier bit the kernel ORs +// into gso_type for TSO superpackets with CWR set. CheckValid must +// validate an ECN-qualified type as its base type — previously TCPV4|ECN +// fell into the default case and skipped the IP-version agreement check. +// The qualifier is TCP-only, so it must be rejected on UDP_L4. +func TestCheckValidMasksGSOECN(t *testing.T) { + v4pkt, _, _ := buildTCPv4Super(100) + v6pkt := make([]byte, len(v4pkt)) + copy(v6pkt, v4pkt) + v6pkt[0] = 0x60 // claim IPv6 + + cases := []struct { + name string + pkt []byte + gsoType uint8 + wantErr bool + }{ + {"tcpv4-ecn-v4", v4pkt, unix.VIRTIO_NET_HDR_GSO_TCPV4 | unix.VIRTIO_NET_HDR_GSO_ECN, false}, + {"tcpv4-ecn-v6-mismatch", v6pkt, unix.VIRTIO_NET_HDR_GSO_TCPV4 | unix.VIRTIO_NET_HDR_GSO_ECN, true}, + {"tcpv6-ecn-v4-mismatch", v4pkt, unix.VIRTIO_NET_HDR_GSO_TCPV6 | unix.VIRTIO_NET_HDR_GSO_ECN, true}, + {"udp-l4-ecn-rejected", v4pkt, unix.VIRTIO_NET_HDR_GSO_UDP_L4 | unix.VIRTIO_NET_HDR_GSO_ECN, true}, + {"tcpv4-plain-v4", v4pkt, unix.VIRTIO_NET_HDR_GSO_TCPV4, false}, + {"tcpv4-plain-v6-mismatch", v6pkt, unix.VIRTIO_NET_HDR_GSO_TCPV4, true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + err := CheckValid(tc.pkt, Hdr{GSOType: tc.gsoType}) + if tc.wantErr && err == nil { + t.Errorf("CheckValid(gsoType=%#x) = nil, want error", tc.gsoType) + } + if !tc.wantErr && err != nil { + t.Errorf("CheckValid(gsoType=%#x) = %v, want nil", tc.gsoType, err) + } + }) + } +}