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  }