github.com/flowerwrong/netstack@v0.0.0-20191009141956-e5848263af28/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  	"log"
    21  	"sync"
    22  	"time"
    23  
    24  	"github.com/FlowerWrong/netstack/tcpip/buffer"
    25  )
    26  
    27  // DefaultReassembleTimeout is based on the linux stack: net.ipv4.ipfrag_time.
    28  const DefaultReassembleTimeout = 30 * time.Second
    29  
    30  // HighFragThreshold is the threshold at which we start trimming old
    31  // fragmented packets. Linux uses a default value of 4 MB. See
    32  // net.ipv4.ipfrag_high_thresh for more information.
    33  const HighFragThreshold = 4 << 20 // 4MB
    34  
    35  // LowFragThreshold is the threshold we reach to when we start dropping
    36  // older fragmented packets. It's important that we keep enough room for newer
    37  // packets to be re-assembled. Hence, this needs to be lower than
    38  // HighFragThreshold enough. Linux uses a default value of 3 MB. See
    39  // net.ipv4.ipfrag_low_thresh for more information.
    40  const LowFragThreshold = 3 << 20 // 3MB
    41  
    42  // Fragmentation is the main structure that other modules
    43  // of the stack should use to implement IP Fragmentation.
    44  type Fragmentation struct {
    45  	mu           sync.Mutex
    46  	highLimit    int
    47  	lowLimit     int
    48  	reassemblers map[uint32]*reassembler
    49  	rList        reassemblerList
    50  	size         int
    51  	timeout      time.Duration
    52  }
    53  
    54  // NewFragmentation creates a new Fragmentation.
    55  //
    56  // highMemoryLimit specifies the limit on the memory consumed
    57  // by the fragments stored by Fragmentation (overhead of internal data-structures
    58  // is not accounted). Fragments are dropped when the limit is reached.
    59  //
    60  // lowMemoryLimit specifies the limit on which we will reach by dropping
    61  // fragments after reaching highMemoryLimit.
    62  //
    63  // reassemblingTimeout specifies the maximum time allowed to reassemble a packet.
    64  // Fragments are lazily evicted only when a new a packet with an
    65  // already existing fragmentation-id arrives after the timeout.
    66  func NewFragmentation(highMemoryLimit, lowMemoryLimit int, reassemblingTimeout time.Duration) *Fragmentation {
    67  	if lowMemoryLimit >= highMemoryLimit {
    68  		lowMemoryLimit = highMemoryLimit
    69  	}
    70  
    71  	if lowMemoryLimit < 0 {
    72  		lowMemoryLimit = 0
    73  	}
    74  
    75  	return &Fragmentation{
    76  		reassemblers: make(map[uint32]*reassembler),
    77  		highLimit:    highMemoryLimit,
    78  		lowLimit:     lowMemoryLimit,
    79  		timeout:      reassemblingTimeout,
    80  	}
    81  }
    82  
    83  // Process processes an incoming fragment belonging to an ID
    84  // and returns a complete packet when all the packets belonging to that ID have been received.
    85  func (f *Fragmentation) Process(id uint32, first, last uint16, more bool, vv buffer.VectorisedView) (buffer.VectorisedView, bool) {
    86  	f.mu.Lock()
    87  	r, ok := f.reassemblers[id]
    88  	if ok && r.tooOld(f.timeout) {
    89  		// This is very likely to be an id-collision or someone performing a slow-rate attack.
    90  		f.release(r)
    91  		ok = false
    92  	}
    93  	if !ok {
    94  		r = newReassembler(id)
    95  		f.reassemblers[id] = r
    96  		f.rList.PushFront(r)
    97  	}
    98  	f.mu.Unlock()
    99  
   100  	res, done, consumed := r.process(first, last, more, vv)
   101  
   102  	f.mu.Lock()
   103  	f.size += consumed
   104  	if done {
   105  		f.release(r)
   106  	}
   107  	// Evict reassemblers if we are consuming more memory than highLimit until
   108  	// we reach lowLimit.
   109  	if f.size > f.highLimit {
   110  		tail := f.rList.Back()
   111  		for f.size > f.lowLimit && tail != nil {
   112  			f.release(tail)
   113  			tail = tail.Prev()
   114  		}
   115  	}
   116  	f.mu.Unlock()
   117  	return res, done
   118  }
   119  
   120  func (f *Fragmentation) release(r *reassembler) {
   121  	// Before releasing a fragment we need to check if r is already marked as done.
   122  	// Otherwise, we would delete it twice.
   123  	if r.checkDoneOrMark() {
   124  		return
   125  	}
   126  
   127  	delete(f.reassemblers, r.id)
   128  	f.rList.Remove(r)
   129  	f.size -= r.size
   130  	if f.size < 0 {
   131  		log.Printf("memory counter < 0 (%d), this is an accounting bug that requires investigation", f.size)
   132  		f.size = 0
   133  	}
   134  }