snapshot: sagernet/gvisor v0.0.0-20250811.0-sing-box-mod.1
Содержимое пина, зафиксированного в go.mod sing-box-lx, одним коммитом без истории. Полная история SagerNet/gvisor — 1.45 ГБ и клонируется в каждой CI-джобе; наша дельта — одна вставка в одну функцию, история для неё не нужна. Module path github.com/sagernet/gvisor сохранён намеренно: на него опирается replace-директива суперпроекта. Патч поверх — отдельным коммитом, чтобы дельта читалась одним git show и переносилась на новый пин копированием. SPECS/TASKS/048-GVISOR_HANDSHAKE_NIL_CRASH
This commit is contained in:
commit
2c4ae3b0a4
712 changed files with 185689 additions and 0 deletions
603
pkg/tcpip/stack/gro/gro.go
Normal file
603
pkg/tcpip/stack/gro/gro.go
Normal file
|
|
@ -0,0 +1,603 @@
|
|||
// Copyright 2022 The gVisor Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package gro implements generic receive offload.
|
||||
package gro
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/header"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// There is room for improvement to the GRO engine:
|
||||
// - We should save those headers in
|
||||
// PacketBuffers so they don't have to be re-parsed later.
|
||||
// - We still see the occasional SACK block in the zero-loss
|
||||
// benchmark, which should not happen.
|
||||
// - Some dispatchers, e.g. XDP and RecvMmsg, can receive
|
||||
// multiple packets at a time. Even if the GRO interval is 0, there is an
|
||||
// opportunity for coalescing.
|
||||
// - We could pass a packet list up the stack to reduce traversals up the
|
||||
// stack.
|
||||
|
||||
const (
|
||||
// groNBuckets is the number of GRO buckets.
|
||||
groNBuckets = 8
|
||||
|
||||
groNBucketsMask = groNBuckets - 1
|
||||
|
||||
// groBucketSize is the size of each GRO bucket.
|
||||
groBucketSize = 8
|
||||
|
||||
// groMaxPacketSize is the maximum size of a GRO'd packet.
|
||||
groMaxPacketSize = 1 << 16 // 65KB.
|
||||
)
|
||||
|
||||
// A groBucket holds packets that are undergoing GRO.
|
||||
//
|
||||
// +stateify savable
|
||||
type groBucket struct {
|
||||
// count is the number of packets in the bucket.
|
||||
count int
|
||||
|
||||
// packets is the linked list of packets.
|
||||
packets groPacketList
|
||||
|
||||
// packetsPrealloc and allocIdxs are used to preallocate and reuse
|
||||
// groPacket structs and avoid allocation.
|
||||
packetsPrealloc [groBucketSize]groPacket
|
||||
|
||||
allocIdxs [groBucketSize]int
|
||||
}
|
||||
|
||||
func (gb *groBucket) full() bool {
|
||||
return gb.count == groBucketSize
|
||||
}
|
||||
|
||||
// insert inserts pkt into the bucket.
|
||||
func (gb *groBucket) insert(pkt *stack.PacketBuffer, ipHdr []byte, tcpHdr header.TCP) {
|
||||
groPkt := &gb.packetsPrealloc[gb.allocIdxs[gb.count]]
|
||||
*groPkt = groPacket{
|
||||
pkt: pkt,
|
||||
ipHdr: ipHdr,
|
||||
tcpHdr: tcpHdr,
|
||||
initialLength: pkt.Data().Size(), // pkt.Data() contains network header.
|
||||
idx: groPkt.idx,
|
||||
}
|
||||
gb.count++
|
||||
gb.packets.PushBack(groPkt)
|
||||
}
|
||||
|
||||
// removeOldest removes the oldest packet from gb and returns the contained
|
||||
// PacketBuffer. gb must not be empty.
|
||||
func (gb *groBucket) removeOldest() *stack.PacketBuffer {
|
||||
pkt := gb.packets.Front()
|
||||
gb.packets.Remove(pkt)
|
||||
gb.count--
|
||||
gb.allocIdxs[gb.count] = pkt.idx
|
||||
ret := pkt.pkt
|
||||
pkt.reset()
|
||||
return ret
|
||||
}
|
||||
|
||||
// removeOne removes a packet from gb. It also resets pkt to its zero value.
|
||||
func (gb *groBucket) removeOne(pkt *groPacket) {
|
||||
gb.packets.Remove(pkt)
|
||||
gb.count--
|
||||
gb.allocIdxs[gb.count] = pkt.idx
|
||||
pkt.reset()
|
||||
}
|
||||
|
||||
// findGROPacket4 returns the groPkt that matches ipHdr and tcpHdr, or nil if
|
||||
// none exists. It also returns whether the groPkt should be flushed based on
|
||||
// differences between the two headers.
|
||||
func (gb *groBucket) findGROPacket4(pkt *stack.PacketBuffer, ipHdr header.IPv4, tcpHdr header.TCP) (*groPacket, bool) {
|
||||
for groPkt := gb.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
|
||||
// Do the addresses match?
|
||||
groIPHdr := header.IPv4(groPkt.ipHdr)
|
||||
if ipHdr.SourceAddress() != groIPHdr.SourceAddress() || ipHdr.DestinationAddress() != groIPHdr.DestinationAddress() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do the ports match?
|
||||
if tcpHdr.SourcePort() != groPkt.tcpHdr.SourcePort() || tcpHdr.DestinationPort() != groPkt.tcpHdr.DestinationPort() {
|
||||
continue
|
||||
}
|
||||
|
||||
// We've found a packet of the same flow.
|
||||
|
||||
// IP checks.
|
||||
TOS, _ := ipHdr.TOS()
|
||||
groTOS, _ := groIPHdr.TOS()
|
||||
if ipHdr.TTL() != groIPHdr.TTL() || TOS != groTOS {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
// TCP checks.
|
||||
if shouldFlushTCP(groPkt, tcpHdr) {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
// There's an upper limit on coalesced packet size.
|
||||
if pkt.Data().Size()-header.IPv4MinimumSize-int(tcpHdr.DataOffset())+groPkt.pkt.Data().Size() >= groMaxPacketSize {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
return groPkt, false
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// findGROPacket6 returns the groPkt that matches ipHdr and tcpHdr, or nil if
|
||||
// none exists. It also returns whether the groPkt should be flushed based on
|
||||
// differences between the two headers.
|
||||
func (gb *groBucket) findGROPacket6(pkt *stack.PacketBuffer, ipHdr header.IPv6, tcpHdr header.TCP) (*groPacket, bool) {
|
||||
for groPkt := gb.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
|
||||
// Do the addresses match?
|
||||
groIPHdr := header.IPv6(groPkt.ipHdr)
|
||||
if ipHdr.SourceAddress() != groIPHdr.SourceAddress() || ipHdr.DestinationAddress() != groIPHdr.DestinationAddress() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Need to check that headers are the same except:
|
||||
// - Traffic class, a difference of which causes a flush.
|
||||
// - Hop limit, a difference of which causes a flush.
|
||||
// - Length, which is checked later.
|
||||
// - Version, which is checked by an earlier call to IsValid().
|
||||
trafficClass, flowLabel := ipHdr.TOS()
|
||||
groTrafficClass, groFlowLabel := groIPHdr.TOS()
|
||||
if flowLabel != groFlowLabel || ipHdr.NextHeader() != groIPHdr.NextHeader() {
|
||||
continue
|
||||
}
|
||||
// Unlike IPv4, IPv6 packets with extension headers can be coalesced.
|
||||
if !bytes.Equal(ipHdr[header.IPv6MinimumSize:], groIPHdr[header.IPv6MinimumSize:]) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Do the ports match?
|
||||
if tcpHdr.SourcePort() != groPkt.tcpHdr.SourcePort() || tcpHdr.DestinationPort() != groPkt.tcpHdr.DestinationPort() {
|
||||
continue
|
||||
}
|
||||
|
||||
// We've found a packet of the same flow.
|
||||
|
||||
// TCP checks.
|
||||
if shouldFlushTCP(groPkt, tcpHdr) {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
// Do the traffic class and hop limit match?
|
||||
if trafficClass != groTrafficClass || ipHdr.HopLimit() != groIPHdr.HopLimit() {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
// This limit is artificial for IPv6 -- we could allow even
|
||||
// larger packets via jumbograms.
|
||||
if pkt.Data().Size()-len(ipHdr)-int(tcpHdr.DataOffset())+groPkt.pkt.Data().Size() >= groMaxPacketSize {
|
||||
return groPkt, true
|
||||
}
|
||||
|
||||
return groPkt, false
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (gb *groBucket) found(gd *GRO, groPkt *groPacket, flushGROPkt bool, pkt *stack.PacketBuffer, ipHdr []byte, tcpHdr header.TCP, updateIPHdr func([]byte, int)) {
|
||||
// Flush groPkt or merge the packets.
|
||||
pktSize := pkt.Data().Size()
|
||||
flags := tcpHdr.Flags()
|
||||
dataOff := tcpHdr.DataOffset()
|
||||
tcpPayloadSize := pkt.Data().Size() - len(ipHdr) - int(dataOff)
|
||||
if flushGROPkt {
|
||||
// Flush the existing GRO packet.
|
||||
pkt := groPkt.pkt
|
||||
gb.removeOne(groPkt)
|
||||
gd.handlePacket(pkt)
|
||||
pkt.DecRef()
|
||||
groPkt = nil
|
||||
} else if groPkt != nil {
|
||||
// Merge pkt in to GRO packet.
|
||||
pkt.Data().TrimFront(len(ipHdr) + int(dataOff))
|
||||
groPkt.pkt.Data().Merge(pkt.Data())
|
||||
// Update the IP total length.
|
||||
updateIPHdr(groPkt.ipHdr, tcpPayloadSize)
|
||||
// Add flags from the packet to the GRO packet.
|
||||
groPkt.tcpHdr.SetFlags(uint8(groPkt.tcpHdr.Flags() | (flags & (header.TCPFlagFin | header.TCPFlagPsh))))
|
||||
|
||||
pkt = nil
|
||||
}
|
||||
|
||||
// Flush if the packet isn't the same size as the previous packets or
|
||||
// if certain flags are set. The reason for checking size equality is:
|
||||
// - If the packet is smaller than the others, this is likely the end
|
||||
// of some message. Peers will send MSS-sized packets until they have
|
||||
// insufficient data to do so.
|
||||
// - If the packet is larger than the others, this packet is either
|
||||
// malformed, a local GSO packet, or has already been handled by host
|
||||
// GRO.
|
||||
flush := header.TCPFlags(flags)&(header.TCPFlagUrg|header.TCPFlagPsh|header.TCPFlagRst|header.TCPFlagSyn|header.TCPFlagFin) != 0
|
||||
flush = flush || tcpPayloadSize == 0
|
||||
if groPkt != nil {
|
||||
flush = flush || pktSize != groPkt.initialLength
|
||||
}
|
||||
|
||||
switch {
|
||||
case flush && groPkt != nil:
|
||||
// A merge occurred and we need to flush groPkt.
|
||||
pkt := groPkt.pkt
|
||||
gb.removeOne(groPkt)
|
||||
gd.handlePacket(pkt)
|
||||
pkt.DecRef()
|
||||
case flush && groPkt == nil:
|
||||
// No merge occurred and the incoming packet needs to be flushed.
|
||||
gd.handlePacket(pkt)
|
||||
case !flush && groPkt == nil:
|
||||
// New flow and we don't need to flush. Insert pkt into GRO.
|
||||
if gb.full() {
|
||||
// Head is always the oldest packet
|
||||
toFlush := gb.removeOldest()
|
||||
gb.insert(pkt.IncRef(), ipHdr, tcpHdr)
|
||||
gd.handlePacket(toFlush)
|
||||
toFlush.DecRef()
|
||||
} else {
|
||||
gb.insert(pkt.IncRef(), ipHdr, tcpHdr)
|
||||
}
|
||||
default:
|
||||
// A merge occurred and we don't need to flush anything.
|
||||
}
|
||||
}
|
||||
|
||||
// A groPacket is packet undergoing GRO. It may be several packets coalesced
|
||||
// together.
|
||||
//
|
||||
// +stateify savable
|
||||
type groPacket struct {
|
||||
// groPacketEntry is an intrusive list.
|
||||
groPacketEntry
|
||||
|
||||
// pkt is the coalesced packet.
|
||||
pkt *stack.PacketBuffer
|
||||
|
||||
// ipHdr is the IP (v4 or v6) header for the coalesced packet.
|
||||
ipHdr []byte
|
||||
|
||||
// tcpHdr is the TCP header for the coalesced packet.
|
||||
tcpHdr header.TCP
|
||||
|
||||
// initialLength is the length of the first packet in the flow. It is
|
||||
// used as a best-effort guess at MSS: senders will send MSS-sized
|
||||
// packets until they run out of data, so we coalesce as long as
|
||||
// packets are the same size.
|
||||
initialLength int
|
||||
|
||||
// idx is the groPacket's index in its bucket packetsPrealloc. It is
|
||||
// immutable.
|
||||
idx int
|
||||
}
|
||||
|
||||
// reset resets all mutable fields of the groPacket.
|
||||
func (pk *groPacket) reset() {
|
||||
*pk = groPacket{
|
||||
idx: pk.idx,
|
||||
}
|
||||
}
|
||||
|
||||
// payloadSize is the payload size of the coalesced packet, which does not
|
||||
// include the network or transport headers.
|
||||
func (pk *groPacket) payloadSize() int {
|
||||
return pk.pkt.Data().Size() - len(pk.ipHdr) - int(pk.tcpHdr.DataOffset())
|
||||
}
|
||||
|
||||
// GRO coalesces incoming packets to increase throughput.
|
||||
//
|
||||
// +stateify savable
|
||||
type GRO struct {
|
||||
enabled bool
|
||||
buckets [groNBuckets]groBucket
|
||||
|
||||
Dispatcher stack.NetworkDispatcher
|
||||
}
|
||||
|
||||
// Init initializes GRO.
|
||||
func (gd *GRO) Init(enabled bool) {
|
||||
gd.enabled = enabled
|
||||
for i := range gd.buckets {
|
||||
bucket := &gd.buckets[i]
|
||||
for j := range bucket.packetsPrealloc {
|
||||
bucket.allocIdxs[j] = j
|
||||
bucket.packetsPrealloc[j].idx = j
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Enqueue the packet in GRO. This does not flush packets; Flush() must be
|
||||
// called explicitly for that.
|
||||
//
|
||||
// pkt.NetworkProtocolNumber and pkt.RXChecksumValidated must be set.
|
||||
func (gd *GRO) Enqueue(pkt *stack.PacketBuffer) {
|
||||
if !gd.enabled {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
switch pkt.NetworkProtocolNumber {
|
||||
case header.IPv4ProtocolNumber:
|
||||
gd.dispatch4(pkt)
|
||||
case header.IPv6ProtocolNumber:
|
||||
gd.dispatch6(pkt)
|
||||
default:
|
||||
gd.handlePacket(pkt)
|
||||
}
|
||||
}
|
||||
|
||||
func (gd *GRO) dispatch4(pkt *stack.PacketBuffer) {
|
||||
// Immediately get the IPv4 and TCP headers. We need a way to hash the
|
||||
// packet into its bucket, which requires addresses and ports. Linux
|
||||
// simply gets a hash passed by hardware, but we're not so lucky.
|
||||
|
||||
// We only GRO TCP packets. The check for the transport protocol number
|
||||
// is done below so that we can PullUp both the IP and TCP headers
|
||||
// together.
|
||||
hdrBytes, ok := pkt.Data().PullUp(header.IPv4MinimumSize + header.TCPMinimumSize)
|
||||
if !ok {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
ipHdr := header.IPv4(hdrBytes)
|
||||
|
||||
// We don't handle fragments. That should be the vast majority of
|
||||
// traffic, and simplifies handling.
|
||||
if ipHdr.FragmentOffset() != 0 || ipHdr.Flags()&header.IPv4FlagMoreFragments != 0 {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
// We only handle TCP packets without IP options.
|
||||
if ipHdr.HeaderLength() != header.IPv4MinimumSize || tcpip.TransportProtocolNumber(ipHdr.Protocol()) != header.TCPProtocolNumber {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
tcpHdr := header.TCP(hdrBytes[header.IPv4MinimumSize:])
|
||||
ipHdr = ipHdr[:header.IPv4MinimumSize]
|
||||
dataOff := tcpHdr.DataOffset()
|
||||
if dataOff < header.TCPMinimumSize {
|
||||
// Malformed packet: will be handled further up the stack.
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
hdrBytes, ok = pkt.Data().PullUp(header.IPv4MinimumSize + int(dataOff))
|
||||
if !ok {
|
||||
// Malformed packet: will be handled further up the stack.
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
tcpHdr = header.TCP(hdrBytes[header.IPv4MinimumSize:])
|
||||
|
||||
// If either checksum is bad, flush the packet. Since we don't know
|
||||
// what bits were flipped, we can't identify this packet with a flow.
|
||||
if !pkt.RXChecksumValidated {
|
||||
if !ipHdr.IsValid(pkt.Data().Size()) || !ipHdr.IsChecksumValid() {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
payloadChecksum := pkt.Data().ChecksumAtOffset(header.IPv4MinimumSize + int(dataOff))
|
||||
tcpPayloadSize := pkt.Data().Size() - header.IPv4MinimumSize - int(dataOff)
|
||||
if !tcpHdr.IsChecksumValid(ipHdr.SourceAddress(), ipHdr.DestinationAddress(), payloadChecksum, uint16(tcpPayloadSize)) {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
// We've validated the checksum, no reason for others to do it
|
||||
// again.
|
||||
pkt.RXChecksumValidated = true
|
||||
}
|
||||
|
||||
// Now we can get the bucket for the packet.
|
||||
bucket := &gd.buckets[gd.bucketForPacket4(ipHdr, tcpHdr)&groNBucketsMask]
|
||||
groPkt, flushGROPkt := bucket.findGROPacket4(pkt, ipHdr, tcpHdr)
|
||||
bucket.found(gd, groPkt, flushGROPkt, pkt, ipHdr, tcpHdr, updateIPv4Hdr)
|
||||
}
|
||||
|
||||
func (gd *GRO) dispatch6(pkt *stack.PacketBuffer) {
|
||||
// Immediately get the IPv6 and TCP headers. We need a way to hash the
|
||||
// packet into its bucket, which requires addresses and ports. Linux
|
||||
// simply gets a hash passed by hardware, but we're not so lucky.
|
||||
|
||||
hdrBytes, ok := pkt.Data().PullUp(header.IPv6MinimumSize)
|
||||
if !ok {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
ipHdr := header.IPv6(hdrBytes)
|
||||
|
||||
// Getting the IP header (+ extension headers) size is a bit of a pain
|
||||
// on IPv6.
|
||||
transProto := tcpip.TransportProtocolNumber(ipHdr.NextHeader())
|
||||
buf := pkt.Data().ToBuffer()
|
||||
buf.TrimFront(header.IPv6MinimumSize)
|
||||
it := header.MakeIPv6PayloadIterator(header.IPv6ExtensionHeaderIdentifier(transProto), buf)
|
||||
ipHdrSize := int(header.IPv6MinimumSize)
|
||||
for {
|
||||
transProto = tcpip.TransportProtocolNumber(it.NextHeaderIdentifier())
|
||||
extHdr, done, err := it.Next()
|
||||
if err != nil {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
if done {
|
||||
break
|
||||
}
|
||||
switch extHdr.(type) {
|
||||
// We can GRO these, so just skip over them.
|
||||
case header.IPv6HopByHopOptionsExtHdr:
|
||||
case header.IPv6RoutingExtHdr:
|
||||
case header.IPv6DestinationOptionsExtHdr:
|
||||
case header.IPv6ExperimentExtHdr:
|
||||
default:
|
||||
// This is either a TCP header or something we can't handle.
|
||||
ipHdrSize = int(it.HeaderOffset())
|
||||
done = true
|
||||
}
|
||||
extHdr.Release()
|
||||
if done {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
hdrBytes, ok = pkt.Data().PullUp(ipHdrSize + header.TCPMinimumSize)
|
||||
if !ok {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
ipHdr = header.IPv6(hdrBytes[:ipHdrSize])
|
||||
|
||||
// We only handle TCP packets.
|
||||
if transProto != header.TCPProtocolNumber {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
tcpHdr := header.TCP(hdrBytes[ipHdrSize:])
|
||||
dataOff := tcpHdr.DataOffset()
|
||||
if dataOff < header.TCPMinimumSize {
|
||||
// Malformed packet: will be handled further up the stack.
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
|
||||
hdrBytes, ok = pkt.Data().PullUp(ipHdrSize + int(dataOff))
|
||||
if !ok {
|
||||
// Malformed packet: will be handled further up the stack.
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
tcpHdr = header.TCP(hdrBytes[ipHdrSize:])
|
||||
|
||||
// If either checksum is bad, flush the packet. Since we don't know
|
||||
// what bits were flipped, we can't identify this packet with a flow.
|
||||
if !pkt.RXChecksumValidated {
|
||||
if !ipHdr.IsValid(pkt.Data().Size()) {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
payloadChecksum := pkt.Data().ChecksumAtOffset(ipHdrSize + int(dataOff))
|
||||
tcpPayloadSize := pkt.Data().Size() - ipHdrSize - int(dataOff)
|
||||
if !tcpHdr.IsChecksumValid(ipHdr.SourceAddress(), ipHdr.DestinationAddress(), payloadChecksum, uint16(tcpPayloadSize)) {
|
||||
gd.handlePacket(pkt)
|
||||
return
|
||||
}
|
||||
// We've validated the checksum, no reason for others to do it
|
||||
// again.
|
||||
pkt.RXChecksumValidated = true
|
||||
}
|
||||
|
||||
// Now we can get the bucket for the packet.
|
||||
bucket := &gd.buckets[gd.bucketForPacket6(ipHdr, tcpHdr)&groNBucketsMask]
|
||||
groPkt, flushGROPkt := bucket.findGROPacket6(pkt, ipHdr, tcpHdr)
|
||||
bucket.found(gd, groPkt, flushGROPkt, pkt, ipHdr, tcpHdr, updateIPv6Hdr)
|
||||
}
|
||||
|
||||
func (gd *GRO) bucketForPacket4(ipHdr header.IPv4, tcpHdr header.TCP) int {
|
||||
// It would be better to use jenkins or checksum.
|
||||
var sum int
|
||||
srcAddr := ipHdr.SourceAddress()
|
||||
for _, val := range srcAddr.AsSlice() {
|
||||
sum += int(val)
|
||||
}
|
||||
dstAddr := ipHdr.DestinationAddress()
|
||||
for _, val := range dstAddr.AsSlice() {
|
||||
sum += int(val)
|
||||
}
|
||||
sum += int(tcpHdr.SourcePort())
|
||||
sum += int(tcpHdr.DestinationPort())
|
||||
return sum
|
||||
}
|
||||
|
||||
func (gd *GRO) bucketForPacket6(ipHdr header.IPv6, tcpHdr header.TCP) int {
|
||||
// It would be better to use jenkins or checksum.
|
||||
var sum int
|
||||
srcAddr := ipHdr.SourceAddress()
|
||||
for _, val := range srcAddr.AsSlice() {
|
||||
sum += int(val)
|
||||
}
|
||||
dstAddr := ipHdr.DestinationAddress()
|
||||
for _, val := range dstAddr.AsSlice() {
|
||||
sum += int(val)
|
||||
}
|
||||
sum += int(tcpHdr.SourcePort())
|
||||
sum += int(tcpHdr.DestinationPort())
|
||||
return sum
|
||||
}
|
||||
|
||||
// Flush sends all packets up the stack.
|
||||
func (gd *GRO) Flush() {
|
||||
for i := range gd.buckets {
|
||||
for groPkt := gd.buckets[i].packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
|
||||
pkt := groPkt.pkt
|
||||
gd.buckets[i].removeOne(groPkt)
|
||||
gd.handlePacket(pkt)
|
||||
pkt.DecRef()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (gd *GRO) handlePacket(pkt *stack.PacketBuffer) {
|
||||
gd.Dispatcher.DeliverNetworkPacket(pkt.NetworkProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
// String implements fmt.Stringer.
|
||||
func (gd *GRO) String() string {
|
||||
ret := "GRO state: \n"
|
||||
for i := range gd.buckets {
|
||||
bucket := &gd.buckets[i]
|
||||
ret += fmt.Sprintf("bucket %d: %d packets: ", i, bucket.count)
|
||||
for groPkt := bucket.packets.Front(); groPkt != nil; groPkt = groPkt.Next() {
|
||||
ret += fmt.Sprintf("%d, ", groPkt.pkt.Data().Size())
|
||||
}
|
||||
ret += "\n"
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// shouldFlushTCP returns whether the TCP headers indicate that groPkt should
|
||||
// be flushed
|
||||
func shouldFlushTCP(groPkt *groPacket, tcpHdr header.TCP) bool {
|
||||
flags := tcpHdr.Flags()
|
||||
groPktFlags := groPkt.tcpHdr.Flags()
|
||||
dataOff := tcpHdr.DataOffset()
|
||||
if flags&header.TCPFlagCwr != 0 || // Is congestion control occurring?
|
||||
(flags^groPktFlags)&^(header.TCPFlagCwr|header.TCPFlagFin|header.TCPFlagPsh) != 0 || // Do the flags differ besides CRW, FIN, and PSH?
|
||||
tcpHdr.AckNumber() != groPkt.tcpHdr.AckNumber() || // Do the ACKs match?
|
||||
dataOff != groPkt.tcpHdr.DataOffset() || // Are the TCP headers the same length?
|
||||
groPkt.tcpHdr.SequenceNumber()+uint32(groPkt.payloadSize()) != tcpHdr.SequenceNumber() { // Does the incoming packet match the expected sequence number?
|
||||
return true
|
||||
}
|
||||
// The options, including timestamps, must be identical.
|
||||
return !bytes.Equal(tcpHdr[header.TCPMinimumSize:], groPkt.tcpHdr[header.TCPMinimumSize:])
|
||||
}
|
||||
|
||||
func updateIPv4Hdr(ipHdrBytes []byte, newBytes int) {
|
||||
ipHdr := header.IPv4(ipHdrBytes)
|
||||
ipHdr.SetTotalLength(ipHdr.TotalLength() + uint16(newBytes))
|
||||
}
|
||||
|
||||
func updateIPv6Hdr(ipHdrBytes []byte, newBytes int) {
|
||||
ipHdr := header.IPv6(ipHdrBytes)
|
||||
ipHdr.SetPayloadLength(ipHdr.PayloadLength() + uint16(newBytes))
|
||||
}
|
||||
239
pkg/tcpip/stack/gro/gro_packet_list.go
Normal file
239
pkg/tcpip/stack/gro/gro_packet_list.go
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package gro
|
||||
|
||||
// ElementMapper provides an identity mapping by default.
|
||||
//
|
||||
// This can be replaced to provide a struct that maps elements to linker
|
||||
// objects, if they are not the same. An ElementMapper is not typically
|
||||
// required if: Linker is left as is, Element is left as is, or Linker and
|
||||
// Element are the same type.
|
||||
type groPacketElementMapper struct{}
|
||||
|
||||
// linkerFor maps an Element to a Linker.
|
||||
//
|
||||
// This default implementation should be inlined.
|
||||
//
|
||||
//go:nosplit
|
||||
func (groPacketElementMapper) linkerFor(elem *groPacket) *groPacket { return elem }
|
||||
|
||||
// List is an intrusive list. Entries can be added to or removed from the list
|
||||
// in O(1) time and with no additional memory allocations.
|
||||
//
|
||||
// The zero value for List is an empty list ready to use.
|
||||
//
|
||||
// To iterate over a list (where l is a List):
|
||||
//
|
||||
// for e := l.Front(); e != nil; e = e.Next() {
|
||||
// // do something with e.
|
||||
// }
|
||||
//
|
||||
// +stateify savable
|
||||
type groPacketList struct {
|
||||
head *groPacket
|
||||
tail *groPacket
|
||||
}
|
||||
|
||||
// Reset resets list l to the empty state.
|
||||
func (l *groPacketList) Reset() {
|
||||
l.head = nil
|
||||
l.tail = nil
|
||||
}
|
||||
|
||||
// Empty returns true iff the list is empty.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) Empty() bool {
|
||||
return l.head == nil
|
||||
}
|
||||
|
||||
// Front returns the first element of list l or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) Front() *groPacket {
|
||||
return l.head
|
||||
}
|
||||
|
||||
// Back returns the last element of list l or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) Back() *groPacket {
|
||||
return l.tail
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the list.
|
||||
//
|
||||
// NOTE: This is an O(n) operation.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) Len() (count int) {
|
||||
for e := l.Front(); e != nil; e = (groPacketElementMapper{}.linkerFor(e)).Next() {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// PushFront inserts the element e at the front of list l.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) PushFront(e *groPacket) {
|
||||
linker := groPacketElementMapper{}.linkerFor(e)
|
||||
linker.SetNext(l.head)
|
||||
linker.SetPrev(nil)
|
||||
if l.head != nil {
|
||||
groPacketElementMapper{}.linkerFor(l.head).SetPrev(e)
|
||||
} else {
|
||||
l.tail = e
|
||||
}
|
||||
|
||||
l.head = e
|
||||
}
|
||||
|
||||
// PushFrontList inserts list m at the start of list l, emptying m.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) PushFrontList(m *groPacketList) {
|
||||
if l.head == nil {
|
||||
l.head = m.head
|
||||
l.tail = m.tail
|
||||
} else if m.head != nil {
|
||||
groPacketElementMapper{}.linkerFor(l.head).SetPrev(m.tail)
|
||||
groPacketElementMapper{}.linkerFor(m.tail).SetNext(l.head)
|
||||
|
||||
l.head = m.head
|
||||
}
|
||||
m.head = nil
|
||||
m.tail = nil
|
||||
}
|
||||
|
||||
// PushBack inserts the element e at the back of list l.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) PushBack(e *groPacket) {
|
||||
linker := groPacketElementMapper{}.linkerFor(e)
|
||||
linker.SetNext(nil)
|
||||
linker.SetPrev(l.tail)
|
||||
if l.tail != nil {
|
||||
groPacketElementMapper{}.linkerFor(l.tail).SetNext(e)
|
||||
} else {
|
||||
l.head = e
|
||||
}
|
||||
|
||||
l.tail = e
|
||||
}
|
||||
|
||||
// PushBackList inserts list m at the end of list l, emptying m.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) PushBackList(m *groPacketList) {
|
||||
if l.head == nil {
|
||||
l.head = m.head
|
||||
l.tail = m.tail
|
||||
} else if m.head != nil {
|
||||
groPacketElementMapper{}.linkerFor(l.tail).SetNext(m.head)
|
||||
groPacketElementMapper{}.linkerFor(m.head).SetPrev(l.tail)
|
||||
|
||||
l.tail = m.tail
|
||||
}
|
||||
m.head = nil
|
||||
m.tail = nil
|
||||
}
|
||||
|
||||
// InsertAfter inserts e after b.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) InsertAfter(b, e *groPacket) {
|
||||
bLinker := groPacketElementMapper{}.linkerFor(b)
|
||||
eLinker := groPacketElementMapper{}.linkerFor(e)
|
||||
|
||||
a := bLinker.Next()
|
||||
|
||||
eLinker.SetNext(a)
|
||||
eLinker.SetPrev(b)
|
||||
bLinker.SetNext(e)
|
||||
|
||||
if a != nil {
|
||||
groPacketElementMapper{}.linkerFor(a).SetPrev(e)
|
||||
} else {
|
||||
l.tail = e
|
||||
}
|
||||
}
|
||||
|
||||
// InsertBefore inserts e before a.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) InsertBefore(a, e *groPacket) {
|
||||
aLinker := groPacketElementMapper{}.linkerFor(a)
|
||||
eLinker := groPacketElementMapper{}.linkerFor(e)
|
||||
|
||||
b := aLinker.Prev()
|
||||
eLinker.SetNext(a)
|
||||
eLinker.SetPrev(b)
|
||||
aLinker.SetPrev(e)
|
||||
|
||||
if b != nil {
|
||||
groPacketElementMapper{}.linkerFor(b).SetNext(e)
|
||||
} else {
|
||||
l.head = e
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes e from l.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *groPacketList) Remove(e *groPacket) {
|
||||
linker := groPacketElementMapper{}.linkerFor(e)
|
||||
prev := linker.Prev()
|
||||
next := linker.Next()
|
||||
|
||||
if prev != nil {
|
||||
groPacketElementMapper{}.linkerFor(prev).SetNext(next)
|
||||
} else if l.head == e {
|
||||
l.head = next
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
groPacketElementMapper{}.linkerFor(next).SetPrev(prev)
|
||||
} else if l.tail == e {
|
||||
l.tail = prev
|
||||
}
|
||||
|
||||
linker.SetNext(nil)
|
||||
linker.SetPrev(nil)
|
||||
}
|
||||
|
||||
// Entry is a default implementation of Linker. Users can add anonymous fields
|
||||
// of this type to their structs to make them automatically implement the
|
||||
// methods needed by List.
|
||||
//
|
||||
// +stateify savable
|
||||
type groPacketEntry struct {
|
||||
next *groPacket
|
||||
prev *groPacket
|
||||
}
|
||||
|
||||
// Next returns the entry that follows e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *groPacketEntry) Next() *groPacket {
|
||||
return e.next
|
||||
}
|
||||
|
||||
// Prev returns the entry that precedes e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *groPacketEntry) Prev() *groPacket {
|
||||
return e.prev
|
||||
}
|
||||
|
||||
// SetNext assigns 'entry' as the entry that follows e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *groPacketEntry) SetNext(elem *groPacket) {
|
||||
e.next = elem
|
||||
}
|
||||
|
||||
// SetPrev assigns 'entry' as the entry that precedes e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *groPacketEntry) SetPrev(elem *groPacket) {
|
||||
e.prev = elem
|
||||
}
|
||||
178
pkg/tcpip/stack/gro/gro_state_autogen.go
Normal file
178
pkg/tcpip/stack/gro/gro_state_autogen.go
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package gro
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (gb *groBucket) StateTypeName() string {
|
||||
return "pkg/tcpip/stack/gro.groBucket"
|
||||
}
|
||||
|
||||
func (gb *groBucket) StateFields() []string {
|
||||
return []string{
|
||||
"count",
|
||||
"packets",
|
||||
"packetsPrealloc",
|
||||
"allocIdxs",
|
||||
}
|
||||
}
|
||||
|
||||
func (gb *groBucket) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (gb *groBucket) StateSave(stateSinkObject state.Sink) {
|
||||
gb.beforeSave()
|
||||
stateSinkObject.Save(0, &gb.count)
|
||||
stateSinkObject.Save(1, &gb.packets)
|
||||
stateSinkObject.Save(2, &gb.packetsPrealloc)
|
||||
stateSinkObject.Save(3, &gb.allocIdxs)
|
||||
}
|
||||
|
||||
func (gb *groBucket) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (gb *groBucket) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &gb.count)
|
||||
stateSourceObject.Load(1, &gb.packets)
|
||||
stateSourceObject.Load(2, &gb.packetsPrealloc)
|
||||
stateSourceObject.Load(3, &gb.allocIdxs)
|
||||
}
|
||||
|
||||
func (pk *groPacket) StateTypeName() string {
|
||||
return "pkg/tcpip/stack/gro.groPacket"
|
||||
}
|
||||
|
||||
func (pk *groPacket) StateFields() []string {
|
||||
return []string{
|
||||
"groPacketEntry",
|
||||
"pkt",
|
||||
"ipHdr",
|
||||
"tcpHdr",
|
||||
"initialLength",
|
||||
"idx",
|
||||
}
|
||||
}
|
||||
|
||||
func (pk *groPacket) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pk *groPacket) StateSave(stateSinkObject state.Sink) {
|
||||
pk.beforeSave()
|
||||
stateSinkObject.Save(0, &pk.groPacketEntry)
|
||||
stateSinkObject.Save(1, &pk.pkt)
|
||||
stateSinkObject.Save(2, &pk.ipHdr)
|
||||
stateSinkObject.Save(3, &pk.tcpHdr)
|
||||
stateSinkObject.Save(4, &pk.initialLength)
|
||||
stateSinkObject.Save(5, &pk.idx)
|
||||
}
|
||||
|
||||
func (pk *groPacket) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (pk *groPacket) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &pk.groPacketEntry)
|
||||
stateSourceObject.Load(1, &pk.pkt)
|
||||
stateSourceObject.Load(2, &pk.ipHdr)
|
||||
stateSourceObject.Load(3, &pk.tcpHdr)
|
||||
stateSourceObject.Load(4, &pk.initialLength)
|
||||
stateSourceObject.Load(5, &pk.idx)
|
||||
}
|
||||
|
||||
func (gd *GRO) StateTypeName() string {
|
||||
return "pkg/tcpip/stack/gro.GRO"
|
||||
}
|
||||
|
||||
func (gd *GRO) StateFields() []string {
|
||||
return []string{
|
||||
"enabled",
|
||||
"buckets",
|
||||
"Dispatcher",
|
||||
}
|
||||
}
|
||||
|
||||
func (gd *GRO) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (gd *GRO) StateSave(stateSinkObject state.Sink) {
|
||||
gd.beforeSave()
|
||||
stateSinkObject.Save(0, &gd.enabled)
|
||||
stateSinkObject.Save(1, &gd.buckets)
|
||||
stateSinkObject.Save(2, &gd.Dispatcher)
|
||||
}
|
||||
|
||||
func (gd *GRO) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (gd *GRO) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &gd.enabled)
|
||||
stateSourceObject.Load(1, &gd.buckets)
|
||||
stateSourceObject.Load(2, &gd.Dispatcher)
|
||||
}
|
||||
|
||||
func (l *groPacketList) StateTypeName() string {
|
||||
return "pkg/tcpip/stack/gro.groPacketList"
|
||||
}
|
||||
|
||||
func (l *groPacketList) StateFields() []string {
|
||||
return []string{
|
||||
"head",
|
||||
"tail",
|
||||
}
|
||||
}
|
||||
|
||||
func (l *groPacketList) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (l *groPacketList) StateSave(stateSinkObject state.Sink) {
|
||||
l.beforeSave()
|
||||
stateSinkObject.Save(0, &l.head)
|
||||
stateSinkObject.Save(1, &l.tail)
|
||||
}
|
||||
|
||||
func (l *groPacketList) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (l *groPacketList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &l.head)
|
||||
stateSourceObject.Load(1, &l.tail)
|
||||
}
|
||||
|
||||
func (e *groPacketEntry) StateTypeName() string {
|
||||
return "pkg/tcpip/stack/gro.groPacketEntry"
|
||||
}
|
||||
|
||||
func (e *groPacketEntry) StateFields() []string {
|
||||
return []string{
|
||||
"next",
|
||||
"prev",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *groPacketEntry) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *groPacketEntry) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.next)
|
||||
stateSinkObject.Save(1, &e.prev)
|
||||
}
|
||||
|
||||
func (e *groPacketEntry) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *groPacketEntry) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.next)
|
||||
stateSourceObject.Load(1, &e.prev)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*groBucket)(nil))
|
||||
state.Register((*groPacket)(nil))
|
||||
state.Register((*GRO)(nil))
|
||||
state.Register((*groPacketList)(nil))
|
||||
state.Register((*groPacketEntry)(nil))
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue