github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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/SagerNet/gvisor/pkg/sync" 22 "github.com/SagerNet/gvisor/pkg/tcpip" 23 "github.com/SagerNet/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, 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(github.com/SagerNet/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 r.pkt = pkt 149 r.proto = proto 150 } 151 152 break 153 } 154 if !holeFound { 155 // Incoming fragment is beyond end. 156 return nil, 0, false, 0, ErrFragmentConflict 157 } 158 159 // Check if all the holes have been filled and we are ready to reassemble. 160 if r.filled < len(r.holes) { 161 return nil, 0, false, memConsumed, nil 162 } 163 164 sort.Slice(r.holes, func(i, j int) bool { 165 return r.holes[i].first < r.holes[j].first 166 }) 167 168 resPkt := r.holes[0].pkt 169 for i := 1; i < len(r.holes); i++ { 170 stack.MergeFragment(resPkt, r.holes[i].pkt) 171 } 172 return resPkt, r.proto, true, memConsumed, nil 173 } 174 175 func (r *reassembler) checkDoneOrMark() bool { 176 r.mu.Lock() 177 prev := r.done 178 r.done = true 179 r.mu.Unlock() 180 return prev 181 }