mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Reduce the memory overhead in IP fragment management
- Deep-copy pkt.Data and hold it instead of shallow-copy (vv.Clone). This allows the pkt's backing array, which includes the header portion, to be freed. - Remove fragHeap. The fragments are now held in holes struct instead. - Stop reserving the initial capacity of holes slice. PiperOrigin-RevId: 347198744
This commit is contained in:
committed by
gVisor bot
parent
4aef908c92
commit
08d36b6c63
@@ -18,7 +18,6 @@ go_template_instance(
|
||||
go_library(
|
||||
name = "fragmentation",
|
||||
srcs = [
|
||||
"frag_heap.go",
|
||||
"fragmentation.go",
|
||||
"reassembler.go",
|
||||
"reassembler_list.go",
|
||||
@@ -38,7 +37,6 @@ go_test(
|
||||
name = "fragmentation_test",
|
||||
size = "small",
|
||||
srcs = [
|
||||
"frag_heap_test.go",
|
||||
"fragmentation_test.go",
|
||||
"reassembler_test.go",
|
||||
],
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
// 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 (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
)
|
||||
|
||||
type fragment struct {
|
||||
offset uint16
|
||||
vv buffer.VectorisedView
|
||||
}
|
||||
|
||||
type fragHeap []fragment
|
||||
|
||||
func (h *fragHeap) Len() int {
|
||||
return len(*h)
|
||||
}
|
||||
|
||||
func (h *fragHeap) Less(i, j int) bool {
|
||||
return (*h)[i].offset < (*h)[j].offset
|
||||
}
|
||||
|
||||
func (h *fragHeap) Swap(i, j int) {
|
||||
(*h)[i], (*h)[j] = (*h)[j], (*h)[i]
|
||||
}
|
||||
|
||||
func (h *fragHeap) Push(x interface{}) {
|
||||
*h = append(*h, x.(fragment))
|
||||
}
|
||||
|
||||
func (h *fragHeap) Pop() interface{} {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[:n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// reassamble empties the heap and returns a VectorisedView
|
||||
// containing a reassambled version of the fragments inside the heap.
|
||||
func (h *fragHeap) reassemble() (buffer.VectorisedView, error) {
|
||||
curr := heap.Pop(h).(fragment)
|
||||
views := curr.vv.Views()
|
||||
size := curr.vv.Size()
|
||||
|
||||
if curr.offset != 0 {
|
||||
return buffer.VectorisedView{}, fmt.Errorf("offset of the first packet is != 0 (%d)", curr.offset)
|
||||
}
|
||||
|
||||
for h.Len() > 0 {
|
||||
curr := heap.Pop(h).(fragment)
|
||||
if int(curr.offset) < size {
|
||||
curr.vv.TrimFront(size - int(curr.offset))
|
||||
} else if int(curr.offset) > size {
|
||||
return buffer.VectorisedView{}, fmt.Errorf("packet has a hole, expected offset %d, got %d", size, curr.offset)
|
||||
}
|
||||
size += curr.vv.Size()
|
||||
views = append(views, curr.vv.Views()...)
|
||||
}
|
||||
return buffer.NewVectorisedView(size, views), nil
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
// 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 (
|
||||
"container/heap"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
)
|
||||
|
||||
var reassambleTestCases = []struct {
|
||||
comment string
|
||||
in []fragment
|
||||
want buffer.VectorisedView
|
||||
}{
|
||||
{
|
||||
comment: "Non-overlapping in-order",
|
||||
in: []fragment{
|
||||
{offset: 0, vv: vv(1, "0")},
|
||||
{offset: 1, vv: vv(1, "1")},
|
||||
},
|
||||
want: vv(2, "0", "1"),
|
||||
},
|
||||
{
|
||||
comment: "Non-overlapping out-of-order",
|
||||
in: []fragment{
|
||||
{offset: 1, vv: vv(1, "1")},
|
||||
{offset: 0, vv: vv(1, "0")},
|
||||
},
|
||||
want: vv(2, "0", "1"),
|
||||
},
|
||||
{
|
||||
comment: "Duplicated packets",
|
||||
in: []fragment{
|
||||
{offset: 0, vv: vv(1, "0")},
|
||||
{offset: 0, vv: vv(1, "0")},
|
||||
},
|
||||
want: vv(1, "0"),
|
||||
},
|
||||
{
|
||||
comment: "Overlapping in-order",
|
||||
in: []fragment{
|
||||
{offset: 0, vv: vv(2, "01")},
|
||||
{offset: 1, vv: vv(2, "12")},
|
||||
},
|
||||
want: vv(3, "01", "2"),
|
||||
},
|
||||
{
|
||||
comment: "Overlapping out-of-order",
|
||||
in: []fragment{
|
||||
{offset: 1, vv: vv(2, "12")},
|
||||
{offset: 0, vv: vv(2, "01")},
|
||||
},
|
||||
want: vv(3, "01", "2"),
|
||||
},
|
||||
{
|
||||
comment: "Overlapping subset in-order",
|
||||
in: []fragment{
|
||||
{offset: 0, vv: vv(3, "012")},
|
||||
{offset: 1, vv: vv(1, "1")},
|
||||
},
|
||||
want: vv(3, "012"),
|
||||
},
|
||||
{
|
||||
comment: "Overlapping subset out-of-order",
|
||||
in: []fragment{
|
||||
{offset: 1, vv: vv(1, "1")},
|
||||
{offset: 0, vv: vv(3, "012")},
|
||||
},
|
||||
want: vv(3, "012"),
|
||||
},
|
||||
}
|
||||
|
||||
func TestReassamble(t *testing.T) {
|
||||
for _, c := range reassambleTestCases {
|
||||
t.Run(c.comment, func(t *testing.T) {
|
||||
h := make(fragHeap, 0, 8)
|
||||
heap.Init(&h)
|
||||
for _, f := range c.in {
|
||||
heap.Push(&h, f)
|
||||
}
|
||||
got, err := h.reassemble()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(got, c.want) {
|
||||
t.Errorf("got reassemble(%+v) = %v, want = %v", c.in, got, c.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReassambleFailsForNonZeroOffset(t *testing.T) {
|
||||
h := make(fragHeap, 0, 8)
|
||||
heap.Init(&h)
|
||||
heap.Push(&h, fragment{offset: 1, vv: vv(1, "0")})
|
||||
_, err := h.reassemble()
|
||||
if err == nil {
|
||||
t.Errorf("reassemble() did not fail when the first packet had offset != 0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReassambleFailsForHoles(t *testing.T) {
|
||||
h := make(fragHeap, 0, 8)
|
||||
heap.Init(&h)
|
||||
heap.Push(&h, fragment{offset: 0, vv: vv(1, "0")})
|
||||
heap.Push(&h, fragment{offset: 2, vv: vv(1, "1")})
|
||||
_, err := h.reassemble()
|
||||
if err == nil {
|
||||
t.Errorf("reassemble() did not fail when there was a hole in the packet")
|
||||
}
|
||||
}
|
||||
@@ -53,6 +53,10 @@ var (
|
||||
// ErrFragmentOverlap indicates that, during reassembly, a fragment overlaps
|
||||
// with another one.
|
||||
ErrFragmentOverlap = errors.New("overlapping fragments")
|
||||
|
||||
// ErrFragmentConflict indicates that, during reassembly, some fragments are
|
||||
// in conflict with one another.
|
||||
ErrFragmentConflict = errors.New("conflicting fragments")
|
||||
)
|
||||
|
||||
// FragmentID is the identifier for a fragment.
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
package fragmentation
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
@@ -29,6 +28,8 @@ type hole struct {
|
||||
first uint16
|
||||
last uint16
|
||||
filled bool
|
||||
final bool
|
||||
data buffer.View
|
||||
}
|
||||
|
||||
type reassembler struct {
|
||||
@@ -39,7 +40,6 @@ type reassembler struct {
|
||||
mu sync.Mutex
|
||||
holes []hole
|
||||
filled int
|
||||
heap fragHeap
|
||||
done bool
|
||||
creationTime int64
|
||||
pkt *stack.PacketBuffer
|
||||
@@ -48,74 +48,17 @@ type reassembler struct {
|
||||
func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
|
||||
r := &reassembler{
|
||||
id: id,
|
||||
holes: make([]hole, 0, 16),
|
||||
heap: make(fragHeap, 0, 8),
|
||||
creationTime: clock.NowMonotonic(),
|
||||
}
|
||||
r.holes = append(r.holes, hole{
|
||||
first: 0,
|
||||
last: math.MaxUint16,
|
||||
filled: false,
|
||||
final: true,
|
||||
})
|
||||
return r
|
||||
}
|
||||
|
||||
// updateHoles updates the list of holes for an incoming fragment. It returns
|
||||
// true if the fragment fits, it is not a duplicate and it does not overlap with
|
||||
// another fragment.
|
||||
//
|
||||
// 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
|
||||
func (r *reassembler) updateHoles(first, last uint16, more bool) (bool, error) {
|
||||
for i := range r.holes {
|
||||
currentHole := &r.holes[i]
|
||||
|
||||
if currentHole.filled || last < currentHole.first || currentHole.last < first {
|
||||
continue
|
||||
}
|
||||
|
||||
if first < currentHole.first || currentHole.last < last {
|
||||
// Incoming fragment only partially fits in the free hole.
|
||||
return false, ErrFragmentOverlap
|
||||
}
|
||||
|
||||
r.filled++
|
||||
if first > currentHole.first {
|
||||
r.holes = append(r.holes, hole{
|
||||
first: currentHole.first,
|
||||
last: first - 1,
|
||||
filled: false,
|
||||
})
|
||||
}
|
||||
if last < currentHole.last && more {
|
||||
r.holes = append(r.holes, hole{
|
||||
first: last + 1,
|
||||
last: currentHole.last,
|
||||
filled: false,
|
||||
})
|
||||
}
|
||||
// Update the current hole to precisely match the incoming fragment.
|
||||
r.holes[i] = hole{
|
||||
first: first,
|
||||
last: last,
|
||||
filled: true,
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Incoming fragment is a duplicate/subset, or its offset comes after the end
|
||||
// of the reassembled payload.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *stack.PacketBuffer) (buffer.VectorisedView, uint8, bool, int, error) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
@@ -126,13 +69,73 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
|
||||
return buffer.VectorisedView{}, 0, false, 0, nil
|
||||
}
|
||||
|
||||
used, err := r.updateHoles(first, last, more)
|
||||
if err != nil {
|
||||
return buffer.VectorisedView{}, 0, false, 0, fmt.Errorf("fragment reassembly failed: %w", err)
|
||||
}
|
||||
|
||||
var holeFound bool
|
||||
var consumed int
|
||||
if used {
|
||||
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 buffer.VectorisedView{}, 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 buffer.VectorisedView{}, 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
|
||||
}
|
||||
v := pkt.Data.ToOwnedView()
|
||||
consumed = v.Size()
|
||||
r.size += consumed
|
||||
// Update the current hole to precisely match the incoming fragment.
|
||||
r.holes[i] = hole{
|
||||
first: first,
|
||||
last: last,
|
||||
filled: true,
|
||||
final: currentHole.final,
|
||||
data: v,
|
||||
}
|
||||
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
|
||||
@@ -145,22 +148,30 @@ func (r *reassembler) process(first, last uint16, more bool, proto uint8, pkt *s
|
||||
r.pkt = pkt
|
||||
r.proto = proto
|
||||
}
|
||||
vv := pkt.Data
|
||||
// We store the incoming packet only if it filled some holes.
|
||||
heap.Push(&r.heap, fragment{offset: first, vv: vv.Clone(nil)})
|
||||
consumed = vv.Size()
|
||||
r.size += consumed
|
||||
|
||||
break
|
||||
}
|
||||
if !holeFound {
|
||||
// Incoming fragment is beyond end.
|
||||
return buffer.VectorisedView{}, 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 buffer.VectorisedView{}, 0, false, consumed, nil
|
||||
}
|
||||
res, err := r.heap.reassemble()
|
||||
if err != nil {
|
||||
return buffer.VectorisedView{}, 0, false, 0, fmt.Errorf("fragment reassembly failed: %w", err)
|
||||
|
||||
sort.Slice(r.holes, func(i, j int) bool {
|
||||
return r.holes[i].first < r.holes[j].first
|
||||
})
|
||||
|
||||
var size int
|
||||
views := make([]buffer.View, 0, len(r.holes))
|
||||
for _, hole := range r.holes {
|
||||
views = append(views, hole.data)
|
||||
size += hole.data.Size()
|
||||
}
|
||||
return res, r.proto, true, consumed, nil
|
||||
return buffer.NewVectorisedView(size, views), r.proto, true, consumed, nil
|
||||
}
|
||||
|
||||
func (r *reassembler) checkDoneOrMark() bool {
|
||||
|
||||
@@ -19,105 +19,156 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/faketime"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/stack"
|
||||
)
|
||||
|
||||
type updateHolesParams struct {
|
||||
type processParams struct {
|
||||
first uint16
|
||||
last uint16
|
||||
more bool
|
||||
wantUsed bool
|
||||
pkt *stack.PacketBuffer
|
||||
wantDone bool
|
||||
wantError error
|
||||
}
|
||||
|
||||
func TestUpdateHoles(t *testing.T) {
|
||||
func TestReassemblerProcess(t *testing.T) {
|
||||
const proto = 99
|
||||
|
||||
v := func(size int) buffer.View {
|
||||
payload := buffer.NewView(size)
|
||||
for i := 1; i < size; i++ {
|
||||
payload[i] = uint8(i) * 3
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
pkt := func(size int) *stack.PacketBuffer {
|
||||
return stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Data: v(size).ToVectorisedView(),
|
||||
})
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
params []updateHolesParams
|
||||
params []processParams
|
||||
want []hole
|
||||
}{
|
||||
{
|
||||
name: "No fragments",
|
||||
params: nil,
|
||||
want: []hole{{first: 0, last: math.MaxUint16, filled: false}},
|
||||
want: []hole{{first: 0, last: math.MaxUint16, filled: false, final: true}},
|
||||
},
|
||||
{
|
||||
name: "One fragment at beginning",
|
||||
params: []updateHolesParams{{first: 0, last: 1, more: true, wantUsed: true, wantError: nil}},
|
||||
params: []processParams{{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
|
||||
want: []hole{
|
||||
{first: 0, last: 1, filled: true},
|
||||
{first: 2, last: math.MaxUint16, filled: false},
|
||||
{first: 0, last: 1, filled: true, final: false, data: v(2)},
|
||||
{first: 2, last: math.MaxUint16, filled: false, final: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "One fragment in the middle",
|
||||
params: []updateHolesParams{{first: 1, last: 2, more: true, wantUsed: true, wantError: nil}},
|
||||
params: []processParams{{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil}},
|
||||
want: []hole{
|
||||
{first: 1, last: 2, filled: true},
|
||||
{first: 0, last: 0, filled: false},
|
||||
{first: 3, last: math.MaxUint16, filled: false},
|
||||
{first: 1, last: 2, filled: true, final: false, data: v(2)},
|
||||
{first: 0, last: 0, filled: false, final: false},
|
||||
{first: 3, last: math.MaxUint16, filled: false, final: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "One fragment at the end",
|
||||
params: []updateHolesParams{{first: 1, last: 2, more: false, wantUsed: true, wantError: nil}},
|
||||
params: []processParams{{first: 1, last: 2, more: false, pkt: pkt(2), wantDone: false, wantError: nil}},
|
||||
want: []hole{
|
||||
{first: 1, last: 2, filled: true},
|
||||
{first: 1, last: 2, filled: true, final: true, data: v(2)},
|
||||
{first: 0, last: 0, filled: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "One fragment completing a packet",
|
||||
params: []updateHolesParams{{first: 0, last: 1, more: false, wantUsed: true, wantError: nil}},
|
||||
params: []processParams{{first: 0, last: 1, more: false, pkt: pkt(2), wantDone: true, wantError: nil}},
|
||||
want: []hole{
|
||||
{first: 0, last: 1, filled: true},
|
||||
{first: 0, last: 1, filled: true, final: true, data: v(2)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two fragments completing a packet",
|
||||
params: []updateHolesParams{
|
||||
{first: 0, last: 1, more: true, wantUsed: true, wantError: nil},
|
||||
{first: 2, last: 3, more: false, wantUsed: true, wantError: nil},
|
||||
params: []processParams{
|
||||
{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
|
||||
{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 0, last: 1, filled: true},
|
||||
{first: 2, last: 3, filled: true},
|
||||
{first: 0, last: 1, filled: true, final: false, data: v(2)},
|
||||
{first: 2, last: 3, filled: true, final: true, data: v(2)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two fragments completing a packet with a duplicate",
|
||||
params: []updateHolesParams{
|
||||
{first: 0, last: 1, more: true, wantUsed: true, wantError: nil},
|
||||
{first: 0, last: 1, more: true, wantUsed: false, wantError: nil},
|
||||
{first: 2, last: 3, more: false, wantUsed: true, wantError: nil},
|
||||
params: []processParams{
|
||||
{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
|
||||
{first: 0, last: 1, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
|
||||
{first: 2, last: 3, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 0, last: 1, filled: true},
|
||||
{first: 2, last: 3, filled: true},
|
||||
{first: 0, last: 1, filled: true, final: false, data: v(2)},
|
||||
{first: 2, last: 3, filled: true, final: true, data: v(2)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two fragments completing a packet with a partial duplicate",
|
||||
params: []processParams{
|
||||
{first: 0, last: 3, more: true, pkt: pkt(4), wantDone: false, wantError: nil},
|
||||
{first: 1, last: 2, more: true, pkt: pkt(2), wantDone: false, wantError: nil},
|
||||
{first: 4, last: 5, more: false, pkt: pkt(2), wantDone: true, wantError: nil},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 0, last: 3, filled: true, final: false, data: v(4)},
|
||||
{first: 4, last: 5, filled: true, final: true, data: v(2)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two overlapping fragments",
|
||||
params: []updateHolesParams{
|
||||
{first: 0, last: 10, more: true, wantUsed: true, wantError: nil},
|
||||
{first: 5, last: 15, more: false, wantUsed: false, wantError: ErrFragmentOverlap},
|
||||
{first: 11, last: 15, more: false, wantUsed: true, wantError: nil},
|
||||
params: []processParams{
|
||||
{first: 0, last: 10, more: true, pkt: pkt(11), wantDone: false, wantError: nil},
|
||||
{first: 5, last: 15, more: false, pkt: pkt(11), wantDone: false, wantError: ErrFragmentOverlap},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 0, last: 10, filled: true},
|
||||
{first: 11, last: 15, filled: true},
|
||||
{first: 0, last: 10, filled: true, final: false, data: v(11)},
|
||||
{first: 11, last: math.MaxUint16, filled: false, final: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Out of bounds fragment",
|
||||
params: []updateHolesParams{
|
||||
{first: 0, last: 10, more: true, wantUsed: true, wantError: nil},
|
||||
{first: 11, last: 15, more: false, wantUsed: true, wantError: nil},
|
||||
{first: 16, last: 20, more: false, wantUsed: false, wantError: nil},
|
||||
name: "Two final fragments with different ends",
|
||||
params: []processParams{
|
||||
{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
|
||||
{first: 0, last: 9, more: false, pkt: pkt(10), wantDone: false, wantError: ErrFragmentConflict},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 0, last: 10, filled: true},
|
||||
{first: 11, last: 15, filled: true},
|
||||
{first: 10, last: 14, filled: true, final: true, data: v(5)},
|
||||
{first: 0, last: 9, filled: false, final: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two final fragments - duplicate",
|
||||
params: []processParams{
|
||||
{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
|
||||
{first: 10, last: 14, more: false, pkt: pkt(5), wantDone: false, wantError: nil},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 5, last: 14, filled: true, final: true, data: v(10)},
|
||||
{first: 0, last: 4, filled: false, final: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Two final fragments - duplicate, with different ends",
|
||||
params: []processParams{
|
||||
{first: 5, last: 14, more: false, pkt: pkt(10), wantDone: false, wantError: nil},
|
||||
{first: 10, last: 13, more: false, pkt: pkt(4), wantDone: false, wantError: ErrFragmentConflict},
|
||||
},
|
||||
want: []hole{
|
||||
{first: 5, last: 14, filled: true, final: true, data: v(10)},
|
||||
{first: 0, last: 4, filled: false, final: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -126,9 +177,9 @@ func TestUpdateHoles(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
r := newReassembler(FragmentID{}, &faketime.NullClock{})
|
||||
for _, param := range test.params {
|
||||
used, err := r.updateHoles(param.first, param.last, param.more)
|
||||
if used != param.wantUsed || err != param.wantError {
|
||||
t.Errorf("got r.updateHoles(%d, %d, %t) = (%t, %v), want = (%t, %v)", param.first, param.last, param.more, used, err, param.wantUsed, param.wantError)
|
||||
_, _, done, _, err := r.process(param.first, param.last, param.more, proto, param.pkt)
|
||||
if done != param.wantDone || err != param.wantError {
|
||||
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)
|
||||
}
|
||||
}
|
||||
if diff := cmp.Diff(test.want, r.holes, cmp.AllowUnexported(hole{})); diff != "" {
|
||||
|
||||
Reference in New Issue
Block a user