mirror of
https://github.com/netbirdio/wireguard-go.git
synced 2026-05-22 17:08:51 -07:00
tun: implement UDP GSO/GRO for Linux
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>
This commit is contained in:
committed by
Jason A. Donenfeld
parent
1cf89f5339
commit
d0bc03c707
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,411 +0,0 @@
|
||||
/* 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 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,
|
||||
},
|
||||
}
|
||||
|
||||
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 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)
|
||||
f.Add(pkt0, pkt1, pkt2, pkt3, pkt4, pkt5, offset)
|
||||
f.Fuzz(func(t *testing.T, pkt0, pkt1, pkt2, pkt3, pkt4, pkt5 []byte, offset int) {
|
||||
pkts := [][]byte{pkt0, pkt1, pkt2, pkt3, pkt4, pkt5}
|
||||
toWrite := make([]int, 0, len(pkts))
|
||||
handleGRO(pkts, offset, newTCPGROTable(), newTCPGROTable(), &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
|
||||
wantToWrite []int
|
||||
wantLens []int
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"multiple flows",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1), // v4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101), // v4 flow 1
|
||||
tcp4Packet(ip4PortA, ip4PortC, header.TCPFlagAck, 100, 201), // v4 flow 2
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1), // v6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101), // v6 flow 1
|
||||
tcp6Packet(ip6PortA, ip6PortC, header.TCPFlagAck, 100, 201), // v6 flow 2
|
||||
},
|
||||
[]int{0, 2, 3, 5},
|
||||
[]int{240, 140, 260, 160},
|
||||
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
|
||||
},
|
||||
[]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
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{140, 240},
|
||||
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
|
||||
},
|
||||
[]int{0},
|
||||
[]int{340},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp4 unequal TTL",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.TTL++
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{140, 140},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp4 unequal ToS",
|
||||
[][]byte{
|
||||
tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp4PacketMutateIPFields(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv4Fields) {
|
||||
fields.TOS++
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{140, 140},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp4 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
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{140, 140},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp4 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
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{140, 140},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp6 unequal hop limit",
|
||||
[][]byte{
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
||||
fields.HopLimit++
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{160, 160},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"tcp6 unequal traffic class",
|
||||
[][]byte{
|
||||
tcp6Packet(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 1),
|
||||
tcp6PacketMutateIPFields(ip6PortA, ip6PortB, header.TCPFlagAck, 100, 101, func(fields *header.IPv6Fields) {
|
||||
fields.TrafficClass++
|
||||
}),
|
||||
},
|
||||
[]int{0, 1},
|
||||
[]int{160, 160},
|
||||
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(), newTCPGROTable(), &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_isTCP4NoIPOptions(t *testing.T) {
|
||||
valid := tcp4Packet(ip4PortA, ip4PortB, header.TCPFlagAck, 100, 1)[virtioNetHdrLen:]
|
||||
invalidLen := valid[:39]
|
||||
invalidHeaderLen := make([]byte, len(valid))
|
||||
copy(invalidHeaderLen, valid)
|
||||
invalidHeaderLen[0] = 0x46
|
||||
invalidProtocol := make([]byte, len(valid))
|
||||
copy(invalidProtocol, valid)
|
||||
invalidProtocol[9] = unix.IPPROTO_TCP + 1
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
b []byte
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
"valid",
|
||||
valid,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"invalid length",
|
||||
invalidLen,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid version",
|
||||
[]byte{0x00},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid header len",
|
||||
invalidHeaderLen,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"invalid protocol",
|
||||
invalidProtocol,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := isTCP4NoIPOptions(tt.b); got != tt.want {
|
||||
t.Errorf("isTCP4NoIPOptions() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
go test fuzz v1
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
int(34)
|
||||
Vendored
-8
@@ -1,8 +0,0 @@
|
||||
go test fuzz v1
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
[]byte("0")
|
||||
int(-48)
|
||||
+42
-29
@@ -38,6 +38,7 @@ type NativeTun struct {
|
||||
statusListenersShutdown chan struct{}
|
||||
batchSize int
|
||||
vnetHdr bool
|
||||
udpGSO bool
|
||||
|
||||
closeOnce sync.Once
|
||||
|
||||
@@ -48,9 +49,10 @@ type NativeTun struct {
|
||||
readOpMu sync.Mutex // readOpMu guards readBuff
|
||||
readBuff [virtioNetHdrLen + 65535]byte // if vnetHdr every read() is prefixed by virtioNetHdr
|
||||
|
||||
writeOpMu sync.Mutex // writeOpMu guards toWrite, tcp4GROTable, tcp6GROTable
|
||||
toWrite []int
|
||||
tcp4GROTable, tcp6GROTable *tcpGROTable
|
||||
writeOpMu sync.Mutex // writeOpMu guards toWrite, tcpGROTable
|
||||
toWrite []int
|
||||
tcpGROTable *tcpGROTable
|
||||
udpGROTable *udpGROTable
|
||||
}
|
||||
|
||||
func (tun *NativeTun) File() *os.File {
|
||||
@@ -333,8 +335,8 @@ func (tun *NativeTun) nameSlow() (string, error) {
|
||||
func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
tun.writeOpMu.Lock()
|
||||
defer func() {
|
||||
tun.tcp4GROTable.reset()
|
||||
tun.tcp6GROTable.reset()
|
||||
tun.tcpGROTable.reset()
|
||||
tun.udpGROTable.reset()
|
||||
tun.writeOpMu.Unlock()
|
||||
}()
|
||||
var (
|
||||
@@ -343,7 +345,7 @@ func (tun *NativeTun) Write(bufs [][]byte, offset int) (int, error) {
|
||||
)
|
||||
tun.toWrite = tun.toWrite[:0]
|
||||
if tun.vnetHdr {
|
||||
err := handleGRO(bufs, offset, tun.tcp4GROTable, tun.tcp6GROTable, &tun.toWrite)
|
||||
err := handleGRO(bufs, offset, tun.tcpGROTable, tun.udpGROTable, tun.udpGSO, &tun.toWrite)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -394,37 +396,42 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
|
||||
sizes[0] = n
|
||||
return 1, nil
|
||||
}
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("unsupported virtio GSO type: %d", hdr.gsoType)
|
||||
}
|
||||
|
||||
ipVersion := in[0] >> 4
|
||||
switch ipVersion {
|
||||
case 4:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 {
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV4 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
case 6:
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
||||
if hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_TCPV6 && hdr.gsoType != unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
return 0, fmt.Errorf("ip header version: %d, GSO type: %d", ipVersion, hdr.gsoType)
|
||||
}
|
||||
default:
|
||||
return 0, fmt.Errorf("invalid ip header version: %d", ipVersion)
|
||||
}
|
||||
|
||||
if len(in) <= int(hdr.csumStart+12) {
|
||||
return 0, errors.New("packet is too short")
|
||||
}
|
||||
// Don't trust hdr.hdrLen from the kernel as it can be equal to the length
|
||||
// of the entire first packet when the kernel is handling it as part of a
|
||||
// FORWARD path. Instead, parse the TCP header length and add it onto
|
||||
// FORWARD path. Instead, parse the transport header length and add it onto
|
||||
// csumStart, which is synonymous for IP header length.
|
||||
tcpHLen := uint16(in[hdr.csumStart+12] >> 4 * 4)
|
||||
if tcpHLen < 20 || tcpHLen > 60 {
|
||||
// A TCP header must be between 20 and 60 bytes in length.
|
||||
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
||||
hdr.hdrLen = hdr.csumStart + 8
|
||||
} else {
|
||||
if len(in) <= int(hdr.csumStart+12) {
|
||||
return 0, errors.New("packet is too short")
|
||||
}
|
||||
|
||||
tcpHLen := uint16(in[hdr.csumStart+12] >> 4 * 4)
|
||||
if tcpHLen < 20 || tcpHLen > 60 {
|
||||
// A TCP header must be between 20 and 60 bytes in length.
|
||||
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
||||
}
|
||||
hdr.hdrLen = hdr.csumStart + tcpHLen
|
||||
}
|
||||
hdr.hdrLen = hdr.csumStart + tcpHLen
|
||||
|
||||
if len(in) < int(hdr.hdrLen) {
|
||||
return 0, fmt.Errorf("length of packet (%d) < virtioNetHdr.hdrLen (%d)", len(in), hdr.hdrLen)
|
||||
@@ -438,7 +445,7 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
|
||||
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
||||
}
|
||||
|
||||
return tcpTSO(in, hdr, bufs, sizes, offset)
|
||||
return gsoSplit(in, hdr, bufs, sizes, offset, ipVersion == 6)
|
||||
}
|
||||
|
||||
func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||
@@ -497,7 +504,8 @@ func (tun *NativeTun) BatchSize() int {
|
||||
|
||||
const (
|
||||
// TODO: support TSO with ECN bits
|
||||
tunOffloads = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||
tunTCPOffloads = unix.TUN_F_CSUM | unix.TUN_F_TSO4 | unix.TUN_F_TSO6
|
||||
tunUDPOffloads = unix.TUN_F_USO4 | unix.TUN_F_USO6
|
||||
)
|
||||
|
||||
func (tun *NativeTun) initFromFlags(name string) error {
|
||||
@@ -519,12 +527,17 @@ func (tun *NativeTun) initFromFlags(name string) error {
|
||||
}
|
||||
got := ifr.Uint16()
|
||||
if got&unix.IFF_VNET_HDR != 0 {
|
||||
err = unix.IoctlSetInt(int(fd), unix.TUNSETOFFLOAD, tunOffloads)
|
||||
// tunTCPOffloads were added in Linux v2.6. We require their support
|
||||
// if IFF_VNET_HDR is set.
|
||||
err = unix.IoctlSetInt(int(fd), unix.TUNSETOFFLOAD, tunTCPOffloads)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
tun.vnetHdr = true
|
||||
tun.batchSize = conn.IdealBatchSize
|
||||
// tunUDPOffloads were added in Linux v6.2. We do not return an
|
||||
// error if they are unsupported at runtime.
|
||||
tun.udpGSO = unix.IoctlSetInt(int(fd), unix.TUNSETOFFLOAD, tunTCPOffloads|tunUDPOffloads) == nil
|
||||
} else {
|
||||
tun.batchSize = 1
|
||||
}
|
||||
@@ -575,8 +588,8 @@ func CreateTUNFromFile(file *os.File, mtu int) (Device, error) {
|
||||
events: make(chan Event, 5),
|
||||
errors: make(chan error, 5),
|
||||
statusListenersShutdown: make(chan struct{}),
|
||||
tcp4GROTable: newTCPGROTable(),
|
||||
tcp6GROTable: newTCPGROTable(),
|
||||
tcpGROTable: newTCPGROTable(),
|
||||
udpGROTable: newUDPGROTable(),
|
||||
toWrite: make([]int, 0, conn.IdealBatchSize),
|
||||
}
|
||||
|
||||
@@ -628,12 +641,12 @@ func CreateUnmonitoredTUNFromFD(fd int) (Device, string, error) {
|
||||
}
|
||||
file := os.NewFile(uintptr(fd), "/dev/tun")
|
||||
tun := &NativeTun{
|
||||
tunFile: file,
|
||||
events: make(chan Event, 5),
|
||||
errors: make(chan error, 5),
|
||||
tcp4GROTable: newTCPGROTable(),
|
||||
tcp6GROTable: newTCPGROTable(),
|
||||
toWrite: make([]int, 0, conn.IdealBatchSize),
|
||||
tunFile: file,
|
||||
events: make(chan Event, 5),
|
||||
errors: make(chan error, 5),
|
||||
tcpGROTable: newTCPGROTable(),
|
||||
udpGROTable: newUDPGROTable(),
|
||||
toWrite: make([]int, 0, conn.IdealBatchSize),
|
||||
}
|
||||
name, err := tun.Name()
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user