// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package fragmentation import ( "math" "sort" "gvisor.dev/gvisor/pkg/sync" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/stack" ) type hole struct { first uint16 last uint16 filled bool final bool // pkt is the fragment packet if hole is filled. We keep the whole pkt rather // than the fragmented payload to prevent binding to specific buffer types. pkt *stack.PacketBuffer } type reassembler struct { reassemblerEntry id FragmentID memSize int proto uint8 mu sync.Mutex holes []hole filled int done bool creationTime int64 pkt *stack.PacketBuffer } func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler { r := &reassembler{ id: id, creationTime: clock.NowMonotonic(), } r.holes = append(r.holes, hole{ first: 0, last: math.MaxUint16, filled: false, final: true, }) return r } func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (*stack.PacketBuffer, uint8, bool, int, error) { r.mu.Lock() defer r.mu.Unlock() if r.done { // A concurrent goroutine might have already reassembled // the packet and emptied the heap while this goroutine // was waiting on the mutex. We don't have to do anything in this case. return nil, 0, false, 0, nil } var holeFound bool var memConsumed int for i := range r.holes { currentHole := &r.holes[i] if last < currentHole.first || currentHole.last < first { continue } // For IPv6, overlaps with an existing fragment are explicitly forbidden by // RFC 8200 section 4.5: // If any of the fragments being reassembled overlap with any other // fragments being reassembled for the same packet, reassembly of that // packet must be abandoned and all the fragments that have been received // for that packet must be discarded, and no ICMP error messages should be // sent. // // It is not explicitly forbidden for IPv4, but to keep parity with Linux we // disallow it as well: // https://github.com/torvalds/linux/blob/38525c6/net/ipv4/inet_fragment.c#L349 if first < currentHole.first || currentHole.last < last { // Incoming fragment only partially fits in the free hole. return nil, 0, false, 0, ErrFragmentOverlap } if !more { if !currentHole.final || currentHole.filled && currentHole.last != last { // We have another final fragment, which does not perfectly overlap. return nil, 0, false, 0, ErrFragmentConflict } } holeFound = true if currentHole.filled { // Incoming fragment is a duplicate. continue } // We are populating the current hole with the payload and creating a new // hole for any unfilled ranges on either end. if first > currentHole.first { r.holes = append(r.holes, hole{ first: currentHole.first, last: first - 1, filled: false, final: false, }) } if last < currentHole.last && more { r.holes = append(r.holes, hole{ first: last + 1, last: currentHole.last, filled: false, final: currentHole.final, }) currentHole.final = false } memConsumed = pkt.MemSize() r.memSize += memConsumed // Update the current hole to precisely match the incoming fragment. r.holes[i] = hole{ first: first, last: last, filled: true, final: currentHole.final, pkt: pkt, } r.filled++ // For IPv6, it is possible to have different Protocol values between // fragments of a packet (because, unlike IPv4, the Protocol is not used to // identify a fragment). In this case, only the Protocol of the first // fragment must be used as per RFC 8200 Section 4.5. // // TODO(gvisor.dev/issue/3648): During reassembly of an IPv6 packet, IP // options received in the first fragment should be used - and they should // override options from following fragments. if first == 0 { r.pkt = pkt r.proto = proto } break } if !holeFound { // Incoming fragment is beyond end. return nil, 0, false, 0, ErrFragmentConflict } // Check if all the holes have been filled and we are ready to reassemble. if r.filled < len(r.holes) { return nil, 0, false, memConsumed, nil } sort.Slice(r.holes, func(i, j int) bool { return r.holes[i].first < r.holes[j].first }) resPkt := r.holes[0].pkt for i := 1; i < len(r.holes); i++ { fragPkt := r.holes[i].pkt fragPkt.Data.ReadToVV(&resPkt.Data, fragPkt.Data.Size()) } return resPkt, r.proto, true, memConsumed, nil } func (r *reassembler) checkDoneOrMark() bool { r.mu.Lock() prev := r.done r.done = true r.mu.Unlock() return prev }