tun: export GSOSplit() for external Device implementers
External implementers of tun.Device may support GSO, and may also be platform-agnostic, e.g. gVisor. Signed-off-by: Jordan Whited <jordan@tailscale.com>
This commit is contained in:
parent
2f5d148bcf
commit
60eeedfd62
4 changed files with 354 additions and 162 deletions
220
tun/offload.go
Normal file
220
tun/offload.go
Normal file
|
|
@ -0,0 +1,220 @@
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GSOType represents the type of segmentation offload.
|
||||||
|
type GSOType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
GSONone GSOType = iota
|
||||||
|
GSOTCPv4
|
||||||
|
GSOTCPv6
|
||||||
|
GSOUDPL4
|
||||||
|
)
|
||||||
|
|
||||||
|
func (g GSOType) String() string {
|
||||||
|
switch g {
|
||||||
|
case GSONone:
|
||||||
|
return "GSONone"
|
||||||
|
case GSOTCPv4:
|
||||||
|
return "GSOTCPv4"
|
||||||
|
case GSOTCPv6:
|
||||||
|
return "GSOTCPv6"
|
||||||
|
case GSOUDPL4:
|
||||||
|
return "GSOUDPL4"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GSOOptions is loosely modeled after struct virtio_net_hdr from the VIRTIO
|
||||||
|
// specification. It is a common representation of GSO metadata that can be
|
||||||
|
// applied to support packet GSO across tun.Device implementations.
|
||||||
|
type GSOOptions struct {
|
||||||
|
// GSOType represents the type of segmentation offload.
|
||||||
|
GSOType GSOType
|
||||||
|
// HdrLen is the sum of the layer 3 and 4 header lengths. This field may be
|
||||||
|
// zero when GSOType == GSONone.
|
||||||
|
HdrLen uint16
|
||||||
|
// CsumStart is the head byte index of the packet data to be checksummed,
|
||||||
|
// i.e. the start of the TCP or UDP header.
|
||||||
|
CsumStart uint16
|
||||||
|
// CsumOffset is the offset from CsumStart where the 2-byte checksum value
|
||||||
|
// should be placed.
|
||||||
|
CsumOffset uint16
|
||||||
|
// GSOSize is the size of each segment exclusive of HdrLen. The tail segment
|
||||||
|
// may be smaller than this value.
|
||||||
|
GSOSize uint16
|
||||||
|
// NeedsCsum may be set where GSOType == GSONone. When set, the checksum
|
||||||
|
// at CsumStart + CsumOffset must be a partial checksum, i.e. the
|
||||||
|
// pseudo-header sum.
|
||||||
|
NeedsCsum bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
ipv4SrcAddrOffset = 12
|
||||||
|
ipv6SrcAddrOffset = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
const tcpFlagsOffset = 13
|
||||||
|
|
||||||
|
const (
|
||||||
|
tcpFlagFIN uint8 = 0x01
|
||||||
|
tcpFlagPSH uint8 = 0x08
|
||||||
|
tcpFlagACK uint8 = 0x10
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// defined here in order to avoid importation of any platform-specific pkgs
|
||||||
|
ipProtoTCP = 6
|
||||||
|
ipProtoUDP = 17
|
||||||
|
)
|
||||||
|
|
||||||
|
// GSOSplit splits packets from 'in' into outBufs[<index>][outOffset:], writing
|
||||||
|
// the size of each element into sizes. It returns the number of buffers
|
||||||
|
// populated, and/or an error. Callers may pass an 'in' slice that overlaps with
|
||||||
|
// the first element of outBuffers, i.e. &in[0] may be equal to
|
||||||
|
// &outBufs[0][outOffset]. GSONone is a valid options.GSOType regardless of the
|
||||||
|
// value of options.NeedsCsum. Length of each outBufs element must be greater
|
||||||
|
// than or equal to the length of 'in', otherwise output may be silently
|
||||||
|
// truncated.
|
||||||
|
func GSOSplit(in []byte, options GSOOptions, outBufs [][]byte, sizes []int, outOffset int) (int, error) {
|
||||||
|
cSumAt := int(options.CsumStart) + int(options.CsumOffset)
|
||||||
|
if cSumAt+1 >= len(in) {
|
||||||
|
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(in) < int(options.HdrLen) {
|
||||||
|
return 0, fmt.Errorf("length of packet (%d) < GSO HdrLen (%d)", len(in), options.HdrLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle the conditions where we are copying a single element to outBuffs.
|
||||||
|
payloadLen := len(in) - int(options.HdrLen)
|
||||||
|
if options.GSOType == GSONone || payloadLen < int(options.GSOSize) {
|
||||||
|
if len(in) > len(outBufs[0][outOffset:]) {
|
||||||
|
return 0, fmt.Errorf("length of packet (%d) exceeds output element length (%d)", len(in), len(outBufs[0][outOffset:]))
|
||||||
|
}
|
||||||
|
if options.NeedsCsum {
|
||||||
|
// The initial value at the checksum offset should be summed with
|
||||||
|
// the checksum we compute. This is typically the pseudo-header sum.
|
||||||
|
initial := binary.BigEndian.Uint16(in[cSumAt:])
|
||||||
|
in[cSumAt], in[cSumAt+1] = 0, 0
|
||||||
|
binary.BigEndian.PutUint16(in[cSumAt:], ^checksum(in[options.CsumStart:], initial))
|
||||||
|
}
|
||||||
|
sizes[0] = copy(outBufs[0][outOffset:], in)
|
||||||
|
return 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.HdrLen < options.CsumStart {
|
||||||
|
return 0, fmt.Errorf("GSO HdrLen (%d) < GSO CsumStart (%d)", options.HdrLen, options.CsumStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
ipVersion := in[0] >> 4
|
||||||
|
switch ipVersion {
|
||||||
|
case 4:
|
||||||
|
if options.GSOType != GSOTCPv4 && options.GSOType != GSOUDPL4 {
|
||||||
|
return 0, fmt.Errorf("ip header version: %d, GSO type: %s", ipVersion, options.GSOType)
|
||||||
|
}
|
||||||
|
if len(in) < 20 {
|
||||||
|
return 0, fmt.Errorf("length of packet (%d) < minimum ipv4 header size (%d)", len(in), 20)
|
||||||
|
}
|
||||||
|
case 6:
|
||||||
|
if options.GSOType != GSOTCPv6 && options.GSOType != GSOUDPL4 {
|
||||||
|
return 0, fmt.Errorf("ip header version: %d, GSO type: %s", ipVersion, options.GSOType)
|
||||||
|
}
|
||||||
|
if len(in) < 40 {
|
||||||
|
return 0, fmt.Errorf("length of packet (%d) < minimum ipv6 header size (%d)", len(in), 40)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("invalid ip header version: %d", ipVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
iphLen := int(options.CsumStart)
|
||||||
|
srcAddrOffset := ipv6SrcAddrOffset
|
||||||
|
addrLen := 16
|
||||||
|
if ipVersion == 4 {
|
||||||
|
srcAddrOffset = ipv4SrcAddrOffset
|
||||||
|
addrLen = 4
|
||||||
|
}
|
||||||
|
transportCsumAt := int(options.CsumStart + options.CsumOffset)
|
||||||
|
var firstTCPSeqNum uint32
|
||||||
|
var protocol uint8
|
||||||
|
if options.GSOType == GSOTCPv4 || options.GSOType == GSOTCPv6 {
|
||||||
|
protocol = ipProtoTCP
|
||||||
|
if len(in) < int(options.CsumStart)+20 {
|
||||||
|
return 0, fmt.Errorf("length of packet (%d) < GSO CsumStart (%d) + minimum TCP header size (%d)",
|
||||||
|
len(in), options.CsumStart, 20)
|
||||||
|
}
|
||||||
|
firstTCPSeqNum = binary.BigEndian.Uint32(in[options.CsumStart+4:])
|
||||||
|
} else {
|
||||||
|
protocol = ipProtoUDP
|
||||||
|
}
|
||||||
|
nextSegmentDataAt := int(options.HdrLen)
|
||||||
|
i := 0
|
||||||
|
for ; nextSegmentDataAt < len(in); i++ {
|
||||||
|
if i == len(outBufs) {
|
||||||
|
return i - 1, ErrTooManySegments
|
||||||
|
}
|
||||||
|
nextSegmentEnd := nextSegmentDataAt + int(options.GSOSize)
|
||||||
|
if nextSegmentEnd > len(in) {
|
||||||
|
nextSegmentEnd = len(in)
|
||||||
|
}
|
||||||
|
segmentDataLen := nextSegmentEnd - nextSegmentDataAt
|
||||||
|
totalLen := int(options.HdrLen) + segmentDataLen
|
||||||
|
sizes[i] = totalLen
|
||||||
|
out := outBufs[i][outOffset:]
|
||||||
|
|
||||||
|
copy(out, in[:iphLen])
|
||||||
|
if ipVersion == 4 {
|
||||||
|
// For IPv4 we are responsible for incrementing the ID field,
|
||||||
|
// updating the total len field, and recalculating the header
|
||||||
|
// checksum.
|
||||||
|
if i > 0 {
|
||||||
|
id := binary.BigEndian.Uint16(out[4:])
|
||||||
|
id += uint16(i)
|
||||||
|
binary.BigEndian.PutUint16(out[4:], id)
|
||||||
|
}
|
||||||
|
out[10], out[11] = 0, 0 // clear ipv4 header checksum
|
||||||
|
binary.BigEndian.PutUint16(out[2:], uint16(totalLen))
|
||||||
|
ipv4CSum := ^checksum(out[:iphLen], 0)
|
||||||
|
binary.BigEndian.PutUint16(out[10:], ipv4CSum)
|
||||||
|
} else {
|
||||||
|
// For IPv6 we are responsible for updating the payload length field.
|
||||||
|
binary.BigEndian.PutUint16(out[4:], uint16(totalLen-iphLen))
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy transport header
|
||||||
|
copy(out[options.CsumStart:options.HdrLen], in[options.CsumStart:options.HdrLen])
|
||||||
|
|
||||||
|
if protocol == ipProtoTCP {
|
||||||
|
// set TCP seq and adjust TCP flags
|
||||||
|
tcpSeq := firstTCPSeqNum + uint32(options.GSOSize*uint16(i))
|
||||||
|
binary.BigEndian.PutUint32(out[options.CsumStart+4:], tcpSeq)
|
||||||
|
if nextSegmentEnd != len(in) {
|
||||||
|
// FIN and PSH should only be set on last segment
|
||||||
|
clearFlags := tcpFlagFIN | tcpFlagPSH
|
||||||
|
out[options.CsumStart+tcpFlagsOffset] &^= clearFlags
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// set UDP header len
|
||||||
|
binary.BigEndian.PutUint16(out[options.CsumStart+4:], uint16(segmentDataLen)+(options.HdrLen-options.CsumStart))
|
||||||
|
}
|
||||||
|
|
||||||
|
// payload
|
||||||
|
copy(out[options.HdrLen:], in[nextSegmentDataAt:nextSegmentEnd])
|
||||||
|
|
||||||
|
// transport checksum
|
||||||
|
out[transportCsumAt], out[transportCsumAt+1] = 0, 0 // clear tcp/udp checksum
|
||||||
|
transportHeaderLen := int(options.HdrLen - options.CsumStart)
|
||||||
|
lenForPseudo := uint16(transportHeaderLen + segmentDataLen)
|
||||||
|
transportCSum := pseudoHeaderChecksum(protocol, in[srcAddrOffset:srcAddrOffset+addrLen], in[srcAddrOffset+addrLen:srcAddrOffset+addrLen*2], lenForPseudo)
|
||||||
|
transportCSum = ^checksum(out[options.CsumStart:totalLen], transportCSum)
|
||||||
|
binary.BigEndian.PutUint16(out[options.CsumStart+options.CsumOffset:], transportCSum)
|
||||||
|
|
||||||
|
nextSegmentDataAt += int(options.GSOSize)
|
||||||
|
}
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
|
|
@ -16,14 +17,6 @@ import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const tcpFlagsOffset = 13
|
|
||||||
|
|
||||||
const (
|
|
||||||
tcpFlagFIN uint8 = 0x01
|
|
||||||
tcpFlagPSH uint8 = 0x08
|
|
||||||
tcpFlagACK uint8 = 0x10
|
|
||||||
)
|
|
||||||
|
|
||||||
// virtioNetHdr is defined in the kernel in include/uapi/linux/virtio_net.h. The
|
// virtioNetHdr is defined in the kernel in include/uapi/linux/virtio_net.h. The
|
||||||
// kernel symbol is virtio_net_hdr.
|
// kernel symbol is virtio_net_hdr.
|
||||||
type virtioNetHdr struct {
|
type virtioNetHdr struct {
|
||||||
|
|
@ -35,6 +28,30 @@ type virtioNetHdr struct {
|
||||||
csumOffset uint16
|
csumOffset uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *virtioNetHdr) toGSOOptions() (GSOOptions, error) {
|
||||||
|
var gsoType GSOType
|
||||||
|
switch v.gsoType {
|
||||||
|
case unix.VIRTIO_NET_HDR_GSO_NONE:
|
||||||
|
gsoType = GSONone
|
||||||
|
case unix.VIRTIO_NET_HDR_GSO_TCPV4:
|
||||||
|
gsoType = GSOTCPv4
|
||||||
|
case unix.VIRTIO_NET_HDR_GSO_TCPV6:
|
||||||
|
gsoType = GSOTCPv6
|
||||||
|
case unix.VIRTIO_NET_HDR_GSO_UDP_L4:
|
||||||
|
gsoType = GSOUDPL4
|
||||||
|
default:
|
||||||
|
return GSOOptions{}, fmt.Errorf("unsupported virtio gsoType: %d", v.gsoType)
|
||||||
|
}
|
||||||
|
return GSOOptions{
|
||||||
|
GSOType: gsoType,
|
||||||
|
HdrLen: v.hdrLen,
|
||||||
|
CsumStart: v.csumStart,
|
||||||
|
CsumOffset: v.csumOffset,
|
||||||
|
GSOSize: v.gsoSize,
|
||||||
|
NeedsCsum: v.flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (v *virtioNetHdr) decode(b []byte) error {
|
func (v *virtioNetHdr) decode(b []byte) error {
|
||||||
if len(b) < virtioNetHdrLen {
|
if len(b) < virtioNetHdrLen {
|
||||||
return io.ErrShortBuffer
|
return io.ErrShortBuffer
|
||||||
|
|
@ -510,8 +527,6 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ipv4SrcAddrOffset = 12
|
|
||||||
ipv6SrcAddrOffset = 8
|
|
||||||
maxUint16 = 1<<16 - 1
|
maxUint16 = 1<<16 - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -894,100 +909,3 @@ func handleGRO(bufs [][]byte, offset int, tcpTable *tcpGROTable, udpTable *udpGR
|
||||||
errUDP := applyUDPCoalesceAccounting(bufs, offset, udpTable)
|
errUDP := applyUDPCoalesceAccounting(bufs, offset, udpTable)
|
||||||
return errors.Join(errTCP, errUDP)
|
return errors.Join(errTCP, errUDP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// gsoSplit splits packets from in into outBuffs, writing the size of each
|
|
||||||
// element into sizes. It returns the number of buffers populated, and/or an
|
|
||||||
// error.
|
|
||||||
func gsoSplit(in []byte, hdr virtioNetHdr, outBuffs [][]byte, sizes []int, outOffset int, isV6 bool) (int, error) {
|
|
||||||
iphLen := int(hdr.csumStart)
|
|
||||||
srcAddrOffset := ipv6SrcAddrOffset
|
|
||||||
addrLen := 16
|
|
||||||
if !isV6 {
|
|
||||||
in[10], in[11] = 0, 0 // clear ipv4 header checksum
|
|
||||||
srcAddrOffset = ipv4SrcAddrOffset
|
|
||||||
addrLen = 4
|
|
||||||
}
|
|
||||||
transportCsumAt := int(hdr.csumStart + hdr.csumOffset)
|
|
||||||
in[transportCsumAt], in[transportCsumAt+1] = 0, 0 // clear tcp/udp checksum
|
|
||||||
var firstTCPSeqNum uint32
|
|
||||||
var protocol uint8
|
|
||||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV4 || hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_TCPV6 {
|
|
||||||
protocol = unix.IPPROTO_TCP
|
|
||||||
firstTCPSeqNum = binary.BigEndian.Uint32(in[hdr.csumStart+4:])
|
|
||||||
} else {
|
|
||||||
protocol = unix.IPPROTO_UDP
|
|
||||||
}
|
|
||||||
nextSegmentDataAt := int(hdr.hdrLen)
|
|
||||||
i := 0
|
|
||||||
for ; nextSegmentDataAt < len(in); i++ {
|
|
||||||
if i == len(outBuffs) {
|
|
||||||
return i - 1, ErrTooManySegments
|
|
||||||
}
|
|
||||||
nextSegmentEnd := nextSegmentDataAt + int(hdr.gsoSize)
|
|
||||||
if nextSegmentEnd > len(in) {
|
|
||||||
nextSegmentEnd = len(in)
|
|
||||||
}
|
|
||||||
segmentDataLen := nextSegmentEnd - nextSegmentDataAt
|
|
||||||
totalLen := int(hdr.hdrLen) + segmentDataLen
|
|
||||||
sizes[i] = totalLen
|
|
||||||
out := outBuffs[i][outOffset:]
|
|
||||||
|
|
||||||
copy(out, in[:iphLen])
|
|
||||||
if !isV6 {
|
|
||||||
// For IPv4 we are responsible for incrementing the ID field,
|
|
||||||
// updating the total len field, and recalculating the header
|
|
||||||
// checksum.
|
|
||||||
if i > 0 {
|
|
||||||
id := binary.BigEndian.Uint16(out[4:])
|
|
||||||
id += uint16(i)
|
|
||||||
binary.BigEndian.PutUint16(out[4:], id)
|
|
||||||
}
|
|
||||||
binary.BigEndian.PutUint16(out[2:], uint16(totalLen))
|
|
||||||
ipv4CSum := ^checksum(out[:iphLen], 0)
|
|
||||||
binary.BigEndian.PutUint16(out[10:], ipv4CSum)
|
|
||||||
} else {
|
|
||||||
// For IPv6 we are responsible for updating the payload length field.
|
|
||||||
binary.BigEndian.PutUint16(out[4:], uint16(totalLen-iphLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy transport header
|
|
||||||
copy(out[hdr.csumStart:hdr.hdrLen], in[hdr.csumStart:hdr.hdrLen])
|
|
||||||
|
|
||||||
if protocol == unix.IPPROTO_TCP {
|
|
||||||
// set TCP seq and adjust TCP flags
|
|
||||||
tcpSeq := firstTCPSeqNum + uint32(hdr.gsoSize*uint16(i))
|
|
||||||
binary.BigEndian.PutUint32(out[hdr.csumStart+4:], tcpSeq)
|
|
||||||
if nextSegmentEnd != len(in) {
|
|
||||||
// FIN and PSH should only be set on last segment
|
|
||||||
clearFlags := tcpFlagFIN | tcpFlagPSH
|
|
||||||
out[hdr.csumStart+tcpFlagsOffset] &^= clearFlags
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// set UDP header len
|
|
||||||
binary.BigEndian.PutUint16(out[hdr.csumStart+4:], uint16(segmentDataLen)+(hdr.hdrLen-hdr.csumStart))
|
|
||||||
}
|
|
||||||
|
|
||||||
// payload
|
|
||||||
copy(out[hdr.hdrLen:], in[nextSegmentDataAt:nextSegmentEnd])
|
|
||||||
|
|
||||||
// transport checksum
|
|
||||||
transportHeaderLen := int(hdr.hdrLen - hdr.csumStart)
|
|
||||||
lenForPseudo := uint16(transportHeaderLen + segmentDataLen)
|
|
||||||
transportCSum := pseudoHeaderChecksum(protocol, in[srcAddrOffset:srcAddrOffset+addrLen], in[srcAddrOffset+addrLen:srcAddrOffset+addrLen*2], lenForPseudo)
|
|
||||||
transportCSum = ^checksum(out[hdr.csumStart:totalLen], transportCSum)
|
|
||||||
binary.BigEndian.PutUint16(out[hdr.csumStart+hdr.csumOffset:], transportCSum)
|
|
||||||
|
|
||||||
nextSegmentDataAt += int(hdr.gsoSize)
|
|
||||||
}
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func gsoNoneChecksum(in []byte, cSumStart, cSumOffset uint16) error {
|
|
||||||
cSumAt := cSumStart + cSumOffset
|
|
||||||
// The initial value at the checksum offset should be summed with the
|
|
||||||
// checksum we compute. This is typically the pseudo-header checksum.
|
|
||||||
initial := binary.BigEndian.Uint16(in[cSumAt:])
|
|
||||||
in[cSumAt], in[cSumAt+1] = 0, 0
|
|
||||||
binary.BigEndian.PutUint16(in[cSumAt:], ^checksum(in[cSumStart:], initial))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
95
tun/offload_test.go
Normal file
95
tun/offload_test.go
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
package tun
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/netip"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tailscale/wireguard-go/conn"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip"
|
||||||
|
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Fuzz_GSOSplit(f *testing.F) {
|
||||||
|
const segmentSize = 100
|
||||||
|
|
||||||
|
tcpFields := &header.TCPFields{
|
||||||
|
SrcPort: 1,
|
||||||
|
DstPort: 1,
|
||||||
|
SeqNum: 1,
|
||||||
|
AckNum: 1,
|
||||||
|
DataOffset: 20,
|
||||||
|
Flags: header.TCPFlagAck | header.TCPFlagPsh,
|
||||||
|
WindowSize: 3000,
|
||||||
|
}
|
||||||
|
udpFields := &header.UDPFields{
|
||||||
|
SrcPort: 1,
|
||||||
|
DstPort: 1,
|
||||||
|
Length: 8 + segmentSize,
|
||||||
|
}
|
||||||
|
|
||||||
|
gsoTCPv4 := make([]byte, 20+20+segmentSize)
|
||||||
|
header.IPv4(gsoTCPv4).Encode(&header.IPv4Fields{
|
||||||
|
SrcAddr: tcpip.AddrFromSlice(netip.MustParseAddr("192.0.2.1").AsSlice()),
|
||||||
|
DstAddr: tcpip.AddrFromSlice(netip.MustParseAddr("192.0.2.2").AsSlice()),
|
||||||
|
Protocol: ipProtoTCP,
|
||||||
|
TTL: 64,
|
||||||
|
TotalLength: uint16(len(gsoTCPv4)),
|
||||||
|
})
|
||||||
|
header.TCP(gsoTCPv4[20:]).Encode(tcpFields)
|
||||||
|
|
||||||
|
gsoUDPv4 := make([]byte, 20+8+segmentSize)
|
||||||
|
header.IPv4(gsoUDPv4).Encode(&header.IPv4Fields{
|
||||||
|
SrcAddr: tcpip.AddrFromSlice(netip.MustParseAddr("192.0.2.1").AsSlice()),
|
||||||
|
DstAddr: tcpip.AddrFromSlice(netip.MustParseAddr("192.0.2.2").AsSlice()),
|
||||||
|
Protocol: ipProtoUDP,
|
||||||
|
TTL: 64,
|
||||||
|
TotalLength: uint16(len(gsoUDPv4)),
|
||||||
|
})
|
||||||
|
header.UDP(gsoTCPv4[20:]).Encode(udpFields)
|
||||||
|
|
||||||
|
gsoTCPv6 := make([]byte, 40+20+segmentSize)
|
||||||
|
header.IPv6(gsoTCPv6).Encode(&header.IPv6Fields{
|
||||||
|
SrcAddr: tcpip.AddrFromSlice(netip.MustParseAddr("2001:db8::1").AsSlice()),
|
||||||
|
DstAddr: tcpip.AddrFromSlice(netip.MustParseAddr("2001:db8::2").AsSlice()),
|
||||||
|
TransportProtocol: ipProtoTCP,
|
||||||
|
HopLimit: 64,
|
||||||
|
PayloadLength: uint16(20 + segmentSize),
|
||||||
|
})
|
||||||
|
header.TCP(gsoTCPv6[40:]).Encode(tcpFields)
|
||||||
|
|
||||||
|
gsoUDPv6 := make([]byte, 40+8+segmentSize)
|
||||||
|
header.IPv6(gsoUDPv6).Encode(&header.IPv6Fields{
|
||||||
|
SrcAddr: tcpip.AddrFromSlice(netip.MustParseAddr("2001:db8::1").AsSlice()),
|
||||||
|
DstAddr: tcpip.AddrFromSlice(netip.MustParseAddr("2001:db8::2").AsSlice()),
|
||||||
|
TransportProtocol: ipProtoUDP,
|
||||||
|
HopLimit: 64,
|
||||||
|
PayloadLength: uint16(8 + segmentSize),
|
||||||
|
})
|
||||||
|
header.UDP(gsoUDPv6[20:]).Encode(udpFields)
|
||||||
|
|
||||||
|
out := make([][]byte, conn.IdealBatchSize)
|
||||||
|
for i := range out {
|
||||||
|
out[i] = make([]byte, 65535)
|
||||||
|
}
|
||||||
|
sizes := make([]int, conn.IdealBatchSize)
|
||||||
|
|
||||||
|
f.Add(gsoTCPv4, int(GSOTCPv4), uint16(40), uint16(20), uint16(16), uint16(100), false)
|
||||||
|
f.Add(gsoUDPv4, int(GSOUDPL4), uint16(28), uint16(20), uint16(6), uint16(100), false)
|
||||||
|
f.Add(gsoTCPv6, int(GSOTCPv6), uint16(60), uint16(40), uint16(16), uint16(100), false)
|
||||||
|
f.Add(gsoUDPv6, int(GSOUDPL4), uint16(48), uint16(40), uint16(6), uint16(100), false)
|
||||||
|
|
||||||
|
f.Fuzz(func(t *testing.T, pkt []byte, gsoType int, hdrLen, csumStart, csumOffset, gsoSize uint16, needsCsum bool) {
|
||||||
|
options := GSOOptions{
|
||||||
|
GSOType: GSOType(gsoType),
|
||||||
|
HdrLen: hdrLen,
|
||||||
|
CsumStart: csumStart,
|
||||||
|
CsumOffset: csumOffset,
|
||||||
|
GSOSize: gsoSize,
|
||||||
|
NeedsCsum: needsCsum,
|
||||||
|
}
|
||||||
|
n, _ := GSOSplit(pkt, options, out, sizes, 0)
|
||||||
|
if n > len(sizes) {
|
||||||
|
t.Errorf("n (%d) > len(sizes): %d", n, len(sizes))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -380,73 +380,32 @@ func handleVirtioRead(in []byte, bufs [][]byte, sizes []int, offset int) (int, e
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
in = in[virtioNetHdrLen:]
|
in = in[virtioNetHdrLen:]
|
||||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_NONE {
|
|
||||||
if hdr.flags&unix.VIRTIO_NET_HDR_F_NEEDS_CSUM != 0 {
|
options, err := hdr.toGSOOptions()
|
||||||
// This means CHECKSUM_PARTIAL in skb context. We are responsible
|
|
||||||
// for computing the checksum starting at hdr.csumStart and placing
|
|
||||||
// at hdr.csumOffset.
|
|
||||||
err = gsoNoneChecksum(in, hdr.csumStart, hdr.csumOffset)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if len(in) > len(bufs[0][offset:]) {
|
|
||||||
return 0, fmt.Errorf("read len %d overflows bufs element len %d", len(in), len(bufs[0][offset:]))
|
|
||||||
}
|
|
||||||
n := copy(bufs[0][offset:], in)
|
|
||||||
sizes[0] = n
|
|
||||||
return 1, nil
|
|
||||||
}
|
|
||||||
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
|
// Don't trust HdrLen from the kernel as it can be equal to the length
|
||||||
switch ipVersion {
|
|
||||||
case 4:
|
|
||||||
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 && 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// of the entire first packet when the kernel is handling it as part of a
|
||||||
// FORWARD path. Instead, parse the transport 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.
|
// CsumStart, which is synonymous for IP header length.
|
||||||
if hdr.gsoType == unix.VIRTIO_NET_HDR_GSO_UDP_L4 {
|
if options.GSOType == GSOUDPL4 {
|
||||||
hdr.hdrLen = hdr.csumStart + 8
|
options.HdrLen = options.CsumStart + 8
|
||||||
} else {
|
} else if options.GSOType != GSONone {
|
||||||
if len(in) <= int(hdr.csumStart+12) {
|
if len(in) <= int(options.CsumStart+12) {
|
||||||
return 0, errors.New("packet is too short")
|
return 0, errors.New("packet is too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
tcpHLen := uint16(in[hdr.csumStart+12] >> 4 * 4)
|
tcpHLen := uint16(in[options.CsumStart+12] >> 4 * 4)
|
||||||
if tcpHLen < 20 || tcpHLen > 60 {
|
if tcpHLen < 20 || tcpHLen > 60 {
|
||||||
// A TCP header must be between 20 and 60 bytes in length.
|
// A TCP header must be between 20 and 60 bytes in length.
|
||||||
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
return 0, fmt.Errorf("tcp header len is invalid: %d", tcpHLen)
|
||||||
}
|
}
|
||||||
hdr.hdrLen = hdr.csumStart + tcpHLen
|
options.HdrLen = options.CsumStart + tcpHLen
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(in) < int(hdr.hdrLen) {
|
return GSOSplit(in, options, bufs, sizes, offset)
|
||||||
return 0, fmt.Errorf("length of packet (%d) < virtioNetHdr.hdrLen (%d)", len(in), hdr.hdrLen)
|
|
||||||
}
|
|
||||||
|
|
||||||
if hdr.hdrLen < hdr.csumStart {
|
|
||||||
return 0, fmt.Errorf("virtioNetHdr.hdrLen (%d) < virtioNetHdr.csumStart (%d)", hdr.hdrLen, hdr.csumStart)
|
|
||||||
}
|
|
||||||
cSumAt := int(hdr.csumStart + hdr.csumOffset)
|
|
||||||
if cSumAt+1 >= len(in) {
|
|
||||||
return 0, fmt.Errorf("end of checksum offset (%d) exceeds packet length (%d)", cSumAt+1, len(in))
|
|
||||||
}
|
|
||||||
|
|
||||||
return gsoSplit(in, hdr, bufs, sizes, offset, ipVersion == 6)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
|
func (tun *NativeTun) Read(bufs [][]byte, sizes []int, offset int) (int, error) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue