mirror of
https://git.zx2c4.com/wireguard-go
synced 2024-11-10 16:59:17 +00:00
d0bc03c707
Implement UDP GSO and GRO for the Linux tun.Device, which is made possible by virtio extensions in the kernel's TUN driver starting in v6.2. secnetperf, a QUIC benchmark utility from microsoft/msquic@8e1eb1a, is used to demonstrate the effect of this commit between two Linux computers with i5-12400 CPUs. There is roughly ~13us of round trip latency between them. secnetperf was invoked with the following command line options: -stats:1 -exec:maxtput -test:tput -download:10000 -timed:1 -encrypt:0 The first result is from commit 2e0774f without UDP GSO/GRO on the TUN. [conn][0x55739a144980] STATS: EcnCapable=0 RTT=3973 us SendTotalPackets=55859 SendSuspectedLostPackets=61 SendSpuriousLostPackets=59 SendCongestionCount=27 SendEcnCongestionCount=0 RecvTotalPackets=2779122 RecvReorderedPackets=0 RecvDroppedPackets=0 RecvDuplicatePackets=0 RecvDecryptionFailures=0 Result: 3654977571 bytes @ 2922821 kbps (10003.972 ms). The second result is with UDP GSO/GRO on the TUN. [conn][0x56493dfd09a0] STATS: EcnCapable=0 RTT=1216 us SendTotalPackets=165033 SendSuspectedLostPackets=64 SendSpuriousLostPackets=61 SendCongestionCount=53 SendEcnCongestionCount=0 RecvTotalPackets=11845268 RecvReorderedPackets=25267 RecvDroppedPackets=0 RecvDuplicatePackets=0 RecvDecryptionFailures=0 Result: 15574671184 bytes @ 12458214 kbps (10001.222 ms). Signed-off-by: Jordan Whited <jordan@tailscale.com> Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
753 lines
21 KiB
Go
753 lines
21 KiB
Go
/* SPDX-License-Identifier: MIT
|
|
*
|
|
* Copyright (C) 2017-2023 WireGuard LLC. All Rights Reserved.
|
|
*/
|
|
|
|
package tun
|
|
|
|
import (
|
|
"net/netip"
|
|
"testing"
|
|
|
|
"golang.org/x/sys/unix"
|
|
"golang.zx2c4.com/wireguard/conn"
|
|
"gvisor.dev/gvisor/pkg/tcpip"
|
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
|
)
|
|
|
|
const (
|
|
offset = virtioNetHdrLen
|
|
)
|
|
|
|
var (
|
|
ip4PortA = netip.MustParseAddrPort("192.0.2.1:1")
|
|
ip4PortB = netip.MustParseAddrPort("192.0.2.2:1")
|
|
ip4PortC = netip.MustParseAddrPort("192.0.2.3:1")
|
|
ip6PortA = netip.MustParseAddrPort("[2001:db8::1]:1")
|
|
ip6PortB = netip.MustParseAddrPort("[2001:db8::2]:1")
|
|
ip6PortC = netip.MustParseAddrPort("[2001:db8::3]:1")
|
|
)
|
|
|
|
func udp4PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, payloadLen int, ipFn func(*header.IPv4Fields)) []byte {
|
|
totalLen := 28 + payloadLen
|
|
b := make([]byte, offset+int(totalLen), 65535)
|
|
ipv4H := header.IPv4(b[offset:])
|
|
srcAs4 := srcIPPort.Addr().As4()
|
|
dstAs4 := dstIPPort.Addr().As4()
|
|
ipFields := &header.IPv4Fields{
|
|
SrcAddr: tcpip.AddrFromSlice(srcAs4[:]),
|
|
DstAddr: tcpip.AddrFromSlice(dstAs4[:]),
|
|
Protocol: unix.IPPROTO_UDP,
|
|
TTL: 64,
|
|
TotalLength: uint16(totalLen),
|
|
}
|
|
if ipFn != nil {
|
|
ipFn(ipFields)
|
|
}
|
|
ipv4H.Encode(ipFields)
|
|
udpH := header.UDP(b[offset+20:])
|
|
udpH.Encode(&header.UDPFields{
|
|
SrcPort: srcIPPort.Port(),
|
|
DstPort: dstIPPort.Port(),
|
|
Length: uint16(payloadLen + udphLen),
|
|
})
|
|
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
|
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_UDP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(udphLen+payloadLen))
|
|
udpH.SetChecksum(^udpH.CalculateChecksum(pseudoCsum))
|
|
return b
|
|
}
|
|
|
|
func udp6Packet(srcIPPort, dstIPPort netip.AddrPort, payloadLen int) []byte {
|
|
return udp6PacketMutateIPFields(srcIPPort, dstIPPort, payloadLen, nil)
|
|
}
|
|
|
|
func udp6PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, payloadLen int, ipFn func(*header.IPv6Fields)) []byte {
|
|
totalLen := 48 + payloadLen
|
|
b := make([]byte, offset+int(totalLen), 65535)
|
|
ipv6H := header.IPv6(b[offset:])
|
|
srcAs16 := srcIPPort.Addr().As16()
|
|
dstAs16 := dstIPPort.Addr().As16()
|
|
ipFields := &header.IPv6Fields{
|
|
SrcAddr: tcpip.AddrFromSlice(srcAs16[:]),
|
|
DstAddr: tcpip.AddrFromSlice(dstAs16[:]),
|
|
TransportProtocol: unix.IPPROTO_UDP,
|
|
HopLimit: 64,
|
|
PayloadLength: uint16(payloadLen + udphLen),
|
|
}
|
|
if ipFn != nil {
|
|
ipFn(ipFields)
|
|
}
|
|
ipv6H.Encode(ipFields)
|
|
udpH := header.UDP(b[offset+40:])
|
|
udpH.Encode(&header.UDPFields{
|
|
SrcPort: srcIPPort.Port(),
|
|
DstPort: dstIPPort.Port(),
|
|
Length: uint16(payloadLen + udphLen),
|
|
})
|
|
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_UDP, ipv6H.SourceAddress(), ipv6H.DestinationAddress(), uint16(udphLen+payloadLen))
|
|
udpH.SetChecksum(^udpH.CalculateChecksum(pseudoCsum))
|
|
return b
|
|
}
|
|
|
|
func udp4Packet(srcIPPort, dstIPPort netip.AddrPort, payloadLen int) []byte {
|
|
return udp4PacketMutateIPFields(srcIPPort, dstIPPort, payloadLen, nil)
|
|
}
|
|
|
|
func tcp4PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32, ipFn func(*header.IPv4Fields)) []byte {
|
|
totalLen := 40 + segmentSize
|
|
b := make([]byte, offset+int(totalLen), 65535)
|
|
ipv4H := header.IPv4(b[offset:])
|
|
srcAs4 := srcIPPort.Addr().As4()
|
|
dstAs4 := dstIPPort.Addr().As4()
|
|
ipFields := &header.IPv4Fields{
|
|
SrcAddr: tcpip.AddrFromSlice(srcAs4[:]),
|
|
DstAddr: tcpip.AddrFromSlice(dstAs4[:]),
|
|
Protocol: unix.IPPROTO_TCP,
|
|
TTL: 64,
|
|
TotalLength: uint16(totalLen),
|
|
}
|
|
if ipFn != nil {
|
|
ipFn(ipFields)
|
|
}
|
|
ipv4H.Encode(ipFields)
|
|
tcpH := header.TCP(b[offset+20:])
|
|
tcpH.Encode(&header.TCPFields{
|
|
SrcPort: srcIPPort.Port(),
|
|
DstPort: dstIPPort.Port(),
|
|
SeqNum: seq,
|
|
AckNum: 1,
|
|
DataOffset: 20,
|
|
Flags: flags,
|
|
WindowSize: 3000,
|
|
})
|
|
ipv4H.SetChecksum(^ipv4H.CalculateChecksum())
|
|
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv4H.SourceAddress(), ipv4H.DestinationAddress(), uint16(20+segmentSize))
|
|
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
|
return b
|
|
}
|
|
|
|
func tcp4Packet(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32) []byte {
|
|
return tcp4PacketMutateIPFields(srcIPPort, dstIPPort, flags, segmentSize, seq, nil)
|
|
}
|
|
|
|
func tcp6PacketMutateIPFields(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32, ipFn func(*header.IPv6Fields)) []byte {
|
|
totalLen := 60 + segmentSize
|
|
b := make([]byte, offset+int(totalLen), 65535)
|
|
ipv6H := header.IPv6(b[offset:])
|
|
srcAs16 := srcIPPort.Addr().As16()
|
|
dstAs16 := dstIPPort.Addr().As16()
|
|
ipFields := &header.IPv6Fields{
|
|
SrcAddr: tcpip.AddrFromSlice(srcAs16[:]),
|
|
DstAddr: tcpip.AddrFromSlice(dstAs16[:]),
|
|
TransportProtocol: unix.IPPROTO_TCP,
|
|
HopLimit: 64,
|
|
PayloadLength: uint16(segmentSize + 20),
|
|
}
|
|
if ipFn != nil {
|
|
ipFn(ipFields)
|
|
}
|
|
ipv6H.Encode(ipFields)
|
|
tcpH := header.TCP(b[offset+40:])
|
|
tcpH.Encode(&header.TCPFields{
|
|
SrcPort: srcIPPort.Port(),
|
|
DstPort: dstIPPort.Port(),
|
|
SeqNum: seq,
|
|
AckNum: 1,
|
|
DataOffset: 20,
|
|
Flags: flags,
|
|
WindowSize: 3000,
|
|
})
|
|
pseudoCsum := header.PseudoHeaderChecksum(unix.IPPROTO_TCP, ipv6H.SourceAddress(), ipv6H.DestinationAddress(), uint16(20+segmentSize))
|
|
tcpH.SetChecksum(^tcpH.CalculateChecksum(pseudoCsum))
|
|
return b
|
|
}
|
|
|
|
func tcp6Packet(srcIPPort, dstIPPort netip.AddrPort, flags header.TCPFlags, segmentSize, seq uint32) []byte {
|
|
return tcp6PacketMutateIPFields(srcIPPort, dstIPPort, flags, segmentSize, seq, nil)
|
|
}
|
|
|
|
func Test_handleVirtioRead(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
hdr virtioNetHdr
|
|
pktIn []byte
|
|
wantLens []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"tcp4",
|
|
virtioNetHdr{
|
|
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
|
gsoType: unix.VIRTIO_NET_HDR_GSO_TCPV4,
|
|
gsoSize: 100,
|
|
hdrLen: 40,
|
|
csumStart: 20,
|
|
csumOffset: 16,
|
|
},
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck|header.TCPFlagPsh, 200, 1),
|
|
[]int{140, 140},
|
|
false,
|
|
},
|
|
{
|
|
"tcp6",
|
|
virtioNetHdr{
|
|
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
|
gsoType: unix.VIRTIO_NET_HDR_GSO_TCPV6,
|
|
gsoSize: 100,
|
|
hdrLen: 60,
|
|
csumStart: 40,
|
|
csumOffset: 16,
|
|
},
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck|header.TCPFlagPsh, 200, 1),
|
|
[]int{160, 160},
|
|
false,
|
|
},
|
|
{
|
|
"udp4",
|
|
virtioNetHdr{
|
|
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
|
gsoType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
|
gsoSize: 100,
|
|
hdrLen: 28,
|
|
csumStart: 20,
|
|
csumOffset: 6,
|
|
},
|
|
udp4Packet(ip4PortA, ip4PortB, 200),
|
|
[]int{128, 128},
|
|
false,
|
|
},
|
|
{
|
|
"udp6",
|
|
virtioNetHdr{
|
|
flags: unix.VIRTIO_NET_HDR_F_NEEDS_CSUM,
|
|
gsoType: unix.VIRTIO_NET_HDR_GSO_UDP_L4,
|
|
gsoSize: 100,
|
|
hdrLen: 48,
|
|
csumStart: 40,
|
|
csumOffset: 6,
|
|
},
|
|
udp6Packet(ip6PortA, ip6PortB, 200),
|
|
[]int{148, 148},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
out := make([][]byte, conn.IdealBatchSize)
|
|
sizes := make([]int, conn.IdealBatchSize)
|
|
for i := range out {
|
|
out[i] = make([]byte, 65535)
|
|
}
|
|
tt.hdr.encode(tt.pktIn)
|
|
n, err := handleVirtioRead(tt.pktIn, out, sizes, offset)
|
|
if err != nil {
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
t.Fatalf("got err: %v", err)
|
|
}
|
|
if n != len(tt.wantLens) {
|
|
t.Fatalf("got %d packets, wanted %d", n, len(tt.wantLens))
|
|
}
|
|
for i := range tt.wantLens {
|
|
if tt.wantLens[i] != sizes[i] {
|
|
t.Fatalf("wantLens[%d]: %d != outSizes: %d", i, tt.wantLens[i], sizes[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func flipTCP4Checksum(b []byte) []byte {
|
|
at := virtioNetHdrLen + 20 + 16 // 20 byte ipv4 header; tcp csum offset is 16
|
|
b[at] ^= 0xFF
|
|
b[at+1] ^= 0xFF
|
|
return b
|
|
}
|
|
|
|
func flipUDP4Checksum(b []byte) []byte {
|
|
at := virtioNetHdrLen + 20 + 6 // 20 byte ipv4 header; udp csum offset is 6
|
|
b[at] ^= 0xFF
|
|
b[at+1] ^= 0xFF
|
|
return b
|
|
}
|
|
|
|
func Fuzz_handleGRO(f *testing.F) {
|
|
pkt0 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)
|
|
pkt1 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101)
|
|
pkt2 := tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201)
|
|
pkt3 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1)
|
|
pkt4 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101)
|
|
pkt5 := tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201)
|
|
pkt6 := udp4Packet(ip4PortA, ip4PortB, 100)
|
|
pkt7 := udp4Packet(ip4PortA, ip4PortB, 100)
|
|
pkt8 := udp4Packet(ip4PortA, ip4PortC, 100)
|
|
pkt9 := udp6Packet(ip6PortA, ip6PortB, 100)
|
|
pkt10 := udp6Packet(ip6PortA, ip6PortB, 100)
|
|
pkt11 := udp6Packet(ip6PortA, ip6PortC, 100)
|
|
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11, true, offset)
|
|
f.Fuzz(func(t *testing.T, pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11 []byte, canUDPGRO bool, offset int) {
|
|
pkts := [][]byte{pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11}
|
|
toWrite := make([]int, 0, len(pkts))
|
|
handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), canUDPGRO, &toWrite)
|
|
if len(toWrite) > len(pkts) {
|
|
t.Errorf("len(toWrite): %d > len(pkts): %d", len(toWrite), len(pkts))
|
|
}
|
|
seenWriteI := make(map[int]bool)
|
|
for _, writeI := range toWrite {
|
|
if writeI < 0 || writeI > len(pkts)-1 {
|
|
t.Errorf("toWrite value (%d) outside bounds of len(pkts): %d", writeI, len(pkts))
|
|
}
|
|
if seenWriteI[writeI] {
|
|
t.Errorf("duplicate toWrite value: %d", writeI)
|
|
}
|
|
seenWriteI[writeI] = true
|
|
}
|
|
})
|
|
}
|
|
|
|
func Test_handleGRO(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
pktsIn [][]byte
|
|
canUDPGRO bool
|
|
wantToWrite []int
|
|
wantLens []int
|
|
wantErr bool
|
|
}{
|
|
{
|
|
"multiple protocols and flows",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // tcp4 flow 1
|
|
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
|
udp4Packet(ip4PortA, ip4PortC, 100), // udp4 flow 2
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // tcp4 flow 1
|
|
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // tcp4 flow 2
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // tcp6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // tcp6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // tcp6 flow 2
|
|
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
|
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
|
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 4, 5, 7, 9},
|
|
[]int{240, 228, 128, 140, 260, 160, 248},
|
|
false,
|
|
},
|
|
{
|
|
"multiple protocols and flows no UDP GRO",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // tcp4 flow 1
|
|
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
|
udp4Packet(ip4PortA, ip4PortC, 100), // udp4 flow 2
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // tcp4 flow 1
|
|
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // tcp4 flow 2
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // tcp6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // tcp6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // tcp6 flow 2
|
|
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
|
|
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
|
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
|
|
},
|
|
false,
|
|
[]int{0, 1, 2, 4, 5, 7, 8, 9, 10},
|
|
[]int{240, 128, 128, 140, 260, 160, 128, 148, 148},
|
|
false,
|
|
},
|
|
{
|
|
"PSH interleaved",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck|header.TCPFlagPsh, 100, 101), // v4 flow 1
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 301), // v4 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // v6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck|header.TCPFlagPsh, 100, 101), // v6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 201), // v6 flow 1
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 301), // v6 flow 1
|
|
},
|
|
true,
|
|
[]int{0, 2, 4, 6},
|
|
[]int{240, 240, 260, 260},
|
|
false,
|
|
},
|
|
{
|
|
"coalesceItemInvalidCSum",
|
|
[][]byte{
|
|
flipTCP4Checksum(tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)), // v4 flow 1 seq 1 len 100
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // v4 flow 1 seq 101 len 100
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
|
|
flipUDP4Checksum(udp4Packet(ip4PortA, ip4PortB, 100)),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
},
|
|
true,
|
|
[]int{0, 1, 3, 4},
|
|
[]int{140, 240, 128, 228},
|
|
false,
|
|
},
|
|
{
|
|
"out of order",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // v4 flow 1 seq 101 len 100
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1 seq 1 len 100
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
|
|
},
|
|
true,
|
|
[]int{0},
|
|
[]int{340},
|
|
false,
|
|
},
|
|
{
|
|
"unequal TTL",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
|
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
|
fields.TTL++
|
|
}),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
|
fields.TTL++
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{140, 140, 128, 128},
|
|
false,
|
|
},
|
|
{
|
|
"unequal ToS",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
|
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
|
fields.TOS++
|
|
}),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
|
fields.TOS++
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{140, 140, 128, 128},
|
|
false,
|
|
},
|
|
{
|
|
"unequal flags more fragments set",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
|
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
|
fields.Flags = 1
|
|
}),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
|
fields.Flags = 1
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{140, 140, 128, 128},
|
|
false,
|
|
},
|
|
{
|
|
"unequal flags DF set",
|
|
[][]byte{
|
|
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
|
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
|
fields.Flags = 2
|
|
}),
|
|
udp4Packet(ip4PortA, ip4PortB, 100),
|
|
udp4PacketMutateIPFields(ip4PortA, ip4PortB, 100, func(fields *header.IPv4Fields) {
|
|
fields.Flags = 2
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{140, 140, 128, 128},
|
|
false,
|
|
},
|
|
{
|
|
"ipv6 unequal hop limit",
|
|
[][]byte{
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
|
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
|
fields.HopLimit++
|
|
}),
|
|
udp6Packet(ip6PortA, ip6PortB, 100),
|
|
udp6PacketMutateIPFields(ip6PortA, ip6PortB, 100, func(fields *header.IPv6Fields) {
|
|
fields.HopLimit++
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{160, 160, 148, 148},
|
|
false,
|
|
},
|
|
{
|
|
"ipv6 unequal traffic class",
|
|
[][]byte{
|
|
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
|
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
|
fields.TrafficClass++
|
|
}),
|
|
udp6Packet(ip6PortA, ip6PortB, 100),
|
|
udp6PacketMutateIPFields(ip6PortA, ip6PortB, 100, func(fields *header.IPv6Fields) {
|
|
fields.TrafficClass++
|
|
}),
|
|
},
|
|
true,
|
|
[]int{0, 1, 2, 3},
|
|
[]int{160, 160, 148, 148},
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
toWrite := make([]int, 0, len(tt.pktsIn))
|
|
err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), tt.canUDPGRO, &toWrite)
|
|
if err != nil {
|
|
if tt.wantErr {
|
|
return
|
|
}
|
|
t.Fatalf("got err: %v", err)
|
|
}
|
|
if len(toWrite) != len(tt.wantToWrite) {
|
|
t.Fatalf("got %d packets, wanted %d", len(toWrite), len(tt.wantToWrite))
|
|
}
|
|
for i, pktI := range tt.wantToWrite {
|
|
if tt.wantToWrite[i] != toWrite[i] {
|
|
t.Fatalf("wantToWrite[%d]: %d != toWrite: %d", i, tt.wantToWrite[i], toWrite[i])
|
|
}
|
|
if tt.wantLens[i] != len(tt.pktsIn[pktI][offset:]) {
|
|
t.Errorf("wanted len %d packet at %d, got: %d", tt.wantLens[i], i, len(tt.pktsIn[pktI][offset:]))
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_packetIsGROCandidate(t *testing.T) {
|
|
tcp4 := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)[virtioNetHdrLen:]
|
|
tcp4TooShort := tcp4[:39]
|
|
ip4InvalidHeaderLen := make([]byte, len(tcp4))
|
|
copy(ip4InvalidHeaderLen, tcp4)
|
|
ip4InvalidHeaderLen[0] = 0x46
|
|
ip4InvalidProtocol := make([]byte, len(tcp4))
|
|
copy(ip4InvalidProtocol, tcp4)
|
|
ip4InvalidProtocol[9] = unix.IPPROTO_GRE
|
|
|
|
tcp6 := tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1)[virtioNetHdrLen:]
|
|
tcp6TooShort := tcp6[:59]
|
|
ip6InvalidProtocol := make([]byte, len(tcp6))
|
|
copy(ip6InvalidProtocol, tcp6)
|
|
ip6InvalidProtocol[6] = unix.IPPROTO_GRE
|
|
|
|
udp4 := udp4Packet(ip4PortA, ip4PortB, 100)[virtioNetHdrLen:]
|
|
udp4TooShort := udp4[:27]
|
|
|
|
udp6 := udp6Packet(ip6PortA, ip6PortB, 100)[virtioNetHdrLen:]
|
|
udp6TooShort := udp6[:47]
|
|
|
|
tests := []struct {
|
|
name string
|
|
b []byte
|
|
canUDPGRO bool
|
|
want groCandidateType
|
|
}{
|
|
{
|
|
"tcp4",
|
|
tcp4,
|
|
true,
|
|
tcp4GROCandidate,
|
|
},
|
|
{
|
|
"tcp6",
|
|
tcp6,
|
|
true,
|
|
tcp6GROCandidate,
|
|
},
|
|
{
|
|
"udp4",
|
|
udp4,
|
|
true,
|
|
udp4GROCandidate,
|
|
},
|
|
{
|
|
"udp4 no support",
|
|
udp4,
|
|
false,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"udp6",
|
|
udp6,
|
|
true,
|
|
udp6GROCandidate,
|
|
},
|
|
{
|
|
"udp6 no support",
|
|
udp6,
|
|
false,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"udp4 too short",
|
|
udp4TooShort,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"udp6 too short",
|
|
udp6TooShort,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"tcp4 too short",
|
|
tcp4TooShort,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"tcp6 too short",
|
|
tcp6TooShort,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"invalid IP version",
|
|
[]byte{0x00},
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"invalid IP header len",
|
|
ip4InvalidHeaderLen,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"ip4 invalid protocol",
|
|
ip4InvalidProtocol,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
{
|
|
"ip6 invalid protocol",
|
|
ip6InvalidProtocol,
|
|
true,
|
|
notGROCandidate,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := packetIsGROCandidate(tt.b, tt.canUDPGRO); got != tt.want {
|
|
t.Errorf("packetIsGROCandidate() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_udpPacketsCanCoalesce(t *testing.T) {
|
|
udp4a := udp4Packet(ip4PortA, ip4PortB, 100)
|
|
udp4b := udp4Packet(ip4PortA, ip4PortB, 100)
|
|
udp4c := udp4Packet(ip4PortA, ip4PortB, 110)
|
|
|
|
type args struct {
|
|
pkt []byte
|
|
iphLen uint8
|
|
gsoSize uint16
|
|
item udpGROItem
|
|
bufs [][]byte
|
|
bufsOffset int
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want canCoalesce
|
|
}{
|
|
{
|
|
"coalesceAppend equal gso",
|
|
args{
|
|
pkt: udp4a[offset:],
|
|
iphLen: 20,
|
|
gsoSize: 100,
|
|
item: udpGROItem{
|
|
gsoSize: 100,
|
|
iphLen: 20,
|
|
},
|
|
bufs: [][]byte{
|
|
udp4a,
|
|
udp4b,
|
|
},
|
|
bufsOffset: offset,
|
|
},
|
|
coalesceAppend,
|
|
},
|
|
{
|
|
"coalesceAppend smaller gso",
|
|
args{
|
|
pkt: udp4a[offset : len(udp4a)-90],
|
|
iphLen: 20,
|
|
gsoSize: 10,
|
|
item: udpGROItem{
|
|
gsoSize: 100,
|
|
iphLen: 20,
|
|
},
|
|
bufs: [][]byte{
|
|
udp4a,
|
|
udp4b,
|
|
},
|
|
bufsOffset: offset,
|
|
},
|
|
coalesceAppend,
|
|
},
|
|
{
|
|
"coalesceUnavailable smaller gso previously appended",
|
|
args{
|
|
pkt: udp4a[offset:],
|
|
iphLen: 20,
|
|
gsoSize: 100,
|
|
item: udpGROItem{
|
|
gsoSize: 100,
|
|
iphLen: 20,
|
|
},
|
|
bufs: [][]byte{
|
|
udp4c,
|
|
udp4b,
|
|
},
|
|
bufsOffset: offset,
|
|
},
|
|
coalesceUnavailable,
|
|
},
|
|
{
|
|
"coalesceUnavailable larger following smaller",
|
|
args{
|
|
pkt: udp4c[offset:],
|
|
iphLen: 20,
|
|
gsoSize: 110,
|
|
item: udpGROItem{
|
|
gsoSize: 100,
|
|
iphLen: 20,
|
|
},
|
|
bufs: [][]byte{
|
|
udp4a,
|
|
udp4c,
|
|
},
|
|
bufsOffset: offset,
|
|
},
|
|
coalesceUnavailable,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := udpPacketsCanCoalesce(tt.args.pkt, tt.args.iphLen, tt.args.gsoSize, tt.args.item, tt.args.bufs, tt.args.bufsOffset); got != tt.want {
|
|
t.Errorf("udpPacketsCanCoalesce() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|