github.com/google/netstack@v0.0.0-20191123085552-55fcc16cd0eb/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/google/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 }