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 }