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
375
pkg/tcpip/network/internal/fragmentation/fragmentation.go
Normal file
375
pkg/tcpip/network/internal/fragmentation/fragmentation.go
Normal file
|
|
@ -0,0 +1,375 @@
|
|||
// Copyright 2018 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 fragmentation contains the implementation of IP fragmentation.
|
||||
// It is based on RFC 791, RFC 815 and RFC 8200.
|
||||
package fragmentation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/buffer"
|
||||
"github.com/sagernet/gvisor/pkg/log"
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
const (
|
||||
// HighFragThreshold is the threshold at which we start trimming old
|
||||
// fragmented packets. Linux uses a default value of 4 MB. See
|
||||
// net.ipv4.ipfrag_high_thresh for more information.
|
||||
HighFragThreshold = 4 << 20 // 4MB
|
||||
|
||||
// LowFragThreshold is the threshold we reach to when we start dropping
|
||||
// older fragmented packets. It's important that we keep enough room for newer
|
||||
// packets to be re-assembled. Hence, this needs to be lower than
|
||||
// HighFragThreshold enough. Linux uses a default value of 3 MB. See
|
||||
// net.ipv4.ipfrag_low_thresh for more information.
|
||||
LowFragThreshold = 3 << 20 // 3MB
|
||||
|
||||
// minBlockSize is the minimum block size for fragments.
|
||||
minBlockSize = 1
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrInvalidArgs indicates to the caller that an invalid argument was
|
||||
// provided.
|
||||
ErrInvalidArgs = errors.New("invalid args")
|
||||
|
||||
// ErrFragmentOverlap indicates that, during reassembly, a fragment overlaps
|
||||
// with another one.
|
||||
ErrFragmentOverlap = errors.New("overlapping fragments")
|
||||
|
||||
// ErrFragmentConflict indicates that, during reassembly, some fragments are
|
||||
// in conflict with one another.
|
||||
ErrFragmentConflict = errors.New("conflicting fragments")
|
||||
)
|
||||
|
||||
// FragmentID is the identifier for a fragment.
|
||||
//
|
||||
// +stateify savable
|
||||
type FragmentID struct {
|
||||
// Source is the source address of the fragment.
|
||||
Source tcpip.Address
|
||||
|
||||
// Destination is the destination address of the fragment.
|
||||
Destination tcpip.Address
|
||||
|
||||
// ID is the identification value of the fragment.
|
||||
//
|
||||
// This is a uint32 because IPv6 uses a 32-bit identification value.
|
||||
ID uint32
|
||||
|
||||
// The protocol for the packet.
|
||||
Protocol uint8
|
||||
}
|
||||
|
||||
// Fragmentation is the main structure that other modules
|
||||
// of the stack should use to implement IP Fragmentation.
|
||||
//
|
||||
// +stateify savable
|
||||
type Fragmentation struct {
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
highLimit int
|
||||
lowLimit int
|
||||
reassemblers map[FragmentID]*reassembler
|
||||
rList reassemblerList
|
||||
memSize int
|
||||
timeout time.Duration
|
||||
blockSize uint16
|
||||
clock tcpip.Clock
|
||||
releaseJob *tcpip.Job
|
||||
timeoutHandler TimeoutHandler
|
||||
}
|
||||
|
||||
// TimeoutHandler is consulted if a packet reassembly has timed out.
|
||||
type TimeoutHandler interface {
|
||||
// OnReassemblyTimeout will be called with the first fragment (or nil, if the
|
||||
// first fragment has not been received) of a packet whose reassembly has
|
||||
// timed out.
|
||||
OnReassemblyTimeout(pkt *stack.PacketBuffer)
|
||||
}
|
||||
|
||||
// NewFragmentation creates a new Fragmentation.
|
||||
//
|
||||
// blockSize specifies the fragment block size, in bytes.
|
||||
//
|
||||
// highMemoryLimit specifies the limit on the memory consumed
|
||||
// by the fragments stored by Fragmentation (overhead of internal data-structures
|
||||
// is not accounted). Fragments are dropped when the limit is reached.
|
||||
//
|
||||
// lowMemoryLimit specifies the limit on which we will reach by dropping
|
||||
// fragments after reaching highMemoryLimit.
|
||||
//
|
||||
// reassemblingTimeout specifies the maximum time allowed to reassemble a packet.
|
||||
// Fragments are lazily evicted only when a new a packet with an
|
||||
// already existing fragmentation-id arrives after the timeout.
|
||||
func NewFragmentation(blockSize uint16, highMemoryLimit, lowMemoryLimit int, reassemblingTimeout time.Duration, clock tcpip.Clock, timeoutHandler TimeoutHandler) *Fragmentation {
|
||||
if lowMemoryLimit >= highMemoryLimit {
|
||||
lowMemoryLimit = highMemoryLimit
|
||||
}
|
||||
|
||||
if lowMemoryLimit < 0 {
|
||||
lowMemoryLimit = 0
|
||||
}
|
||||
|
||||
if blockSize < minBlockSize {
|
||||
blockSize = minBlockSize
|
||||
}
|
||||
|
||||
f := &Fragmentation{
|
||||
reassemblers: make(map[FragmentID]*reassembler),
|
||||
highLimit: highMemoryLimit,
|
||||
lowLimit: lowMemoryLimit,
|
||||
timeout: reassemblingTimeout,
|
||||
blockSize: blockSize,
|
||||
clock: clock,
|
||||
timeoutHandler: timeoutHandler,
|
||||
}
|
||||
f.releaseJob = tcpip.NewJob(f.clock, &f.mu, f.releaseReassemblersLocked)
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// Process processes an incoming fragment belonging to an ID and returns a
|
||||
// complete packet and its protocol number when all the packets belonging to
|
||||
// that ID have been received.
|
||||
//
|
||||
// [first, last] is the range of the fragment bytes.
|
||||
//
|
||||
// first must be a multiple of the block size f is configured with. The size
|
||||
// of the fragment data must be a multiple of the block size, unless there are
|
||||
// no fragments following this fragment (more set to false).
|
||||
//
|
||||
// proto is the protocol number marked in the fragment being processed. It has
|
||||
// to be given here outside of the FragmentID struct because IPv6 should not use
|
||||
// the protocol to identify a fragment.
|
||||
func (f *Fragmentation) Process(
|
||||
id FragmentID, first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (
|
||||
*stack.PacketBuffer, uint8, bool, error,
|
||||
) {
|
||||
if first > last {
|
||||
return nil, 0, false, fmt.Errorf("first=%d is greater than last=%d: %w", first, last, ErrInvalidArgs)
|
||||
}
|
||||
|
||||
if first%f.blockSize != 0 {
|
||||
return nil, 0, false, fmt.Errorf("first=%d is not a multiple of block size=%d: %w", first, f.blockSize, ErrInvalidArgs)
|
||||
}
|
||||
|
||||
fragmentSize := last - first + 1
|
||||
if more && fragmentSize%f.blockSize != 0 {
|
||||
return nil, 0, false, fmt.Errorf("fragment size=%d bytes is not a multiple of block size=%d on non-final fragment: %w", fragmentSize, f.blockSize, ErrInvalidArgs)
|
||||
}
|
||||
|
||||
if l := pkt.Data().Size(); l != int(fragmentSize) {
|
||||
return nil, 0, false, fmt.Errorf("got fragment size=%d bytes not equal to the expected fragment size=%d bytes (first=%d last=%d): %w", l, fragmentSize, first, last, ErrInvalidArgs)
|
||||
}
|
||||
|
||||
f.mu.Lock()
|
||||
if f.reassemblers == nil {
|
||||
return nil, 0, false, fmt.Errorf("Release() called before fragmentation processing could finish")
|
||||
}
|
||||
|
||||
r, ok := f.reassemblers[id]
|
||||
if !ok {
|
||||
r = newReassembler(id, f.clock)
|
||||
f.reassemblers[id] = r
|
||||
wasEmpty := f.rList.Empty()
|
||||
f.rList.PushFront(r)
|
||||
if wasEmpty {
|
||||
// If we have just pushed a first reassembler into an empty list, we
|
||||
// should kickstart the release job. The release job will keep
|
||||
// rescheduling itself until the list becomes empty.
|
||||
f.releaseReassemblersLocked()
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
resPkt, firstFragmentProto, done, memConsumed, err := r.process(first, last, more, proto, pkt)
|
||||
if err != nil {
|
||||
// We probably got an invalid sequence of fragments. Just
|
||||
// discard the reassembler and move on.
|
||||
f.mu.Lock()
|
||||
f.release(r, false /* timedOut */)
|
||||
f.mu.Unlock()
|
||||
return nil, 0, false, fmt.Errorf("fragmentation processing error: %w", err)
|
||||
}
|
||||
f.mu.Lock()
|
||||
f.memSize += memConsumed
|
||||
if done {
|
||||
f.release(r, false /* timedOut */)
|
||||
}
|
||||
// Evict reassemblers if we are consuming more memory than highLimit until
|
||||
// we reach lowLimit.
|
||||
if f.memSize > f.highLimit {
|
||||
for f.memSize > f.lowLimit {
|
||||
tail := f.rList.Back()
|
||||
if tail == nil {
|
||||
break
|
||||
}
|
||||
f.release(tail, false /* timedOut */)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return resPkt, firstFragmentProto, done, nil
|
||||
}
|
||||
|
||||
// Release releases all underlying resources.
|
||||
func (f *Fragmentation) Release() {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
for _, r := range f.reassemblers {
|
||||
f.release(r, false /* timedOut */)
|
||||
}
|
||||
f.reassemblers = nil
|
||||
}
|
||||
|
||||
func (f *Fragmentation) release(r *reassembler, timedOut bool) {
|
||||
// Before releasing a fragment we need to check if r is already marked as done.
|
||||
// Otherwise, we would delete it twice.
|
||||
if r.checkDoneOrMark() {
|
||||
return
|
||||
}
|
||||
|
||||
delete(f.reassemblers, r.id)
|
||||
f.rList.Remove(r)
|
||||
f.memSize -= r.memSize
|
||||
if f.memSize < 0 {
|
||||
log.Warningf("memory counter < 0 (%d), this is an accounting bug that requires investigation", f.memSize)
|
||||
f.memSize = 0
|
||||
}
|
||||
|
||||
if h := f.timeoutHandler; timedOut && h != nil {
|
||||
h.OnReassemblyTimeout(r.pkt)
|
||||
}
|
||||
if r.pkt != nil {
|
||||
r.pkt.DecRef()
|
||||
r.pkt = nil
|
||||
}
|
||||
for _, h := range r.holes {
|
||||
if h.pkt != nil {
|
||||
h.pkt.DecRef()
|
||||
h.pkt = nil
|
||||
}
|
||||
}
|
||||
r.holes = nil
|
||||
}
|
||||
|
||||
// releaseReassemblersLocked releases already-expired reassemblers, then
|
||||
// schedules the job to call back itself for the remaining reassemblers if
|
||||
// any. This function must be called with f.mu locked.
|
||||
func (f *Fragmentation) releaseReassemblersLocked() {
|
||||
now := f.clock.NowMonotonic()
|
||||
for {
|
||||
// The reassembler at the end of the list is the oldest.
|
||||
r := f.rList.Back()
|
||||
if r == nil {
|
||||
// The list is empty.
|
||||
break
|
||||
}
|
||||
elapsed := now.Sub(r.createdAt)
|
||||
if f.timeout > elapsed {
|
||||
// If the oldest reassembler has not expired, schedule the release
|
||||
// job so that this function is called back when it has expired.
|
||||
f.releaseJob.Schedule(f.timeout - elapsed)
|
||||
break
|
||||
}
|
||||
// If the oldest reassembler has already expired, release it.
|
||||
f.release(r, true /* timedOut*/)
|
||||
}
|
||||
}
|
||||
|
||||
// PacketFragmenter is the book-keeping struct for packet fragmentation.
|
||||
type PacketFragmenter struct {
|
||||
transportHeader []byte
|
||||
data buffer.Buffer
|
||||
reserve int
|
||||
fragmentPayloadLen int
|
||||
fragmentCount int
|
||||
currentFragment int
|
||||
fragmentOffset int
|
||||
}
|
||||
|
||||
// MakePacketFragmenter prepares the struct needed for packet fragmentation.
|
||||
//
|
||||
// pkt is the packet to be fragmented.
|
||||
//
|
||||
// fragmentPayloadLen is the maximum number of bytes of fragmentable data a fragment can
|
||||
// have.
|
||||
//
|
||||
// reserve is the number of bytes that should be reserved for the headers in
|
||||
// each generated fragment.
|
||||
func MakePacketFragmenter(pkt *stack.PacketBuffer, fragmentPayloadLen uint32, reserve int) PacketFragmenter {
|
||||
// As per RFC 8200 Section 4.5, some IPv6 extension headers should not be
|
||||
// repeated in each fragment. However we do not currently support any header
|
||||
// of that kind yet, so the following computation is valid for both IPv4 and
|
||||
// IPv6.
|
||||
// TODO(gvisor.dev/issue/3912): Once Authentication or ESP Headers are
|
||||
// supported for outbound packets, the fragmentable data should not include
|
||||
// these headers.
|
||||
var fragmentableData buffer.Buffer
|
||||
fragmentableData.Append(pkt.TransportHeader().View())
|
||||
pktBuf := pkt.Data().ToBuffer()
|
||||
fragmentableData.Merge(&pktBuf)
|
||||
fragmentCount := (uint32(fragmentableData.Size()) + fragmentPayloadLen - 1) / fragmentPayloadLen
|
||||
|
||||
return PacketFragmenter{
|
||||
data: fragmentableData,
|
||||
reserve: reserve,
|
||||
fragmentPayloadLen: int(fragmentPayloadLen),
|
||||
fragmentCount: int(fragmentCount),
|
||||
}
|
||||
}
|
||||
|
||||
// BuildNextFragment returns a packet with the payload of the next fragment,
|
||||
// along with the fragment's offset, the number of bytes copied and a boolean
|
||||
// indicating if there are more fragments left or not. If this function is
|
||||
// called again after it indicated that no more fragments were left, it will
|
||||
// panic.
|
||||
//
|
||||
// Note that the returned packet will not have its network and link headers
|
||||
// populated, but space for them will be reserved. The transport header will be
|
||||
// stored in the packet's data.
|
||||
func (pf *PacketFragmenter) BuildNextFragment() (*stack.PacketBuffer, int, int, bool) {
|
||||
if pf.currentFragment >= pf.fragmentCount {
|
||||
panic("BuildNextFragment should not be called again after the last fragment was returned")
|
||||
}
|
||||
|
||||
fragPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
ReserveHeaderBytes: pf.reserve,
|
||||
})
|
||||
|
||||
// Copy data for the fragment.
|
||||
copied := fragPkt.Data().ReadFrom(&pf.data, pf.fragmentPayloadLen)
|
||||
|
||||
offset := pf.fragmentOffset
|
||||
pf.fragmentOffset += copied
|
||||
pf.currentFragment++
|
||||
more := pf.currentFragment != pf.fragmentCount
|
||||
|
||||
return fragPkt, offset, copied, more
|
||||
}
|
||||
|
||||
// RemainingFragmentCount returns the number of fragments left to be built.
|
||||
func (pf *PacketFragmenter) RemainingFragmentCount() int {
|
||||
return pf.fragmentCount - pf.currentFragment
|
||||
}
|
||||
|
||||
// Release frees resources owned by the packet fragmenter.
|
||||
func (pf *PacketFragmenter) Release() {
|
||||
pf.data.Release()
|
||||
}
|
||||
|
|
@ -0,0 +1,246 @@
|
|||
// automatically generated by stateify.
|
||||
|
||||
package fragmentation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/state"
|
||||
)
|
||||
|
||||
func (f *FragmentID) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.FragmentID"
|
||||
}
|
||||
|
||||
func (f *FragmentID) StateFields() []string {
|
||||
return []string{
|
||||
"Source",
|
||||
"Destination",
|
||||
"ID",
|
||||
"Protocol",
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FragmentID) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *FragmentID) StateSave(stateSinkObject state.Sink) {
|
||||
f.beforeSave()
|
||||
stateSinkObject.Save(0, &f.Source)
|
||||
stateSinkObject.Save(1, &f.Destination)
|
||||
stateSinkObject.Save(2, &f.ID)
|
||||
stateSinkObject.Save(3, &f.Protocol)
|
||||
}
|
||||
|
||||
func (f *FragmentID) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *FragmentID) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &f.Source)
|
||||
stateSourceObject.Load(1, &f.Destination)
|
||||
stateSourceObject.Load(2, &f.ID)
|
||||
stateSourceObject.Load(3, &f.Protocol)
|
||||
}
|
||||
|
||||
func (f *Fragmentation) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.Fragmentation"
|
||||
}
|
||||
|
||||
func (f *Fragmentation) StateFields() []string {
|
||||
return []string{
|
||||
"highLimit",
|
||||
"lowLimit",
|
||||
"reassemblers",
|
||||
"rList",
|
||||
"memSize",
|
||||
"timeout",
|
||||
"blockSize",
|
||||
"clock",
|
||||
"releaseJob",
|
||||
"timeoutHandler",
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Fragmentation) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *Fragmentation) StateSave(stateSinkObject state.Sink) {
|
||||
f.beforeSave()
|
||||
stateSinkObject.Save(0, &f.highLimit)
|
||||
stateSinkObject.Save(1, &f.lowLimit)
|
||||
stateSinkObject.Save(2, &f.reassemblers)
|
||||
stateSinkObject.Save(3, &f.rList)
|
||||
stateSinkObject.Save(4, &f.memSize)
|
||||
stateSinkObject.Save(5, &f.timeout)
|
||||
stateSinkObject.Save(6, &f.blockSize)
|
||||
stateSinkObject.Save(7, &f.clock)
|
||||
stateSinkObject.Save(8, &f.releaseJob)
|
||||
stateSinkObject.Save(9, &f.timeoutHandler)
|
||||
}
|
||||
|
||||
func (f *Fragmentation) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (f *Fragmentation) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &f.highLimit)
|
||||
stateSourceObject.Load(1, &f.lowLimit)
|
||||
stateSourceObject.Load(2, &f.reassemblers)
|
||||
stateSourceObject.Load(3, &f.rList)
|
||||
stateSourceObject.Load(4, &f.memSize)
|
||||
stateSourceObject.Load(5, &f.timeout)
|
||||
stateSourceObject.Load(6, &f.blockSize)
|
||||
stateSourceObject.Load(7, &f.clock)
|
||||
stateSourceObject.Load(8, &f.releaseJob)
|
||||
stateSourceObject.Load(9, &f.timeoutHandler)
|
||||
}
|
||||
|
||||
func (h *hole) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.hole"
|
||||
}
|
||||
|
||||
func (h *hole) StateFields() []string {
|
||||
return []string{
|
||||
"first",
|
||||
"last",
|
||||
"filled",
|
||||
"final",
|
||||
"pkt",
|
||||
}
|
||||
}
|
||||
|
||||
func (h *hole) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (h *hole) StateSave(stateSinkObject state.Sink) {
|
||||
h.beforeSave()
|
||||
stateSinkObject.Save(0, &h.first)
|
||||
stateSinkObject.Save(1, &h.last)
|
||||
stateSinkObject.Save(2, &h.filled)
|
||||
stateSinkObject.Save(3, &h.final)
|
||||
stateSinkObject.Save(4, &h.pkt)
|
||||
}
|
||||
|
||||
func (h *hole) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (h *hole) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &h.first)
|
||||
stateSourceObject.Load(1, &h.last)
|
||||
stateSourceObject.Load(2, &h.filled)
|
||||
stateSourceObject.Load(3, &h.final)
|
||||
stateSourceObject.Load(4, &h.pkt)
|
||||
}
|
||||
|
||||
func (r *reassembler) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.reassembler"
|
||||
}
|
||||
|
||||
func (r *reassembler) StateFields() []string {
|
||||
return []string{
|
||||
"reassemblerEntry",
|
||||
"id",
|
||||
"memSize",
|
||||
"proto",
|
||||
"holes",
|
||||
"filled",
|
||||
"done",
|
||||
"createdAt",
|
||||
"pkt",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reassembler) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *reassembler) StateSave(stateSinkObject state.Sink) {
|
||||
r.beforeSave()
|
||||
stateSinkObject.Save(0, &r.reassemblerEntry)
|
||||
stateSinkObject.Save(1, &r.id)
|
||||
stateSinkObject.Save(2, &r.memSize)
|
||||
stateSinkObject.Save(3, &r.proto)
|
||||
stateSinkObject.Save(4, &r.holes)
|
||||
stateSinkObject.Save(5, &r.filled)
|
||||
stateSinkObject.Save(6, &r.done)
|
||||
stateSinkObject.Save(7, &r.createdAt)
|
||||
stateSinkObject.Save(8, &r.pkt)
|
||||
}
|
||||
|
||||
func (r *reassembler) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (r *reassembler) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &r.reassemblerEntry)
|
||||
stateSourceObject.Load(1, &r.id)
|
||||
stateSourceObject.Load(2, &r.memSize)
|
||||
stateSourceObject.Load(3, &r.proto)
|
||||
stateSourceObject.Load(4, &r.holes)
|
||||
stateSourceObject.Load(5, &r.filled)
|
||||
stateSourceObject.Load(6, &r.done)
|
||||
stateSourceObject.Load(7, &r.createdAt)
|
||||
stateSourceObject.Load(8, &r.pkt)
|
||||
}
|
||||
|
||||
func (l *reassemblerList) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.reassemblerList"
|
||||
}
|
||||
|
||||
func (l *reassemblerList) StateFields() []string {
|
||||
return []string{
|
||||
"head",
|
||||
"tail",
|
||||
}
|
||||
}
|
||||
|
||||
func (l *reassemblerList) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (l *reassemblerList) StateSave(stateSinkObject state.Sink) {
|
||||
l.beforeSave()
|
||||
stateSinkObject.Save(0, &l.head)
|
||||
stateSinkObject.Save(1, &l.tail)
|
||||
}
|
||||
|
||||
func (l *reassemblerList) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (l *reassemblerList) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &l.head)
|
||||
stateSourceObject.Load(1, &l.tail)
|
||||
}
|
||||
|
||||
func (e *reassemblerEntry) StateTypeName() string {
|
||||
return "pkg/tcpip/network/internal/fragmentation.reassemblerEntry"
|
||||
}
|
||||
|
||||
func (e *reassemblerEntry) StateFields() []string {
|
||||
return []string{
|
||||
"next",
|
||||
"prev",
|
||||
}
|
||||
}
|
||||
|
||||
func (e *reassemblerEntry) beforeSave() {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *reassemblerEntry) StateSave(stateSinkObject state.Sink) {
|
||||
e.beforeSave()
|
||||
stateSinkObject.Save(0, &e.next)
|
||||
stateSinkObject.Save(1, &e.prev)
|
||||
}
|
||||
|
||||
func (e *reassemblerEntry) afterLoad(context.Context) {}
|
||||
|
||||
// +checklocksignore
|
||||
func (e *reassemblerEntry) StateLoad(ctx context.Context, stateSourceObject state.Source) {
|
||||
stateSourceObject.Load(0, &e.next)
|
||||
stateSourceObject.Load(1, &e.prev)
|
||||
}
|
||||
|
||||
func init() {
|
||||
state.Register((*FragmentID)(nil))
|
||||
state.Register((*Fragmentation)(nil))
|
||||
state.Register((*hole)(nil))
|
||||
state.Register((*reassembler)(nil))
|
||||
state.Register((*reassemblerList)(nil))
|
||||
state.Register((*reassemblerEntry)(nil))
|
||||
}
|
||||
185
pkg/tcpip/network/internal/fragmentation/reassembler.go
Normal file
185
pkg/tcpip/network/internal/fragmentation/reassembler.go
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
// Copyright 2018 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 fragmentation
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/sagernet/gvisor/pkg/sync"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip"
|
||||
"github.com/sagernet/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
// +stateify savable
|
||||
type hole struct {
|
||||
first uint16
|
||||
last uint16
|
||||
filled bool
|
||||
final bool
|
||||
// pkt is the fragment packet if hole is filled. We keep the whole pkt rather
|
||||
// than the fragmented payload to prevent binding to specific buffer types.
|
||||
pkt *stack.PacketBuffer
|
||||
}
|
||||
|
||||
// +stateify savable
|
||||
type reassembler struct {
|
||||
reassemblerEntry
|
||||
id FragmentID
|
||||
memSize int
|
||||
proto uint8
|
||||
mu sync.Mutex `state:"nosave"`
|
||||
holes []hole
|
||||
filled int
|
||||
done bool
|
||||
createdAt tcpip.MonotonicTime
|
||||
pkt *stack.PacketBuffer
|
||||
}
|
||||
|
||||
func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
|
||||
r := &reassembler{
|
||||
id: id,
|
||||
createdAt: clock.NowMonotonic(),
|
||||
}
|
||||
r.holes = append(r.holes, hole{
|
||||
first: 0,
|
||||
last: math.MaxUint16,
|
||||
filled: false,
|
||||
final: true,
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (*stack.PacketBuffer, uint8, bool, int, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.done {
|
||||
// A concurrent goroutine might have already reassembled
|
||||
// the packet and emptied the heap while this goroutine
|
||||
// was waiting on the mutex. We don't have to do anything in this case.
|
||||
return nil, 0, false, 0, nil
|
||||
}
|
||||
|
||||
var holeFound bool
|
||||
var memConsumed int
|
||||
for i := range r.holes {
|
||||
currentHole := &r.holes[i]
|
||||
|
||||
if last < currentHole.first || currentHole.last < first {
|
||||
continue
|
||||
}
|
||||
// For IPv6, overlaps with an existing fragment are explicitly forbidden by
|
||||
// RFC 8200 section 4.5:
|
||||
// If any of the fragments being reassembled overlap with any other
|
||||
// fragments being reassembled for the same packet, reassembly of that
|
||||
// packet must be abandoned and all the fragments that have been received
|
||||
// for that packet must be discarded, and no ICMP error messages should be
|
||||
// sent.
|
||||
//
|
||||
// It is not explicitly forbidden for IPv4, but to keep parity with Linux we
|
||||
// disallow it as well:
|
||||
// https://github.com/torvalds/linux/blob/38525c6/net/ipv4/inet_fragment.c#L349
|
||||
if first < currentHole.first || currentHole.last < last {
|
||||
// Incoming fragment only partially fits in the free hole.
|
||||
return nil, 0, false, 0, ErrFragmentOverlap
|
||||
}
|
||||
if !more {
|
||||
if !currentHole.final || currentHole.filled && currentHole.last != last {
|
||||
// We have another final fragment, which does not perfectly overlap.
|
||||
return nil, 0, false, 0, ErrFragmentConflict
|
||||
}
|
||||
}
|
||||
|
||||
holeFound = true
|
||||
if currentHole.filled {
|
||||
// Incoming fragment is a duplicate.
|
||||
continue
|
||||
}
|
||||
|
||||
// We are populating the current hole with the payload and creating a new
|
||||
// hole for any unfilled ranges on either end.
|
||||
if first > currentHole.first {
|
||||
r.holes = append(r.holes, hole{
|
||||
first: currentHole.first,
|
||||
last: first - 1,
|
||||
filled: false,
|
||||
final: false,
|
||||
})
|
||||
}
|
||||
if last < currentHole.last && more {
|
||||
r.holes = append(r.holes, hole{
|
||||
first: last + 1,
|
||||
last: currentHole.last,
|
||||
filled: false,
|
||||
final: currentHole.final,
|
||||
})
|
||||
currentHole.final = false
|
||||
}
|
||||
memConsumed = pkt.MemSize()
|
||||
r.memSize += memConsumed
|
||||
// Update the current hole to precisely match the incoming fragment.
|
||||
r.holes[i] = hole{
|
||||
first: first,
|
||||
last: last,
|
||||
filled: true,
|
||||
final: currentHole.final,
|
||||
pkt: pkt.Clone(),
|
||||
}
|
||||
r.filled++
|
||||
// For IPv6, it is possible to have different Protocol values between
|
||||
// fragments of a packet (because, unlike IPv4, the Protocol is not used to
|
||||
// identify a fragment). In this case, only the Protocol of the first
|
||||
// fragment must be used as per RFC 8200 Section 4.5.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/3648): During reassembly of an IPv6 packet, IP
|
||||
// options received in the first fragment should be used - and they should
|
||||
// override options from following fragments.
|
||||
if first == 0 {
|
||||
if r.pkt != nil {
|
||||
r.pkt.DecRef()
|
||||
}
|
||||
r.pkt = pkt.Clone()
|
||||
r.proto = proto
|
||||
}
|
||||
break
|
||||
}
|
||||
if !holeFound {
|
||||
// Incoming fragment is beyond end.
|
||||
return nil, 0, false, 0, ErrFragmentConflict
|
||||
}
|
||||
|
||||
// Check if all the holes have been filled and we are ready to reassemble.
|
||||
if r.filled < len(r.holes) {
|
||||
return nil, 0, false, memConsumed, nil
|
||||
}
|
||||
|
||||
sort.Slice(r.holes, func(i, j int) bool {
|
||||
return r.holes[i].first < r.holes[j].first
|
||||
})
|
||||
|
||||
resPkt := r.holes[0].pkt.Clone()
|
||||
for i := 1; i < len(r.holes); i++ {
|
||||
stack.MergeFragment(resPkt, r.holes[i].pkt)
|
||||
}
|
||||
return resPkt, r.proto, true /* done */, memConsumed, nil
|
||||
}
|
||||
|
||||
func (r *reassembler) checkDoneOrMark() bool {
|
||||
r.mu.Lock()
|
||||
prev := r.done
|
||||
r.done = true
|
||||
r.mu.Unlock()
|
||||
return prev
|
||||
}
|
||||
239
pkg/tcpip/network/internal/fragmentation/reassembler_list.go
Normal file
239
pkg/tcpip/network/internal/fragmentation/reassembler_list.go
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package fragmentation
|
||||
|
||||
// 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 reassemblerElementMapper struct{}
|
||||
|
||||
// linkerFor maps an Element to a Linker.
|
||||
//
|
||||
// This default implementation should be inlined.
|
||||
//
|
||||
//go:nosplit
|
||||
func (reassemblerElementMapper) linkerFor(elem *reassembler) *reassembler { 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 reassemblerList struct {
|
||||
head *reassembler
|
||||
tail *reassembler
|
||||
}
|
||||
|
||||
// Reset resets list l to the empty state.
|
||||
func (l *reassemblerList) Reset() {
|
||||
l.head = nil
|
||||
l.tail = nil
|
||||
}
|
||||
|
||||
// Empty returns true iff the list is empty.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) Empty() bool {
|
||||
return l.head == nil
|
||||
}
|
||||
|
||||
// Front returns the first element of list l or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) Front() *reassembler {
|
||||
return l.head
|
||||
}
|
||||
|
||||
// Back returns the last element of list l or nil.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) Back() *reassembler {
|
||||
return l.tail
|
||||
}
|
||||
|
||||
// Len returns the number of elements in the list.
|
||||
//
|
||||
// NOTE: This is an O(n) operation.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) Len() (count int) {
|
||||
for e := l.Front(); e != nil; e = (reassemblerElementMapper{}.linkerFor(e)).Next() {
|
||||
count++
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
// PushFront inserts the element e at the front of list l.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) PushFront(e *reassembler) {
|
||||
linker := reassemblerElementMapper{}.linkerFor(e)
|
||||
linker.SetNext(l.head)
|
||||
linker.SetPrev(nil)
|
||||
if l.head != nil {
|
||||
reassemblerElementMapper{}.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 *reassemblerList) PushFrontList(m *reassemblerList) {
|
||||
if l.head == nil {
|
||||
l.head = m.head
|
||||
l.tail = m.tail
|
||||
} else if m.head != nil {
|
||||
reassemblerElementMapper{}.linkerFor(l.head).SetPrev(m.tail)
|
||||
reassemblerElementMapper{}.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 *reassemblerList) PushBack(e *reassembler) {
|
||||
linker := reassemblerElementMapper{}.linkerFor(e)
|
||||
linker.SetNext(nil)
|
||||
linker.SetPrev(l.tail)
|
||||
if l.tail != nil {
|
||||
reassemblerElementMapper{}.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 *reassemblerList) PushBackList(m *reassemblerList) {
|
||||
if l.head == nil {
|
||||
l.head = m.head
|
||||
l.tail = m.tail
|
||||
} else if m.head != nil {
|
||||
reassemblerElementMapper{}.linkerFor(l.tail).SetNext(m.head)
|
||||
reassemblerElementMapper{}.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 *reassemblerList) InsertAfter(b, e *reassembler) {
|
||||
bLinker := reassemblerElementMapper{}.linkerFor(b)
|
||||
eLinker := reassemblerElementMapper{}.linkerFor(e)
|
||||
|
||||
a := bLinker.Next()
|
||||
|
||||
eLinker.SetNext(a)
|
||||
eLinker.SetPrev(b)
|
||||
bLinker.SetNext(e)
|
||||
|
||||
if a != nil {
|
||||
reassemblerElementMapper{}.linkerFor(a).SetPrev(e)
|
||||
} else {
|
||||
l.tail = e
|
||||
}
|
||||
}
|
||||
|
||||
// InsertBefore inserts e before a.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) InsertBefore(a, e *reassembler) {
|
||||
aLinker := reassemblerElementMapper{}.linkerFor(a)
|
||||
eLinker := reassemblerElementMapper{}.linkerFor(e)
|
||||
|
||||
b := aLinker.Prev()
|
||||
eLinker.SetNext(a)
|
||||
eLinker.SetPrev(b)
|
||||
aLinker.SetPrev(e)
|
||||
|
||||
if b != nil {
|
||||
reassemblerElementMapper{}.linkerFor(b).SetNext(e)
|
||||
} else {
|
||||
l.head = e
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes e from l.
|
||||
//
|
||||
//go:nosplit
|
||||
func (l *reassemblerList) Remove(e *reassembler) {
|
||||
linker := reassemblerElementMapper{}.linkerFor(e)
|
||||
prev := linker.Prev()
|
||||
next := linker.Next()
|
||||
|
||||
if prev != nil {
|
||||
reassemblerElementMapper{}.linkerFor(prev).SetNext(next)
|
||||
} else if l.head == e {
|
||||
l.head = next
|
||||
}
|
||||
|
||||
if next != nil {
|
||||
reassemblerElementMapper{}.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 reassemblerEntry struct {
|
||||
next *reassembler
|
||||
prev *reassembler
|
||||
}
|
||||
|
||||
// Next returns the entry that follows e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *reassemblerEntry) Next() *reassembler {
|
||||
return e.next
|
||||
}
|
||||
|
||||
// Prev returns the entry that precedes e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *reassemblerEntry) Prev() *reassembler {
|
||||
return e.prev
|
||||
}
|
||||
|
||||
// SetNext assigns 'entry' as the entry that follows e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *reassemblerEntry) SetNext(elem *reassembler) {
|
||||
e.next = elem
|
||||
}
|
||||
|
||||
// SetPrev assigns 'entry' as the entry that precedes e in the list.
|
||||
//
|
||||
//go:nosplit
|
||||
func (e *reassemblerEntry) SetPrev(elem *reassembler) {
|
||||
e.prev = elem
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue