tun: don't assume UDP GRO is supported

Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
Jordan Whited 2023-10-31 18:08:04 -07:00 committed by Jordan Whited
parent 24f8d7c9e7
commit db7604d1aa
3 changed files with 72 additions and 15 deletions

View file

@ -748,7 +748,7 @@ const (
udp6GROCandidate udp6GROCandidate
) )
func packetIsGROCandidate(b []byte) groCandidateType { func packetIsGROCandidate(b []byte, canUDPGRO bool) groCandidateType {
if len(b) < 28 { if len(b) < 28 {
return notGROCandidate return notGROCandidate
} }
@ -760,14 +760,14 @@ func packetIsGROCandidate(b []byte) groCandidateType {
if b[9] == unix.IPPROTO_TCP && len(b) >= 40 { if b[9] == unix.IPPROTO_TCP && len(b) >= 40 {
return tcp4GROCandidate return tcp4GROCandidate
} }
if b[9] == unix.IPPROTO_UDP { if b[9] == unix.IPPROTO_UDP && canUDPGRO {
return udp4GROCandidate return udp4GROCandidate
} }
} else if b[0]>>4 == 6 { } else if b[0]>>4 == 6 {
if b[6] == unix.IPPROTO_TCP && len(b) >= 60 { if b[6] == unix.IPPROTO_TCP && len(b) >= 60 {
return tcp6GROCandidate return tcp6GROCandidate
} }
if b[6] == unix.IPPROTO_UDP && len(b) >= 48 { if b[6] == unix.IPPROTO_UDP && len(b) >= 48 && canUDPGRO {
return udp6GROCandidate return udp6GROCandidate
} }
} }
@ -860,14 +860,15 @@ func udpGRO(bufs [][]byte, offset int, pktI int, table *udpGROTable, isV6 bool)
// handleGRO evaluates bufs for GRO, and writes the indices of the resulting // handleGRO evaluates bufs for GRO, and writes the indices of the resulting
// packets into toWrite. toWrite, tcpTable, and udpTable should initially be // packets into toWrite. toWrite, tcpTable, and udpTable should initially be
// empty (but non-nil), and are passed in to save allocs as the caller may reset // empty (but non-nil), and are passed in to save allocs as the caller may reset
// and recycle them across vectors of packets. // and recycle them across vectors of packets. canUDPGRO indicates if UDP GRO is
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, toWrite *[]int) error { // supported.
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, canUDPGRO bool, toWrite *[]int) error {
for i := range bufs { for i := range bufs {
if offset < virtioNetHdrLen || offset > len(bufs[i])-1 { if offset < virtioNetHdrLen || offset > len(bufs[i])-1 {
return errors.New("invalid offset") return errors.New("invalid offset")
} }
var result groResult var result groResult
switch packetIsGROCandidate(bufs[i][offset:]) { switch packetIsGROCandidate(bufs[i][offset:], canUDPGRO) {
case tcp4GROCandidate: case tcp4GROCandidate:
result = tcpGRO(bufs, offset, i, tcpTable, false) result = tcpGRO(bufs, offset, i, tcpTable, false)
case tcp6GROCandidate: case tcp6GROCandidate:

View file

@ -286,11 +286,11 @@ func Fuzz_handleGRO(f *testing.F) {
pkt9 := udp6Packet(ip6PortA, ip6PortB, 100) pkt9 := udp6Packet(ip6PortA, ip6PortB, 100)
pkt10 := udp6Packet(ip6PortA, ip6PortB, 100) pkt10 := udp6Packet(ip6PortA, ip6PortB, 100)
pkt11 := udp6Packet(ip6PortA, ip6PortC, 100) pkt11 := udp6Packet(ip6PortA, ip6PortC, 100)
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11, offset) 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, offset int) { 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} pkts := [][]byte{pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11}
toWrite := make([]int, 0, len(pkts)) toWrite := make([]int, 0, len(pkts))
handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), &toWrite) handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), canUDPGRO, &toWrite)
if len(toWrite) > len(pkts) { if len(toWrite) > len(pkts) {
t.Errorf("len(toWrite): %d > len(pkts): %d", len(toWrite), len(pkts)) t.Errorf("len(toWrite): %d > len(pkts): %d", len(toWrite), len(pkts))
} }
@ -311,6 +311,7 @@ func Test_handleGRO(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
pktsIn [][]byte pktsIn [][]byte
canUDPGRO bool
wantToWrite []int wantToWrite []int
wantLens []int wantLens []int
wantErr bool wantErr bool
@ -330,10 +331,31 @@ func Test_handleGRO(t *testing.T) {
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1 udp6Packet(ip6PortA, ip6PortB, 100), // udp6 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{0, 1, 2, 4, 5, 7, 9},
[]int{240, 228, 128, 140, 260, 160, 248}, []int{240, 228, 128, 140, 260, 160, 248},
false, 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", "PSH interleaved",
[][]byte{ [][]byte{
@ -346,6 +368,7 @@ func Test_handleGRO(t *testing.T) {
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 201), // v6 flow 1 tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 201), // v6 flow 1
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 301), // v6 flow 1 tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 301), // v6 flow 1
}, },
true,
[]int{0, 2, 4, 6}, []int{0, 2, 4, 6},
[]int{240, 240, 260, 260}, []int{240, 240, 260, 260},
false, false,
@ -360,6 +383,7 @@ func Test_handleGRO(t *testing.T) {
udp4Packet(ip4PortA, ip4PortB, 100), udp4Packet(ip4PortA, ip4PortB, 100),
udp4Packet(ip4PortA, ip4PortB, 100), udp4Packet(ip4PortA, ip4PortB, 100),
}, },
true,
[]int{0, 1, 3, 4}, []int{0, 1, 3, 4},
[]int{140, 240, 128, 228}, []int{140, 240, 128, 228},
false, false,
@ -371,6 +395,7 @@ func Test_handleGRO(t *testing.T) {
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1 seq 1 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 tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
}, },
true,
[]int{0}, []int{0},
[]int{340}, []int{340},
false, false,
@ -387,6 +412,7 @@ func Test_handleGRO(t *testing.T) {
fields.TTL++ fields.TTL++
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{140, 140, 128, 128}, []int{140, 140, 128, 128},
false, false,
@ -403,6 +429,7 @@ func Test_handleGRO(t *testing.T) {
fields.TOS++ fields.TOS++
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{140, 140, 128, 128}, []int{140, 140, 128, 128},
false, false,
@ -419,6 +446,7 @@ func Test_handleGRO(t *testing.T) {
fields.Flags = 1 fields.Flags = 1
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{140, 140, 128, 128}, []int{140, 140, 128, 128},
false, false,
@ -435,6 +463,7 @@ func Test_handleGRO(t *testing.T) {
fields.Flags = 2 fields.Flags = 2
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{140, 140, 128, 128}, []int{140, 140, 128, 128},
false, false,
@ -451,6 +480,7 @@ func Test_handleGRO(t *testing.T) {
fields.HopLimit++ fields.HopLimit++
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{160, 160, 148, 148}, []int{160, 160, 148, 148},
false, false,
@ -467,6 +497,7 @@ func Test_handleGRO(t *testing.T) {
fields.TrafficClass++ fields.TrafficClass++
}), }),
}, },
true,
[]int{0, 1, 2, 3}, []int{0, 1, 2, 3},
[]int{160, 160, 148, 148}, []int{160, 160, 148, 148},
false, false,
@ -476,7 +507,7 @@ func Test_handleGRO(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
toWrite := make([]int, 0, len(tt.pktsIn)) toWrite := make([]int, 0, len(tt.pktsIn))
err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), &toWrite) err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), tt.canUDPGRO, &toWrite)
if err != nil { if err != nil {
if tt.wantErr { if tt.wantErr {
return return
@ -521,74 +552,99 @@ func Test_packetIsGROCandidate(t *testing.T) {
udp6TooShort := udp6[:47] udp6TooShort := udp6[:47]
tests := []struct { tests := []struct {
name string name string
b []byte b []byte
want groCandidateType canUDPGRO bool
want groCandidateType
}{ }{
{ {
"tcp4", "tcp4",
tcp4, tcp4,
true,
tcp4GROCandidate, tcp4GROCandidate,
}, },
{ {
"tcp6", "tcp6",
tcp6, tcp6,
true,
tcp6GROCandidate, tcp6GROCandidate,
}, },
{ {
"udp4", "udp4",
udp4, udp4,
true,
udp4GROCandidate, udp4GROCandidate,
}, },
{
"udp4 no support",
udp4,
false,
notGROCandidate,
},
{ {
"udp6", "udp6",
udp6, udp6,
true,
udp6GROCandidate, udp6GROCandidate,
}, },
{
"udp6 no support",
udp6,
false,
notGROCandidate,
},
{ {
"udp4 too short", "udp4 too short",
udp4TooShort, udp4TooShort,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"udp6 too short", "udp6 too short",
udp6TooShort, udp6TooShort,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"tcp4 too short", "tcp4 too short",
tcp4TooShort, tcp4TooShort,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"tcp6 too short", "tcp6 too short",
tcp6TooShort, tcp6TooShort,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"invalid IP version", "invalid IP version",
[]byte{0x00}, []byte{0x00},
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"invalid IP header len", "invalid IP header len",
ip4InvalidHeaderLen, ip4InvalidHeaderLen,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"ip4 invalid protocol", "ip4 invalid protocol",
ip4InvalidProtocol, ip4InvalidProtocol,
true,
notGROCandidate, notGROCandidate,
}, },
{ {
"ip6 invalid protocol", "ip6 invalid protocol",
ip6InvalidProtocol, ip6InvalidProtocol,
true,
notGROCandidate, notGROCandidate,
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
if got := packetIsGROCandidate(tt.b); got != tt.want { if got := packetIsGROCandidate(tt.b, tt.canUDPGRO); got != tt.want {
t.Errorf("packetIsGROCandidate() = %v, want %v", got, tt.want) t.Errorf("packetIsGROCandidate() = %v, want %v", got, tt.want)
} }
}) })

View file

@ -345,7 +345,7 @@ func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
) )
tun.toWrite = tun.toWrite[:0] tun.toWrite = tun.toWrite[:0]
if tun.vnetHdr { if tun.vnetHdr {
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, &tun.toWrite) err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, tun.udpGSO, &tun.toWrite)
if err != nil { if err != nil {
return 0, err return 0, err
} }