github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/tcpip/network/internal/fragmentation/reassembler.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 16 17 import ( 18 "math" 19 "sort" 20 21 "github.com/metacubex/gvisor/pkg/sync" 22 "github.com/metacubex/gvisor/pkg/tcpip" 23 "github.com/metacubex/gvisor/pkg/tcpip/stack" 24 ) 25 26 type hole struct { 27 first uint16 28 last uint16 29 filled bool 30 final bool 31 // pkt is the fragment packet if hole is filled. We keep the whole pkt rather 32 // than the fragmented payload to prevent binding to specific buffer types. 33 pkt *stack.PacketBuffer 34 } 35 36 type reassembler struct { 37 reassemblerEntry 38 id FragmentID 39 memSize int 40 proto uint8 41 mu sync.Mutex 42 holes []hole 43 filled int 44 done bool 45 createdAt tcpip.MonotonicTime 46 pkt *stack.PacketBuffer 47 } 48 49 func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler { 50 r := &reassembler{ 51 id: id, 52 createdAt: clock.NowMonotonic(), 53 } 54 r.holes = append(r.holes, hole{ 55 first: 0, 56 last: math.MaxUint16, 57 filled: false, 58 final: true, 59 }) 60 return r 61 } 62 63 func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (*stack.PacketBuffer, uint8, bool, int, error) { 64 r.mu.Lock() 65 defer r.mu.Unlock() 66 if r.done { 67 // A concurrent goroutine might have already reassembled 68 // the packet and emptied the heap while this goroutine 69 // was waiting on the mutex. We don't have to do anything in this case. 70 return nil, 0, false, 0, nil 71 } 72 73 var holeFound bool 74 var memConsumed int 75 for i := range r.holes { 76 currentHole := &r.holes[i] 77 78 if last < currentHole.first || currentHole.last < first { 79 continue 80 } 81 // For IPv6, overlaps with an existing fragment are explicitly forbidden by 82 // RFC 8200 section 4.5: 83 // If any of the fragments being reassembled overlap with any other 84 // fragments being reassembled for the same packet, reassembly of that 85 // packet must be abandoned and all the fragments that have been received 86 // for that packet must be discarded, and no ICMP error messages should be 87 // sent. 88 // 89 // It is not explicitly forbidden for IPv4, but to keep parity with Linux we 90 // disallow it as well: 91 // https://github.com/torvalds/linux/blob/38525c6/net/ipv4/inet_fragment.c#L349 92 if first < currentHole.first || currentHole.last < last { 93 // Incoming fragment only partially fits in the free hole. 94 return nil, 0, false, 0, ErrFragmentOverlap 95 } 96 if !more { 97 if !currentHole.final || currentHole.filled && currentHole.last != last { 98 // We have another final fragment, which does not perfectly overlap. 99 return nil, 0, false, 0, ErrFragmentConflict 100 } 101 } 102 103 holeFound = true 104 if currentHole.filled { 105 // Incoming fragment is a duplicate. 106 continue 107 } 108 109 // We are populating the current hole with the payload and creating a new 110 // hole for any unfilled ranges on either end. 111 if first > currentHole.first { 112 r.holes = append(r.holes, hole{ 113 first: currentHole.first, 114 last: first - 1, 115 filled: false, 116 final: false, 117 }) 118 } 119 if last < currentHole.last && more { 120 r.holes = append(r.holes, hole{ 121 first: last + 1, 122 last: currentHole.last, 123 filled: false, 124 final: currentHole.final, 125 }) 126 currentHole.final = false 127 } 128 memConsumed = pkt.MemSize() 129 r.memSize += memConsumed 130 // Update the current hole to precisely match the incoming fragment. 131 r.holes[i] = hole{ 132 first: first, 133 last: last, 134 filled: true, 135 final: currentHole.final, 136 pkt: pkt.IncRef(), 137 } 138 r.filled++ 139 // For IPv6, it is possible to have different Protocol values between 140 // fragments of a packet (because, unlike IPv4, the Protocol is not used to 141 // identify a fragment). In this case, only the Protocol of the first 142 // fragment must be used as per RFC 8200 Section 4.5. 143 // 144 // TODO(gvisor.dev/issue/3648): During reassembly of an IPv6 packet, IP 145 // options received in the first fragment should be used - and they should 146 // override options from following fragments. 147 if first == 0 { 148 if !r.pkt.IsNil() { 149 r.pkt.DecRef() 150 } 151 r.pkt = pkt.IncRef() 152 r.proto = proto 153 } 154 break 155 } 156 if !holeFound { 157 // Incoming fragment is beyond end. 158 return nil, 0, false, 0, ErrFragmentConflict 159 } 160 161 // Check if all the holes have been filled and we are ready to reassemble. 162 if r.filled < len(r.holes) { 163 return nil, 0, false, memConsumed, nil 164 } 165 166 sort.Slice(r.holes, func(i, j int) bool { 167 return r.holes[i].first < r.holes[j].first 168 }) 169 170 resPkt := r.holes[0].pkt.Clone() 171 for i := 1; i < len(r.holes); i++ { 172 stack.MergeFragment(resPkt, r.holes[i].pkt) 173 } 174 return resPkt, r.proto, true /* done */, memConsumed, nil 175 } 176 177 func (r *reassembler) checkDoneOrMark() bool { 178 r.mu.Lock() 179 prev := r.done 180 r.done = true 181 r.mu.Unlock() 182 return prev 183 }