github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/pkg/tcpip/network/internal/fragmentation/reassembler_test.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  	"bytes"
    19  	"math"
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  	"github.com/SagerNet/gvisor/pkg/tcpip/buffer"
    24  	"github.com/SagerNet/gvisor/pkg/tcpip/faketime"
    25  	"github.com/SagerNet/gvisor/pkg/tcpip/stack"
    26  )
    27  
    28  type processParams struct {
    29  	first     uint16
    30  	last      uint16
    31  	more      bool
    32  	pkt       *stack.PacketBuffer
    33  	wantDone  bool
    34  	wantError error
    35  }
    36  
    37  func TestReassemblerProcess(t *testing.T) {
    38  	const proto = 99
    39  
    40  	v := func(size int) buffer.View {
    41  		payload := buffer.NewView(size)
    42  		for i := 1; i < size; i++ {
    43  			payload[i] = uint8(i) * 3
    44  		}
    45  		return payload
    46  	}
    47  
    48  	pkt := func(sizes ...int) *stack.PacketBuffer {
    49  		var vv buffer.VectorisedView
    50  		for _, size := range sizes {
    51  			vv.AppendView(v(size))
    52  		}
    53  		return stack.NewPacketBuffer(stack.PacketBufferOptions{
    54  			Data: vv,
    55  		})
    56  	}
    57  
    58  	var tests = []struct {
    59  		name    string
    60  		params  []processParams
    61  		want    []hole
    62  		wantPkt *stack.PacketBuffer
    63  	}{
    64  		{
    65  			name:   "No fragments",
    66  			params: nil,
    67  			want:   []hole{{first: 0, last: math.MaxUint16, filled: false, final: true}},
    68  		},
    69  		{
    70  			name:   "One fragment at beginning",
    71  			params: []processParams{{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
    72  			want: []hole{
    73  				{first: 0, last: 1, filled: true, final: false, pkt: pkt(2)},
    74  				{first: 2, last: math.MaxUint16, filled: false, final: true},
    75  			},
    76  		},
    77  		{
    78  			name:   "One fragment in the middle",
    79  			params: []processParams{{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
    80  			want: []hole{
    81  				{first: 1, last: 2, filled: true, final: false, pkt: pkt(2)},
    82  				{first: 0, last: 0, filled: false, final: false},
    83  				{first: 3, last: math.MaxUint16, filled: false, final: true},
    84  			},
    85  		},
    86  		{
    87  			name:   "One fragment at the end",
    88  			params: []processParams{{first: 1, last: 2, more: false, pkt: pkt(2), wantDone: false, wantError: nil}},
    89  			want: []hole{
    90  				{first: 1, last: 2, filled: true, final: true, pkt: pkt(2)},
    91  				{first: 0, last: 0, filled: false},
    92  			},
    93  		},
    94  		{
    95  			name:   "One fragment completing a packet",
    96  			params: []processParams{{first: 0, last: 1, more: false, pkt: pkt(2), wantDone: true, wantError: nil}},
    97  			want: []hole{
    98  				{first: 0, last: 1, filled: true, final: true},
    99  			},
   100  			wantPkt: pkt(2),
   101  		},
   102  		{
   103  			name: "Two fragments completing a packet",
   104  			params: []processParams{
   105  				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
   106  				{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
   107  			},
   108  			want: []hole{
   109  				{first: 0, last: 1, filled: true, final: false},
   110  				{first: 2, last: 3, filled: true, final: true},
   111  			},
   112  			wantPkt: pkt(2, 2),
   113  		},
   114  		{
   115  			name: "Two fragments completing a packet with a duplicate",
   116  			params: []processParams{
   117  				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
   118  				{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
   119  				{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
   120  			},
   121  			want: []hole{
   122  				{first: 0, last: 1, filled: true, final: false},
   123  				{first: 2, last: 3, filled: true, final: true},
   124  			},
   125  			wantPkt: pkt(2, 2),
   126  		},
   127  		{
   128  			name: "Two fragments completing a packet with a partial duplicate",
   129  			params: []processParams{
   130  				{first: 0, last: 3, more: true, pkt: pkt(4), wantDone: false, wantError: nil},
   131  				{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
   132  				{first: 4, last: 5, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
   133  			},
   134  			want: []hole{
   135  				{first: 0, last: 3, filled: true, final: false},
   136  				{first: 4, last: 5, filled: true, final: true},
   137  			},
   138  			wantPkt: pkt(4, 2),
   139  		},
   140  		{
   141  			name: "Two overlapping fragments",
   142  			params: []processParams{
   143  				{first: 0, last: 10, more: true, pkt: pkt(11), wantDone: false, wantError: nil},
   144  				{first: 5, last: 15, more: false, pkt: pkt(11), wantDone: false, wantError: ErrFragmentOverlap},
   145  			},
   146  			want: []hole{
   147  				{first: 0, last: 10, filled: true, final: false, pkt: pkt(11)},
   148  				{first: 11, last: math.MaxUint16, filled: false, final: true},
   149  			},
   150  		},
   151  		{
   152  			name: "Two final fragments with different ends",
   153  			params: []processParams{
   154  				{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
   155  				{first: 0, last: 9, more: false, pkt: pkt(10), wantDone: false, wantError: ErrFragmentConflict},
   156  			},
   157  			want: []hole{
   158  				{first: 10, last: 14, filled: true, final: true, pkt: pkt(5)},
   159  				{first: 0, last: 9, filled: false, final: false},
   160  			},
   161  		},
   162  		{
   163  			name: "Two final fragments - duplicate",
   164  			params: []processParams{
   165  				{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
   166  				{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
   167  			},
   168  			want: []hole{
   169  				{first: 5, last: 14, filled: true, final: true, pkt: pkt(10)},
   170  				{first: 0, last: 4, filled: false, final: false},
   171  			},
   172  		},
   173  		{
   174  			name: "Two final fragments - duplicate, with different ends",
   175  			params: []processParams{
   176  				{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
   177  				{first: 10, last: 13, more: false, pkt: pkt(4), wantDone: false, wantError: ErrFragmentConflict},
   178  			},
   179  			want: []hole{
   180  				{first: 5, last: 14, filled: true, final: true, pkt: pkt(10)},
   181  				{first: 0, last: 4, filled: false, final: false},
   182  			},
   183  		},
   184  	}
   185  
   186  	for _, test := range tests {
   187  		t.Run(test.name, func(t *testing.T) {
   188  			r := newReassembler(FragmentID{}, &faketime.NullClock{})
   189  			var resPkt *stack.PacketBuffer
   190  			var isDone bool
   191  			for _, param := range test.params {
   192  				pkt, _, done, _, err := r.process(param.first, param.last, param.more, proto, param.pkt)
   193  				if done != param.wantDone || err != param.wantError {
   194  					t.Errorf("got r.process(%d, %d, %t, %d, _) = (_, _, %t, _, %v), want = (%t, %v)", param.first, param.last, param.more, proto, done, err, param.wantDone, param.wantError)
   195  				}
   196  				if done {
   197  					resPkt = pkt
   198  					isDone = true
   199  				}
   200  			}
   201  
   202  			ignorePkt := func(a, b *stack.PacketBuffer) bool { return true }
   203  			cmpPktData := func(a, b *stack.PacketBuffer) bool {
   204  				if a == nil || b == nil {
   205  					return a == b
   206  				}
   207  				return bytes.Equal(a.Data().AsRange().ToOwnedView(), b.Data().AsRange().ToOwnedView())
   208  			}
   209  
   210  			if isDone {
   211  				if diff := cmp.Diff(
   212  					test.want, r.holes,
   213  					cmp.AllowUnexported(hole{}),
   214  					// Do not compare pkt in hole. Data will be altered.
   215  					cmp.Comparer(ignorePkt),
   216  				); diff != "" {
   217  					t.Errorf("r.holes mismatch (-want +got):\n%s", diff)
   218  				}
   219  				if diff := cmp.Diff(test.wantPkt, resPkt, cmp.Comparer(cmpPktData)); diff != "" {
   220  					t.Errorf("Reassembled pkt mismatch (-want +got):\n%s", diff)
   221  				}
   222  			} else {
   223  				if diff := cmp.Diff(
   224  					test.want, r.holes,
   225  					cmp.AllowUnexported(hole{}),
   226  					cmp.Comparer(cmpPktData),
   227  				); diff != "" {
   228  					t.Errorf("r.holes mismatch (-want +got):\n%s", diff)
   229  				}
   230  			}
   231  		})
   232  	}
   233  }