Skip to content

Commit db7604d

Browse files
committed
tun: don't assume UDP GRO is supported
Signed-off-by: Jordan Whited <[email protected]>
1 parent 24f8d7c commit db7604d

File tree

3 files changed

+72
-15
lines changed

3 files changed

+72
-15
lines changed

tun/offload_linux.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,7 @@ const (
748748
udp6GROCandidate
749749
)
750750

751-
func packetIsGROCandidate(b []byte) groCandidateType {
751+
func packetIsGROCandidate(b []byte, canUDPGRO bool) groCandidateType {
752752
if len(b) < 28 {
753753
return notGROCandidate
754754
}
@@ -760,14 +760,14 @@ func packetIsGROCandidate(b []byte) groCandidateType {
760760
if b[9] == unix.IPPROTO_TCP && len(b) >= 40 {
761761
return tcp4GROCandidate
762762
}
763-
if b[9] == unix.IPPROTO_UDP {
763+
if b[9] == unix.IPPROTO_UDP && canUDPGRO {
764764
return udp4GROCandidate
765765
}
766766
} else if b[0]>>4 == 6 {
767767
if b[6] == unix.IPPROTO_TCP && len(b) >= 60 {
768768
return tcp6GROCandidate
769769
}
770-
if b[6] == unix.IPPROTO_UDP && len(b) >= 48 {
770+
if b[6] == unix.IPPROTO_UDP && len(b) >= 48 && canUDPGRO {
771771
return udp6GROCandidate
772772
}
773773
}
@@ -860,14 +860,15 @@ func udpGRO(bufs [][]byte, offset int, pktI int, table *udpGROTable, isV6 bool)
860860
// handleGRO evaluates bufs for GRO, and writes the indices of the resulting
861861
// packets into toWrite. toWrite, tcpTable, and udpTable should initially be
862862
// empty (but non-nil), and are passed in to save allocs as the caller may reset
863-
// and recycle them across vectors of packets.
864-
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, toWrite *[]int) error {
863+
// and recycle them across vectors of packets. canUDPGRO indicates if UDP GRO is
864+
// supported.
865+
func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGROTable, canUDPGRO bool, toWrite *[]int) error {
865866
for i := range bufs {
866867
if offset < virtioNetHdrLen || offset > len(bufs[i])-1 {
867868
return errors.New("invalid offset")
868869
}
869870
var result groResult
870-
switch packetIsGROCandidate(bufs[i][offset:]) {
871+
switch packetIsGROCandidate(bufs[i][offset:], canUDPGRO) {
871872
case tcp4GROCandidate:
872873
result = tcpGRO(bufs, offset, i, tcpTable, false)
873874
case tcp6GROCandidate:

tun/offload_linux_test.go

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -286,11 +286,11 @@ func Fuzz_handleGRO(f *testing.F) {
286286
pkt9 := udp6Packet(ip6PortA, ip6PortB, 100)
287287
pkt10 := udp6Packet(ip6PortA, ip6PortB, 100)
288288
pkt11 := udp6Packet(ip6PortA, ip6PortC, 100)
289-
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11, offset)
290-
f.Fuzz(func(t *testing.T, pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11 []byte, offset int) {
289+
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11, true, offset)
290+
f.Fuzz(func(t *testing.T, pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11 []byte, canUDPGRO bool, offset int) {
291291
pkts := [][]byte{pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, pkt6, pkt7, pkt8, pkt9, pkt10, pkt11}
292292
toWrite := make([]int, 0, len(pkts))
293-
handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), &toWrite)
293+
handleGRO(pkts, offset, newTCPGROTable(), newUDPGROTable(), canUDPGRO, &toWrite)
294294
if len(toWrite) > len(pkts) {
295295
t.Errorf("len(toWrite): %d > len(pkts): %d", len(toWrite), len(pkts))
296296
}
@@ -311,6 +311,7 @@ func Test_handleGRO(t *testing.T) {
311311
tests := []struct {
312312
name string
313313
pktsIn [][]byte
314+
canUDPGRO bool
314315
wantToWrite []int
315316
wantLens []int
316317
wantErr bool
@@ -330,10 +331,31 @@ func Test_handleGRO(t *testing.T) {
330331
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
331332
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
332333
},
334+
true,
333335
[]int{0, 1, 2, 4, 5, 7, 9},
334336
[]int{240, 228, 128, 140, 260, 160, 248},
335337
false,
336338
},
339+
{
340+
"multiple protocols and flows no UDP GRO",
341+
[][]byte{
342+
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // tcp4 flow 1
343+
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
344+
udp4Packet(ip4PortA, ip4PortC, 100), // udp4 flow 2
345+
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // tcp4 flow 1
346+
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // tcp4 flow 2
347+
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // tcp6 flow 1
348+
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // tcp6 flow 1
349+
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // tcp6 flow 2
350+
udp4Packet(ip4PortA, ip4PortB, 100), // udp4 flow 1
351+
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
352+
udp6Packet(ip6PortA, ip6PortB, 100), // udp6 flow 1
353+
},
354+
false,
355+
[]int{0, 1, 2, 4, 5, 7, 8, 9, 10},
356+
[]int{240, 128, 128, 140, 260, 160, 128, 148, 148},
357+
false,
358+
},
337359
{
338360
"PSH interleaved",
339361
[][]byte{
@@ -346,6 +368,7 @@ func Test_handleGRO(t *testing.T) {
346368
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 201), // v6 flow 1
347369
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 301), // v6 flow 1
348370
},
371+
true,
349372
[]int{0, 2, 4, 6},
350373
[]int{240, 240, 260, 260},
351374
false,
@@ -360,6 +383,7 @@ func Test_handleGRO(t *testing.T) {
360383
udp4Packet(ip4PortA, ip4PortB, 100),
361384
udp4Packet(ip4PortA, ip4PortB, 100),
362385
},
386+
true,
363387
[]int{0, 1, 3, 4},
364388
[]int{140, 240, 128, 228},
365389
false,
@@ -371,6 +395,7 @@ func Test_handleGRO(t *testing.T) {
371395
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1 seq 1 len 100
372396
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 201), // v4 flow 1 seq 201 len 100
373397
},
398+
true,
374399
[]int{0},
375400
[]int{340},
376401
false,
@@ -387,6 +412,7 @@ func Test_handleGRO(t *testing.T) {
387412
fields.TTL++
388413
}),
389414
},
415+
true,
390416
[]int{0, 1, 2, 3},
391417
[]int{140, 140, 128, 128},
392418
false,
@@ -403,6 +429,7 @@ func Test_handleGRO(t *testing.T) {
403429
fields.TOS++
404430
}),
405431
},
432+
true,
406433
[]int{0, 1, 2, 3},
407434
[]int{140, 140, 128, 128},
408435
false,
@@ -419,6 +446,7 @@ func Test_handleGRO(t *testing.T) {
419446
fields.Flags = 1
420447
}),
421448
},
449+
true,
422450
[]int{0, 1, 2, 3},
423451
[]int{140, 140, 128, 128},
424452
false,
@@ -435,6 +463,7 @@ func Test_handleGRO(t *testing.T) {
435463
fields.Flags = 2
436464
}),
437465
},
466+
true,
438467
[]int{0, 1, 2, 3},
439468
[]int{140, 140, 128, 128},
440469
false,
@@ -451,6 +480,7 @@ func Test_handleGRO(t *testing.T) {
451480
fields.HopLimit++
452481
}),
453482
},
483+
true,
454484
[]int{0, 1, 2, 3},
455485
[]int{160, 160, 148, 148},
456486
false,
@@ -467,6 +497,7 @@ func Test_handleGRO(t *testing.T) {
467497
fields.TrafficClass++
468498
}),
469499
},
500+
true,
470501
[]int{0, 1, 2, 3},
471502
[]int{160, 160, 148, 148},
472503
false,
@@ -476,7 +507,7 @@ func Test_handleGRO(t *testing.T) {
476507
for _, tt := range tests {
477508
t.Run(tt.name, func(t *testing.T) {
478509
toWrite := make([]int, 0, len(tt.pktsIn))
479-
err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), &toWrite)
510+
err := handleGRO(tt.pktsIn, offset, newTCPGROTable(), newUDPGROTable(), tt.canUDPGRO, &toWrite)
480511
if err != nil {
481512
if tt.wantErr {
482513
return
@@ -521,74 +552,99 @@ func Test_packetIsGROCandidate(t *testing.T) {
521552
udp6TooShort := udp6[:47]
522553

523554
tests := []struct {
524-
name string
525-
b []byte
526-
want groCandidateType
555+
name string
556+
b []byte
557+
canUDPGRO bool
558+
want groCandidateType
527559
}{
528560
{
529561
"tcp4",
530562
tcp4,
563+
true,
531564
tcp4GROCandidate,
532565
},
533566
{
534567
"tcp6",
535568
tcp6,
569+
true,
536570
tcp6GROCandidate,
537571
},
538572
{
539573
"udp4",
540574
udp4,
575+
true,
541576
udp4GROCandidate,
542577
},
578+
{
579+
"udp4 no support",
580+
udp4,
581+
false,
582+
notGROCandidate,
583+
},
543584
{
544585
"udp6",
545586
udp6,
587+
true,
546588
udp6GROCandidate,
547589
},
590+
{
591+
"udp6 no support",
592+
udp6,
593+
false,
594+
notGROCandidate,
595+
},
548596
{
549597
"udp4 too short",
550598
udp4TooShort,
599+
true,
551600
notGROCandidate,
552601
},
553602
{
554603
"udp6 too short",
555604
udp6TooShort,
605+
true,
556606
notGROCandidate,
557607
},
558608
{
559609
"tcp4 too short",
560610
tcp4TooShort,
611+
true,
561612
notGROCandidate,
562613
},
563614
{
564615
"tcp6 too short",
565616
tcp6TooShort,
617+
true,
566618
notGROCandidate,
567619
},
568620
{
569621
"invalid IP version",
570622
[]byte{0x00},
623+
true,
571624
notGROCandidate,
572625
},
573626
{
574627
"invalid IP header len",
575628
ip4InvalidHeaderLen,
629+
true,
576630
notGROCandidate,
577631
},
578632
{
579633
"ip4 invalid protocol",
580634
ip4InvalidProtocol,
635+
true,
581636
notGROCandidate,
582637
},
583638
{
584639
"ip6 invalid protocol",
585640
ip6InvalidProtocol,
641+
true,
586642
notGROCandidate,
587643
},
588644
}
589645
for _, tt := range tests {
590646
t.Run(tt.name, func(t *testing.T) {
591-
if got := packetIsGROCandidate(tt.b); got != tt.want {
647+
if got := packetIsGROCandidate(tt.b, tt.canUDPGRO); got != tt.want {
592648
t.Errorf("packetIsGROCandidate() = %v, want %v", got, tt.want)
593649
}
594650
})

tun/tun_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
345345
)
346346
tun.toWrite = tun.toWrite[:0]
347347
if tun.vnetHdr {
348-
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, &tun.toWrite)
348+
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, tun.udpGSO, &tun.toWrite)
349349
if err != nil {
350350
return 0, err
351351
}

0 commit comments

Comments
 (0)