github.com/vpnishe/netstack@v1.10.6/tcpip/network/fragmentation/fragmentation.go (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package fragmentation contains the implementation of IP fragmentation.
    16  // It is based on RFC 791 and RFC 815.
    17  package fragmentation
    18  
    19  import (
    20  	"fmt"
    21  	"log"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/vpnishe/netstack/tcpip/buffer"
    26  )
    27  
    28  // DefaultReassembleTimeout is based on the linux stack: net.ipv4.ipfrag_time.
    29  const DefaultReassembleTimeout = 30 * time.Second
    30  
    31  // HighFragThreshold is the threshold at which we start trimming old
    32  // fragmented packets. Linux uses a default value of 4 MB. See
    33  // net.ipv4.ipfrag_high_thresh for more information.
    34  const HighFragThreshold = 4 << 20 // 4MB
    35  
    36  // LowFragThreshold is the threshold we reach to when we start dropping
    37  // older fragmented packets. It's important that we keep enough room for newer
    38  // packets to be re-assembled. Hence, this needs to be lower than
    39  // HighFragThreshold enough. Linux uses a default value of 3 MB. See
    40  // net.ipv4.ipfrag_low_thresh for more information.
    41  const LowFragThreshold = 3 << 20 // 3MB
    42  
    43  // Fragmentation is the main structure that other modules
    44  // of the stack should use to implement IP Fragmentation.
    45  type Fragmentation struct {
    46  	mu           sync.Mutex
    47  	highLimit    int
    48  	lowLimit     int
    49  	reassemblers map[uint32]*reassembler
    50  	rList        reassemblerList
    51  	size         int
    52  	timeout      time.Duration
    53  }
    54  
    55  // NewFragmentation creates a new Fragmentation.
    56  //
    57  // highMemoryLimit specifies the limit on the memory consumed
    58  // by the fragments stored by Fragmentation (overhead of internal data-structures
    59  // is not accounted). Fragments are dropped when the limit is reached.
    60  //
    61  // lowMemoryLimit specifies the limit on which we will reach by dropping
    62  // fragments after reaching highMemoryLimit.
    63  //
    64  // reassemblingTimeout specifies the maximum time allowed to reassemble a packet.
    65  // Fragments are lazily evicted only when a new a packet with an
    66  // already existing fragmentation-id arrives after the timeout.
    67  func NewFragmentation(highMemoryLimit, lowMemoryLimit int, reassemblingTimeout time.Duration) *Fragmentation {
    68  	if lowMemoryLimit >= highMemoryLimit {
    69  		lowMemoryLimit = highMemoryLimit
    70  	}
    71  
    72  	if lowMemoryLimit < 0 {
    73  		lowMemoryLimit = 0
    74  	}
    75  
    76  	return &Fragmentation{
    77  		reassemblers: make(map[uint32]*reassembler),
    78  		highLimit:    highMemoryLimit,
    79  		lowLimit:     lowMemoryLimit,
    80  		timeout:      reassemblingTimeout,
    81  	}
    82  }
    83  
    84  // Process processes an incoming fragment belonging to an ID
    85  // and returns a complete packet when all the packets belonging to that ID have been received.
    86  func (f *Fragmentation) Process(id uint32, first, last uint16, more bool, vv buffer.VectorisedView) (buffer.VectorisedView, bool, error) {
    87  	f.mu.Lock()
    88  	r, ok := f.reassemblers[id]
    89  	if ok && r.tooOld(f.timeout) {
    90  		// This is very likely to be an id-collision or someone performing a slow-rate attack.
    91  		f.release(r)
    92  		ok = false
    93  	}
    94  	if !ok {
    95  		r = newReassembler(id)
    96  		f.reassemblers[id] = r
    97  		f.rList.PushFront(r)
    98  	}
    99  	f.mu.Unlock()
   100  
   101  	res, done, consumed, err := r.process(first, last, more, vv)
   102  	if err != nil {
   103  		// We probably got an invalid sequence of fragments. Just
   104  		// discard the reassembler and move on.
   105  		f.mu.Lock()
   106  		f.release(r)
   107  		f.mu.Unlock()
   108  		return buffer.VectorisedView{}, false, fmt.Errorf("fragmentation processing error: %v", err)
   109  	}
   110  	f.mu.Lock()
   111  	f.size += consumed
   112  	if done {
   113  		f.release(r)
   114  	}
   115  	// Evict reassemblers if we are consuming more memory than highLimit until
   116  	// we reach lowLimit.
   117  	if f.size > f.highLimit {
   118  		tail := f.rList.Back()
   119  		for f.size > f.lowLimit && tail != nil {
   120  			f.release(tail)
   121  			tail = tail.Prev()
   122  		}
   123  	}
   124  	f.mu.Unlock()
   125  	return res, done, nil
   126  }
   127  
   128  func (f *Fragmentation) release(r *reassembler) {
   129  	// Before releasing a fragment we need to check if r is already marked as done.
   130  	// Otherwise, we would delete it twice.
   131  	if r.checkDoneOrMark() {
   132  		return
   133  	}
   134  
   135  	delete(f.reassemblers, r.id)
   136  	f.rList.Remove(r)
   137  	f.size -= r.size
   138  	if f.size < 0 {
   139  		log.Printf("memory counter < 0 (%d), this is an accounting bug that requires investigation", f.size)
   140  		f.size = 0
   141  	}
   142  }