gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/pkg/tcpip/network/internal/fragmentation/fragmentation_test.go (about)

     1  // Copyright 2022 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  	"errors"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"gvisor.dev/gvisor/pkg/buffer"
    24  	"gvisor.dev/gvisor/pkg/tcpip/faketime"
    25  	"gvisor.dev/gvisor/pkg/tcpip/network/internal/testutil"
    26  	"gvisor.dev/gvisor/pkg/tcpip/stack"
    27  )
    28  
    29  // reassembleTimeout is dummy timeout used for testing, where the clock never
    30  // advances.
    31  const reassembleTimeout = 1
    32  
    33  // buf is a helper to build a Buffer from different strings.
    34  func buf(size int, pieces ...string) buffer.Buffer {
    35  	buf := buffer.Buffer{}
    36  	c := buf.Clone()
    37  	defer c.Release()
    38  	for _, p := range pieces {
    39  		v := buffer.NewViewWithData([]byte(p))
    40  		buf.Append(v)
    41  	}
    42  
    43  	return buf
    44  }
    45  
    46  func pkt(size int, pieces ...string) *stack.PacketBuffer {
    47  	return stack.NewPacketBuffer(stack.PacketBufferOptions{
    48  		Payload: buf(size, pieces...),
    49  	})
    50  }
    51  
    52  type processInput struct {
    53  	id    FragmentID
    54  	first uint16
    55  	last  uint16
    56  	more  bool
    57  	proto uint8
    58  	pkt   *stack.PacketBuffer
    59  }
    60  
    61  type processOutput struct {
    62  	buf   buffer.Buffer
    63  	proto uint8
    64  	done  bool
    65  }
    66  
    67  func TestFragmentationProcess(t *testing.T) {
    68  	var processTestCases = []struct {
    69  		comment string
    70  		in      []processInput
    71  		out     []processOutput
    72  	}{
    73  		{
    74  			comment: "One ID",
    75  			in: []processInput{
    76  				{id: FragmentID{ID: 0}, first: 0, last: 1, more: true, pkt: pkt(2, "01")},
    77  				{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, pkt: pkt(2, "23")},
    78  			},
    79  			out: []processOutput{
    80  				{buf: buffer.Buffer{}, done: false},
    81  				{buf: buf(4, "01", "23"), done: true},
    82  			},
    83  		},
    84  		{
    85  			comment: "Next Header protocol mismatch",
    86  			in: []processInput{
    87  				{id: FragmentID{ID: 0}, first: 0, last: 1, more: true, proto: 6, pkt: pkt(2, "01")},
    88  				{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, proto: 17, pkt: pkt(2, "23")},
    89  			},
    90  			out: []processOutput{
    91  				{buf: buffer.Buffer{}, done: false},
    92  				{buf: buf(4, "01", "23"), proto: 6, done: true},
    93  			},
    94  		},
    95  		{
    96  			comment: "Two IDs",
    97  			in: []processInput{
    98  				{id: FragmentID{ID: 0}, first: 0, last: 1, more: true, pkt: pkt(2, "01")},
    99  				{id: FragmentID{ID: 1}, first: 0, last: 1, more: true, pkt: pkt(2, "ab")},
   100  				{id: FragmentID{ID: 1}, first: 2, last: 3, more: false, pkt: pkt(2, "cd")},
   101  				{id: FragmentID{ID: 0}, first: 2, last: 3, more: false, pkt: pkt(2, "23")},
   102  			},
   103  			out: []processOutput{
   104  				{buf: buffer.Buffer{}, done: false},
   105  				{buf: buffer.Buffer{}, done: false},
   106  				{buf: buf(4, "ab", "cd"), done: true},
   107  				{buf: buf(4, "01", "23"), done: true},
   108  			},
   109  		},
   110  	}
   111  	for _, c := range processTestCases {
   112  		t.Run(c.comment, func(t *testing.T) {
   113  			f := NewFragmentation(minBlockSize, 2048, 512, reassembleTimeout, &faketime.NullClock{}, nil)
   114  			firstFragmentProto := c.in[0].proto
   115  			for i, in := range c.in {
   116  				in := in
   117  				defer in.pkt.DecRef()
   118  				defer c.out[i].buf.Release()
   119  				resPkt, proto, done, err := f.Process(in.id, in.first, in.last, in.more, in.proto, in.pkt)
   120  				if resPkt != nil {
   121  					defer resPkt.DecRef()
   122  				}
   123  				if err != nil {
   124  					t.Fatalf("f.Process(%+v, %d, %d, %t, %d, %#v) failed: %s",
   125  						in.id, in.first, in.last, in.more, in.proto, in.pkt, err)
   126  				}
   127  				if done != c.out[i].done {
   128  					t.Errorf("got Process(%+v, %d, %d, %t, %d, _) = (_, _, %t, _), want = (_, _, %t, _)",
   129  						in.id, in.first, in.last, in.more, in.proto, done, c.out[i].done)
   130  				}
   131  				if c.out[i].done {
   132  					if diff := cmp.Diff(c.out[i].buf.Flatten(), resPkt.Data().AsRange().ToSlice()); diff != "" {
   133  						t.Errorf("got Process(%+v, %d, %d, %t, %d, %#v) result mismatch (-want, +got):\n%s",
   134  							in.id, in.first, in.last, in.more, in.proto, in.pkt, diff)
   135  					}
   136  					if firstFragmentProto != proto {
   137  						t.Errorf("got Process(%+v, %d, %d, %t, %d, _) = (_, %d, _, _), want = (_, %d, _, _)",
   138  							in.id, in.first, in.last, in.more, in.proto, proto, firstFragmentProto)
   139  					}
   140  					if _, ok := f.reassemblers[in.id]; ok {
   141  						t.Errorf("Process(%d) did not remove buffer from reassemblers", i)
   142  					}
   143  					for n := f.rList.Front(); n != nil; n = n.Next() {
   144  						if n.id == in.id {
   145  							t.Errorf("Process(%d) did not remove buffer from rList", i)
   146  						}
   147  					}
   148  				}
   149  			}
   150  		})
   151  	}
   152  }
   153  
   154  func TestReassemblingTimeout(t *testing.T) {
   155  	const (
   156  		reassemblyTimeout = time.Millisecond
   157  		protocol          = 0xff
   158  	)
   159  
   160  	type fragment struct {
   161  		first uint16
   162  		last  uint16
   163  		more  bool
   164  		data  string
   165  	}
   166  
   167  	type event struct {
   168  		// name is a nickname of this event.
   169  		name string
   170  
   171  		// clockAdvance is a duration to advance the clock. The clock advances
   172  		// before a fragment specified in the fragment field is processed.
   173  		clockAdvance time.Duration
   174  
   175  		// fragment is a fragment to process. This can be nil if there is no
   176  		// fragment to process.
   177  		fragment *fragment
   178  
   179  		// expectDone is true if the fragmentation instance should report the
   180  		// reassembly is done after the fragment is processd.
   181  		expectDone bool
   182  
   183  		// memSizeAfterEvent is the expected memory size of the fragmentation
   184  		// instance after the event.
   185  		memSizeAfterEvent int
   186  	}
   187  
   188  	memSizeOfFrags := func(frags ...*fragment) int {
   189  		var size int
   190  		for _, frag := range frags {
   191  			p := pkt(len(frag.data), frag.data)
   192  			size += p.MemSize()
   193  			p.DecRef()
   194  		}
   195  		return size
   196  	}
   197  
   198  	half1 := &fragment{first: 0, last: 0, more: true, data: "0"}
   199  	half2 := &fragment{first: 1, last: 1, more: false, data: "1"}
   200  
   201  	tests := []struct {
   202  		name   string
   203  		events []event
   204  	}{
   205  		{
   206  			name: "half1 and half2 are reassembled successfully",
   207  			events: []event{
   208  				{
   209  					name:              "half1",
   210  					fragment:          half1,
   211  					expectDone:        false,
   212  					memSizeAfterEvent: memSizeOfFrags(half1),
   213  				},
   214  				{
   215  					name:              "half2",
   216  					fragment:          half2,
   217  					expectDone:        true,
   218  					memSizeAfterEvent: 0,
   219  				},
   220  			},
   221  		},
   222  		{
   223  			name: "half1 timeout, half2 timeout",
   224  			events: []event{
   225  				{
   226  					name:              "half1",
   227  					fragment:          half1,
   228  					expectDone:        false,
   229  					memSizeAfterEvent: memSizeOfFrags(half1),
   230  				},
   231  				{
   232  					name:              "half1 just before reassembly timeout",
   233  					clockAdvance:      reassemblyTimeout - 1,
   234  					memSizeAfterEvent: memSizeOfFrags(half1),
   235  				},
   236  				{
   237  					name:              "half1 reassembly timeout",
   238  					clockAdvance:      1,
   239  					memSizeAfterEvent: 0,
   240  				},
   241  				{
   242  					name:              "half2",
   243  					fragment:          half2,
   244  					expectDone:        false,
   245  					memSizeAfterEvent: memSizeOfFrags(half2),
   246  				},
   247  				{
   248  					name:              "half2 just before reassembly timeout",
   249  					clockAdvance:      reassemblyTimeout - 1,
   250  					memSizeAfterEvent: memSizeOfFrags(half2),
   251  				},
   252  				{
   253  					name:              "half2 reassembly timeout",
   254  					clockAdvance:      1,
   255  					memSizeAfterEvent: 0,
   256  				},
   257  			},
   258  		},
   259  	}
   260  	for _, test := range tests {
   261  		t.Run(test.name, func(t *testing.T) {
   262  			clock := faketime.NewManualClock()
   263  			f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassemblyTimeout, clock, nil)
   264  			for _, event := range test.events {
   265  				clock.Advance(event.clockAdvance)
   266  				if frag := event.fragment; frag != nil {
   267  					p := pkt(len(frag.data), frag.data)
   268  					defer p.DecRef()
   269  					pkt, _, done, err := f.Process(FragmentID{}, frag.first, frag.last, frag.more, protocol, p)
   270  					if pkt != nil {
   271  						pkt.DecRef()
   272  					}
   273  					if err != nil {
   274  						t.Fatalf("%s: f.Process failed: %s", event.name, err)
   275  					}
   276  					if done != event.expectDone {
   277  						t.Fatalf("%s: got done = %t, want = %t", event.name, done, event.expectDone)
   278  					}
   279  				}
   280  				if got, want := f.memSize, event.memSizeAfterEvent; got != want {
   281  					t.Errorf("%s: got f.memSize = %d, want = %d", event.name, got, want)
   282  				}
   283  			}
   284  		})
   285  	}
   286  }
   287  
   288  func TestMemoryLimits(t *testing.T) {
   289  	p := pkt(1, "0")
   290  	defer p.DecRef()
   291  	lowLimit := p.MemSize()
   292  	highLimit := 3 * lowLimit // Allow at most 3 such packets.
   293  	// Using a manual clock here and below because the fragmentation object
   294  	// cleans up its reassemblers with a job that's scheduled with the clock
   295  	// argument. If the clock does not schedule jobs, the reassemblers are not
   296  	// released and the fragmentation object leaks packets.
   297  	c := faketime.NewManualClock()
   298  	defer c.Advance(reassembleTimeout)
   299  	f := NewFragmentation(minBlockSize, highLimit, lowLimit, reassembleTimeout, c, nil)
   300  	// Send first fragment with id = 0.
   301  	p0 := pkt(1, "0")
   302  	defer p0.DecRef()
   303  	if _, _, _, err := f.Process(FragmentID{ID: 0}, 0, 0, true, 0xFF, p0); err != nil {
   304  		t.Fatal(err)
   305  	}
   306  	// Send first fragment with id = 1.
   307  	p1 := pkt(1, "1")
   308  	defer p1.DecRef()
   309  	if _, _, _, err := f.Process(FragmentID{ID: 1}, 0, 0, true, 0xFF, p1); err != nil {
   310  		t.Fatal(err)
   311  	}
   312  	// Send first fragment with id = 2.
   313  	p2 := pkt(1, "2")
   314  	defer p2.DecRef()
   315  	if _, _, _, err := f.Process(FragmentID{ID: 2}, 0, 0, true, 0xFF, p2); err != nil {
   316  		t.Fatal(err)
   317  	}
   318  
   319  	// Send first fragment with id = 3. This should caused id = 0 and id = 1 to be
   320  	// evicted.
   321  	p3 := pkt(1, "3")
   322  	defer p3.DecRef()
   323  	if _, _, _, err := f.Process(FragmentID{ID: 3}, 0, 0, true, 0xFF, p3); err != nil {
   324  		t.Fatal(err)
   325  	}
   326  
   327  	if _, ok := f.reassemblers[FragmentID{ID: 0}]; ok {
   328  		t.Errorf("Memory limits are not respected: id=0 has not been evicted.")
   329  	}
   330  	if _, ok := f.reassemblers[FragmentID{ID: 1}]; ok {
   331  		t.Errorf("Memory limits are not respected: id=1 has not been evicted.")
   332  	}
   333  	if _, ok := f.reassemblers[FragmentID{ID: 3}]; !ok {
   334  		t.Errorf("Implementation of memory limits is wrong: id=3 is not present.")
   335  	}
   336  }
   337  
   338  func TestMemoryLimitsIgnoresDuplicates(t *testing.T) {
   339  	p0 := pkt(1, "0")
   340  	defer p0.DecRef()
   341  	memSize := p0.MemSize()
   342  	c := faketime.NewManualClock()
   343  	defer c.Advance(reassembleTimeout)
   344  	f := NewFragmentation(minBlockSize, memSize, 0, reassembleTimeout, c, nil)
   345  	// Send first fragment with id = 0.
   346  	p1 := pkt(1, "0")
   347  	defer p1.DecRef()
   348  	if _, _, _, err := f.Process(FragmentID{}, 0, 0, true, 0xFF, p1); err != nil {
   349  		t.Fatal(err)
   350  	}
   351  	// Send the same packet again.
   352  	p1dup := pkt(1, "0")
   353  	defer p1dup.DecRef()
   354  	if _, _, _, err := f.Process(FragmentID{}, 0, 0, true, 0xFF, p1dup); err != nil {
   355  		t.Fatal(err)
   356  	}
   357  
   358  	if got, want := f.memSize, memSize; got != want {
   359  		t.Errorf("Wrong size, duplicates are not handled correctly: got=%d, want=%d.", got, want)
   360  	}
   361  }
   362  
   363  func TestErrors(t *testing.T) {
   364  	tests := []struct {
   365  		name      string
   366  		blockSize uint16
   367  		first     uint16
   368  		last      uint16
   369  		more      bool
   370  		data      string
   371  		err       error
   372  	}{
   373  		{
   374  			name:      "exact block size without more",
   375  			blockSize: 2,
   376  			first:     2,
   377  			last:      3,
   378  			more:      false,
   379  			data:      "01",
   380  		},
   381  		{
   382  			name:      "exact block size with more",
   383  			blockSize: 2,
   384  			first:     2,
   385  			last:      3,
   386  			more:      true,
   387  			data:      "01",
   388  		},
   389  		{
   390  			name:      "exact block size with more and extra data",
   391  			blockSize: 2,
   392  			first:     2,
   393  			last:      3,
   394  			more:      true,
   395  			data:      "012",
   396  			err:       ErrInvalidArgs,
   397  		},
   398  		{
   399  			name:      "exact block size with more and too little data",
   400  			blockSize: 2,
   401  			first:     2,
   402  			last:      3,
   403  			more:      true,
   404  			data:      "0",
   405  			err:       ErrInvalidArgs,
   406  		},
   407  		{
   408  			name:      "not exact block size with more",
   409  			blockSize: 2,
   410  			first:     2,
   411  			last:      2,
   412  			more:      true,
   413  			data:      "0",
   414  			err:       ErrInvalidArgs,
   415  		},
   416  		{
   417  			name:      "not exact block size without more",
   418  			blockSize: 2,
   419  			first:     2,
   420  			last:      2,
   421  			more:      false,
   422  			data:      "0",
   423  		},
   424  		{
   425  			name:      "first not a multiple of block size",
   426  			blockSize: 2,
   427  			first:     3,
   428  			last:      4,
   429  			more:      true,
   430  			data:      "01",
   431  			err:       ErrInvalidArgs,
   432  		},
   433  		{
   434  			name:      "first more than last",
   435  			blockSize: 2,
   436  			first:     4,
   437  			last:      3,
   438  			more:      true,
   439  			data:      "01",
   440  			err:       ErrInvalidArgs,
   441  		},
   442  	}
   443  
   444  	for _, test := range tests {
   445  		t.Run(test.name, func(t *testing.T) {
   446  			p0 := pkt(len(test.data), test.data)
   447  			defer p0.DecRef()
   448  			c := faketime.NewManualClock()
   449  			defer c.Advance(reassembleTimeout)
   450  			f := NewFragmentation(test.blockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, c, nil)
   451  			resPkt, _, done, err := f.Process(FragmentID{}, test.first, test.last, test.more, 0, p0)
   452  
   453  			if resPkt != nil {
   454  				resPkt.DecRef()
   455  			}
   456  			if !errors.Is(err, test.err) {
   457  				t.Errorf("got Process(_, %d, %d, %t, _, %q) = (_, _, _, %v), want = (_, _, _, %v)", test.first, test.last, test.more, test.data, err, test.err)
   458  			}
   459  			if done {
   460  				t.Errorf("got Process(_, %d, %d, %t, _, %q) = (_, _, true, _), want = (_, _, false, _)", test.first, test.last, test.more, test.data)
   461  			}
   462  		})
   463  	}
   464  }
   465  
   466  type fragmentInfo struct {
   467  	remaining int
   468  	copied    int
   469  	offset    int
   470  	more      bool
   471  }
   472  
   473  func TestPacketFragmenter(t *testing.T) {
   474  	const (
   475  		reserve = 60
   476  		proto   = 0
   477  	)
   478  
   479  	tests := []struct {
   480  		name               string
   481  		fragmentPayloadLen uint32
   482  		transportHeaderLen int
   483  		payloadSize        int
   484  		wantFragments      []fragmentInfo
   485  	}{
   486  		{
   487  			name:               "Packet exactly fits in MTU",
   488  			fragmentPayloadLen: 1280,
   489  			transportHeaderLen: 0,
   490  			payloadSize:        1280,
   491  			wantFragments: []fragmentInfo{
   492  				{remaining: 0, copied: 1280, offset: 0, more: false},
   493  			},
   494  		},
   495  		{
   496  			name:               "Packet exactly does not fit in MTU",
   497  			fragmentPayloadLen: 1000,
   498  			transportHeaderLen: 0,
   499  			payloadSize:        1001,
   500  			wantFragments: []fragmentInfo{
   501  				{remaining: 1, copied: 1000, offset: 0, more: true},
   502  				{remaining: 0, copied: 1, offset: 1000, more: false},
   503  			},
   504  		},
   505  		{
   506  			name:               "Packet has a transport header",
   507  			fragmentPayloadLen: 560,
   508  			transportHeaderLen: 40,
   509  			payloadSize:        560,
   510  			wantFragments: []fragmentInfo{
   511  				{remaining: 1, copied: 560, offset: 0, more: true},
   512  				{remaining: 0, copied: 40, offset: 560, more: false},
   513  			},
   514  		},
   515  		{
   516  			name:               "Packet has a huge transport header",
   517  			fragmentPayloadLen: 500,
   518  			transportHeaderLen: 1300,
   519  			payloadSize:        500,
   520  			wantFragments: []fragmentInfo{
   521  				{remaining: 3, copied: 500, offset: 0, more: true},
   522  				{remaining: 2, copied: 500, offset: 500, more: true},
   523  				{remaining: 1, copied: 500, offset: 1000, more: true},
   524  				{remaining: 0, copied: 300, offset: 1500, more: false},
   525  			},
   526  		},
   527  	}
   528  
   529  	for _, test := range tests {
   530  		t.Run(test.name, func(t *testing.T) {
   531  			pkt := testutil.MakeRandPkt(test.transportHeaderLen, reserve, []int{test.payloadSize}, proto)
   532  			defer pkt.DecRef()
   533  			payloadView := stack.PayloadSince(pkt.TransportHeader())
   534  			defer payloadView.Release()
   535  			originalPayload := payloadView.AsSlice()
   536  			var reassembledPayload buffer.Buffer
   537  			defer reassembledPayload.Release()
   538  			pf := MakePacketFragmenter(pkt, test.fragmentPayloadLen, reserve)
   539  			for i := 0; ; i++ {
   540  				fragPkt, offset, copied, more := pf.BuildNextFragment()
   541  				defer fragPkt.DecRef()
   542  				wantFragment := test.wantFragments[i]
   543  				if got := pf.RemainingFragmentCount(); got != wantFragment.remaining {
   544  					t.Errorf("(fragment #%d) got pf.RemainingFragmentCount() = %d, want = %d", i, got, wantFragment.remaining)
   545  				}
   546  				if copied != wantFragment.copied {
   547  					t.Errorf("(fragment #%d) got copied = %d, want = %d", i, copied, wantFragment.copied)
   548  				}
   549  				if offset != wantFragment.offset {
   550  					t.Errorf("(fragment #%d) got offset = %d, want = %d", i, offset, wantFragment.offset)
   551  				}
   552  				if more != wantFragment.more {
   553  					t.Errorf("(fragment #%d) got more = %t, want = %t", i, more, wantFragment.more)
   554  				}
   555  				if got := uint32(fragPkt.Size()); got > test.fragmentPayloadLen {
   556  					t.Errorf("(fragment #%d) got fragPkt.Size() = %d, want <= %d", i, got, test.fragmentPayloadLen)
   557  				}
   558  				if got := fragPkt.AvailableHeaderBytes(); got != reserve {
   559  					t.Errorf("(fragment #%d) got fragPkt.AvailableHeaderBytes() = %d, want = %d", i, got, reserve)
   560  				}
   561  				if got := len(fragPkt.TransportHeader().Slice()); got != 0 {
   562  					t.Errorf("(fragment #%d) got fragPkt.TransportHeader().View().Size() = %d, want = 0", i, got)
   563  				}
   564  				fragBuf := fragPkt.Data().ToBuffer()
   565  				reassembledPayload.Merge(&fragBuf)
   566  				if !more {
   567  					if i != len(test.wantFragments)-1 {
   568  						t.Errorf("got fragment count = %d, want = %d", i, len(test.wantFragments)-1)
   569  					}
   570  					break
   571  				}
   572  			}
   573  			if diff := cmp.Diff(reassembledPayload.Flatten(), originalPayload); diff != "" {
   574  				t.Errorf("reassembledPayload mismatch (-want +got):\n%s", diff)
   575  			}
   576  		})
   577  	}
   578  }
   579  
   580  type testTimeoutHandler struct {
   581  	pkt *stack.PacketBuffer
   582  }
   583  
   584  func (h *testTimeoutHandler) OnReassemblyTimeout(pkt *stack.PacketBuffer) {
   585  	h.pkt = pkt
   586  }
   587  
   588  func TestTimeoutHandler(t *testing.T) {
   589  	const (
   590  		proto = 99
   591  	)
   592  
   593  	pk1 := pkt(1, "1")
   594  	defer pk1.DecRef()
   595  	pk2 := pkt(1, "2")
   596  	defer pk2.DecRef()
   597  
   598  	type processParam struct {
   599  		first uint16
   600  		last  uint16
   601  		more  bool
   602  		pkt   *stack.PacketBuffer
   603  	}
   604  
   605  	tests := []struct {
   606  		name      string
   607  		params    []processParam
   608  		wantError bool
   609  		wantPkt   *stack.PacketBuffer
   610  	}{
   611  		{
   612  			name: "onTimeout runs",
   613  			params: []processParam{
   614  				{
   615  					first: 0,
   616  					last:  0,
   617  					more:  true,
   618  					pkt:   pk1,
   619  				},
   620  			},
   621  			wantError: false,
   622  			wantPkt:   pk1,
   623  		},
   624  		{
   625  			name: "no first fragment",
   626  			params: []processParam{
   627  				{
   628  					first: 1,
   629  					last:  1,
   630  					more:  true,
   631  					pkt:   pk1,
   632  				},
   633  			},
   634  			wantError: false,
   635  			wantPkt:   nil,
   636  		},
   637  		{
   638  			name: "second pkt is ignored",
   639  			params: []processParam{
   640  				{
   641  					first: 0,
   642  					last:  0,
   643  					more:  true,
   644  					pkt:   pk1,
   645  				},
   646  				{
   647  					first: 0,
   648  					last:  0,
   649  					more:  true,
   650  					pkt:   pk2,
   651  				},
   652  			},
   653  			wantError: false,
   654  			wantPkt:   pk1,
   655  		},
   656  		{
   657  			name: "invalid args - first is greater than last",
   658  			params: []processParam{
   659  				{
   660  					first: 1,
   661  					last:  0,
   662  					more:  true,
   663  					pkt:   pk1,
   664  				},
   665  			},
   666  			wantError: true,
   667  			wantPkt:   nil,
   668  		},
   669  	}
   670  
   671  	id := FragmentID{ID: 0}
   672  
   673  	for _, test := range tests {
   674  		t.Run(test.name, func(t *testing.T) {
   675  			handler := &testTimeoutHandler{pkt: nil}
   676  
   677  			f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{}, handler)
   678  
   679  			for _, p := range test.params {
   680  				if _, _, _, err := f.Process(id, p.first, p.last, p.more, proto, p.pkt); err != nil && !test.wantError {
   681  					t.Errorf("f.Process error = %s", err)
   682  				}
   683  			}
   684  			if !test.wantError {
   685  				r, ok := f.reassemblers[id]
   686  				if !ok {
   687  					t.Fatal("Reassembler not found")
   688  				}
   689  				f.release(r, true)
   690  			}
   691  			switch {
   692  			case handler.pkt != nil && test.wantPkt == nil:
   693  				t.Errorf("got handler.pkt = not nil (pkt.Data = %x), want = nil", handler.pkt.Data().AsRange().ToSlice())
   694  			case handler.pkt == nil && test.wantPkt != nil:
   695  				t.Errorf("got handler.pkt = nil, want = not nil (pkt.Data = %x)", test.wantPkt.Data().AsRange().ToSlice())
   696  			case handler.pkt != nil && test.wantPkt != nil:
   697  				if diff := cmp.Diff(test.wantPkt.Data().AsRange().ToSlice(), handler.pkt.Data().AsRange().ToSlice()); diff != "" {
   698  					t.Errorf("pkt.Data mismatch (-want, +got):\n%s", diff)
   699  				}
   700  			}
   701  		})
   702  	}
   703  }
   704  
   705  func TestFragmentSurvivesReleaseJob(t *testing.T) {
   706  	handler := &testTimeoutHandler{pkt: nil}
   707  	c := faketime.NewManualClock()
   708  	f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, c, handler)
   709  	pkt := pkt(2, "01")
   710  	// Values to Process don't matter except for pkt.
   711  	resPkt, _, _, _ := f.Process(FragmentID{ID: 0}, 0, 1, false, 0, pkt)
   712  	pkt.DecRef()
   713  	// This clears out the references held by the reassembler.
   714  	c.Advance(reassembleTimeout)
   715  	// If Process doesn't give the returned packet its own reference, this will
   716  	// fail.
   717  	resPkt.DecRef()
   718  }