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 }