mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Send ICMP error message if IP fragment reassembly fails
Fixes #4427, #4428 PiperOrigin-RevId: 338805047
This commit is contained in:
committed by
gVisor bot
parent
d1e4813e01
commit
0a035a1011
@@ -953,6 +953,38 @@ func ICMPv6Code(want header.ICMPv6Code) TransportChecker {
|
||||
}
|
||||
}
|
||||
|
||||
// ICMPv6TypeSpecific creates a checker that checks the ICMPv6 TypeSpecific
|
||||
// field.
|
||||
func ICMPv6TypeSpecific(want uint32) TransportChecker {
|
||||
return func(t *testing.T, h header.Transport) {
|
||||
t.Helper()
|
||||
|
||||
icmpv6, ok := h.(header.ICMPv6)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected transport header passed to checker, got = %T, want = header.ICMPv6", h)
|
||||
}
|
||||
if got := icmpv6.TypeSpecific(); got != want {
|
||||
t.Fatalf("unexpected ICMP TypeSpecific, got = %d, want = %d", got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ICMPv6Payload creates a checker that checks the payload in an ICMPv6 packet.
|
||||
func ICMPv6Payload(want []byte) TransportChecker {
|
||||
return func(t *testing.T, h header.Transport) {
|
||||
t.Helper()
|
||||
|
||||
icmpv6, ok := h.(header.ICMPv6)
|
||||
if !ok {
|
||||
t.Fatalf("unexpected transport header passed to checker, got = %T, want = header.ICMPv6", h)
|
||||
}
|
||||
payload := icmpv6.Payload()
|
||||
if diff := cmp.Diff(want, payload); diff != "" {
|
||||
t.Errorf("ICMP payload mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NDP creates a checker that checks that the packet contains a valid NDP
|
||||
// message for type of ty, with potentially additional checks specified by
|
||||
// checkers.
|
||||
|
||||
@@ -99,7 +99,8 @@ const (
|
||||
|
||||
// ICMP codes for ICMPv4 Time Exceeded messages as defined in RFC 792.
|
||||
const (
|
||||
ICMPv4TTLExceeded ICMPv4Code = 0
|
||||
ICMPv4TTLExceeded ICMPv4Code = 0
|
||||
ICMPv4ReassemblyTimeout ICMPv4Code = 1
|
||||
)
|
||||
|
||||
// ICMP codes for ICMPv4 Destination Unreachable messages as defined in RFC 792.
|
||||
|
||||
@@ -136,8 +136,16 @@ func NewFragmentation(blockSize uint16, highMemoryLimit, lowMemoryLimit int, rea
|
||||
// proto is the protocol number marked in the fragment being processed. It has
|
||||
// to be given here outside of the FragmentID struct because IPv6 should not use
|
||||
// the protocol to identify a fragment.
|
||||
//
|
||||
// releaseCB is a callback that will run when the fragment reassembly of a
|
||||
// packet is complete or cancelled. releaseCB take a a boolean argument which is
|
||||
// true iff the reassembly is cancelled due to timeout. releaseCB should be
|
||||
// passed only with the first fragment of a packet. If more than one releaseCB
|
||||
// are passed for the same packet, only the first releaseCB will be saved for
|
||||
// the packet and the succeeding ones will be dropped by running them
|
||||
// immediately with a false argument.
|
||||
func (f *Fragmentation) Process(
|
||||
id FragmentID, first, last uint16, more bool, proto uint8, vv buffer.VectorisedView) (
|
||||
id FragmentID, first, last uint16, more bool, proto uint8, vv buffer.VectorisedView, releaseCB func(bool)) (
|
||||
buffer.VectorisedView, uint8, bool, error) {
|
||||
if first > last {
|
||||
return buffer.VectorisedView{}, 0, false, fmt.Errorf("first=%d is greater than last=%d: %w", first, last, ErrInvalidArgs)
|
||||
@@ -171,6 +179,12 @@ func (f *Fragmentation) Process(
|
||||
f.releaseReassemblersLocked()
|
||||
}
|
||||
}
|
||||
if releaseCB != nil {
|
||||
if !r.setCallback(releaseCB) {
|
||||
// We got a duplicate callback. Release it immediately.
|
||||
releaseCB(false /* timedOut */)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
|
||||
res, firstFragmentProto, done, consumed, err := r.process(first, last, more, proto, vv)
|
||||
@@ -178,14 +192,14 @@ func (f *Fragmentation) Process(
|
||||
// We probably got an invalid sequence of fragments. Just
|
||||
// discard the reassembler and move on.
|
||||
f.mu.Lock()
|
||||
f.release(r)
|
||||
f.release(r, false /* timedOut */)
|
||||
f.mu.Unlock()
|
||||
return buffer.VectorisedView{}, 0, false, fmt.Errorf("fragmentation processing error: %w", err)
|
||||
}
|
||||
f.mu.Lock()
|
||||
f.size += consumed
|
||||
if done {
|
||||
f.release(r)
|
||||
f.release(r, false /* timedOut */)
|
||||
}
|
||||
// Evict reassemblers if we are consuming more memory than highLimit until
|
||||
// we reach lowLimit.
|
||||
@@ -195,14 +209,14 @@ func (f *Fragmentation) Process(
|
||||
if tail == nil {
|
||||
break
|
||||
}
|
||||
f.release(tail)
|
||||
f.release(tail, false /* timedOut */)
|
||||
}
|
||||
}
|
||||
f.mu.Unlock()
|
||||
return res, firstFragmentProto, done, nil
|
||||
}
|
||||
|
||||
func (f *Fragmentation) release(r *reassembler) {
|
||||
func (f *Fragmentation) release(r *reassembler, timedOut bool) {
|
||||
// Before releasing a fragment we need to check if r is already marked as done.
|
||||
// Otherwise, we would delete it twice.
|
||||
if r.checkDoneOrMark() {
|
||||
@@ -216,6 +230,8 @@ func (f *Fragmentation) release(r *reassembler) {
|
||||
log.Printf("memory counter < 0 (%d), this is an accounting bug that requires investigation", f.size)
|
||||
f.size = 0
|
||||
}
|
||||
|
||||
r.release(timedOut) // releaseCB may run.
|
||||
}
|
||||
|
||||
// releaseReassemblersLocked releases already-expired reassemblers, then
|
||||
@@ -238,7 +254,7 @@ func (f *Fragmentation) releaseReassemblersLocked() {
|
||||
break
|
||||
}
|
||||
// If the oldest reassembler has already expired, release it.
|
||||
f.release(r)
|
||||
f.release(r, true /* timedOut*/)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ func TestFragmentationProcess(t *testing.T) {
|
||||
f := NewFragmentation(minBlockSize, 1024, 512, reassembleTimeout, &faketime.NullClock{})
|
||||
firstFragmentProto := c.in[0].proto
|
||||
for i, in := range c.in {
|
||||
vv, proto, done, err := f.Process(in.id, in.first, in.last, in.more, in.proto, in.vv)
|
||||
vv, proto, done, err := f.Process(in.id, in.first, in.last, in.more, in.proto, in.vv, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("f.Process(%+v, %d, %d, %t, %d, %X) failed: %s",
|
||||
in.id, in.first, in.last, in.more, in.proto, in.vv.ToView(), err)
|
||||
@@ -240,7 +240,7 @@ func TestReassemblingTimeout(t *testing.T) {
|
||||
for _, event := range test.events {
|
||||
clock.Advance(event.clockAdvance)
|
||||
if frag := event.fragment; frag != nil {
|
||||
_, _, done, err := f.Process(FragmentID{}, frag.first, frag.last, frag.more, protocol, vv(len(frag.data), frag.data))
|
||||
_, _, done, err := f.Process(FragmentID{}, frag.first, frag.last, frag.more, protocol, vv(len(frag.data), frag.data), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: f.Process failed: %s", event.name, err)
|
||||
}
|
||||
@@ -259,15 +259,15 @@ func TestReassemblingTimeout(t *testing.T) {
|
||||
func TestMemoryLimits(t *testing.T) {
|
||||
f := NewFragmentation(minBlockSize, 3, 1, reassembleTimeout, &faketime.NullClock{})
|
||||
// Send first fragment with id = 0.
|
||||
f.Process(FragmentID{ID: 0}, 0, 0, true, 0xFF, vv(1, "0"))
|
||||
f.Process(FragmentID{ID: 0}, 0, 0, true, 0xFF, vv(1, "0"), nil)
|
||||
// Send first fragment with id = 1.
|
||||
f.Process(FragmentID{ID: 1}, 0, 0, true, 0xFF, vv(1, "1"))
|
||||
f.Process(FragmentID{ID: 1}, 0, 0, true, 0xFF, vv(1, "1"), nil)
|
||||
// Send first fragment with id = 2.
|
||||
f.Process(FragmentID{ID: 2}, 0, 0, true, 0xFF, vv(1, "2"))
|
||||
f.Process(FragmentID{ID: 2}, 0, 0, true, 0xFF, vv(1, "2"), nil)
|
||||
|
||||
// Send first fragment with id = 3. This should caused id = 0 and id = 1 to be
|
||||
// evicted.
|
||||
f.Process(FragmentID{ID: 3}, 0, 0, true, 0xFF, vv(1, "3"))
|
||||
f.Process(FragmentID{ID: 3}, 0, 0, true, 0xFF, vv(1, "3"), nil)
|
||||
|
||||
if _, ok := f.reassemblers[FragmentID{ID: 0}]; ok {
|
||||
t.Errorf("Memory limits are not respected: id=0 has not been evicted.")
|
||||
@@ -283,9 +283,9 @@ func TestMemoryLimits(t *testing.T) {
|
||||
func TestMemoryLimitsIgnoresDuplicates(t *testing.T) {
|
||||
f := NewFragmentation(minBlockSize, 1, 0, reassembleTimeout, &faketime.NullClock{})
|
||||
// Send first fragment with id = 0.
|
||||
f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"))
|
||||
f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"), nil)
|
||||
// Send the same packet again.
|
||||
f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"))
|
||||
f.Process(FragmentID{}, 0, 0, true, 0xFF, vv(1, "0"), nil)
|
||||
|
||||
got := f.size
|
||||
want := 1
|
||||
@@ -377,7 +377,7 @@ func TestErrors(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
f := NewFragmentation(test.blockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{})
|
||||
_, _, done, err := f.Process(FragmentID{}, test.first, test.last, test.more, 0, vv(len(test.data), test.data))
|
||||
_, _, done, err := f.Process(FragmentID{}, test.first, test.last, test.more, 0, vv(len(test.data), test.data), nil)
|
||||
if !errors.Is(err, test.err) {
|
||||
t.Errorf("got Process(_, %d, %d, %t, _, %q) = (_, _, _, %v), want = (_, _, _, %v)", test.first, test.last, test.more, test.data, err, test.err)
|
||||
}
|
||||
@@ -497,3 +497,89 @@ func TestPacketFragmenter(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseCallback(t *testing.T) {
|
||||
const (
|
||||
proto = 99
|
||||
)
|
||||
|
||||
var result int
|
||||
var callbackReasonIsTimeout bool
|
||||
cb1 := func(timedOut bool) { result = 1; callbackReasonIsTimeout = timedOut }
|
||||
cb2 := func(timedOut bool) { result = 2; callbackReasonIsTimeout = timedOut }
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
callbacks []func(bool)
|
||||
timeout bool
|
||||
wantResult int
|
||||
wantCallbackReasonIsTimeout bool
|
||||
}{
|
||||
{
|
||||
name: "callback runs on release",
|
||||
callbacks: []func(bool){cb1},
|
||||
timeout: false,
|
||||
wantResult: 1,
|
||||
wantCallbackReasonIsTimeout: false,
|
||||
},
|
||||
{
|
||||
name: "first callback is nil",
|
||||
callbacks: []func(bool){nil, cb2},
|
||||
timeout: false,
|
||||
wantResult: 2,
|
||||
wantCallbackReasonIsTimeout: false,
|
||||
},
|
||||
{
|
||||
name: "two callbacks - first one is set",
|
||||
callbacks: []func(bool){cb1, cb2},
|
||||
timeout: false,
|
||||
wantResult: 1,
|
||||
wantCallbackReasonIsTimeout: false,
|
||||
},
|
||||
{
|
||||
name: "callback runs on timeout",
|
||||
callbacks: []func(bool){cb1},
|
||||
timeout: true,
|
||||
wantResult: 1,
|
||||
wantCallbackReasonIsTimeout: true,
|
||||
},
|
||||
{
|
||||
name: "no callbacks",
|
||||
callbacks: []func(bool){nil},
|
||||
timeout: false,
|
||||
wantResult: 0,
|
||||
wantCallbackReasonIsTimeout: false,
|
||||
},
|
||||
}
|
||||
|
||||
id := FragmentID{ID: 0}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
result = 0
|
||||
callbackReasonIsTimeout = false
|
||||
|
||||
f := NewFragmentation(minBlockSize, HighFragThreshold, LowFragThreshold, reassembleTimeout, &faketime.NullClock{})
|
||||
|
||||
for i, cb := range test.callbacks {
|
||||
_, _, _, err := f.Process(id, uint16(i), uint16(i), true, proto, vv(1, "0"), cb)
|
||||
if err != nil {
|
||||
t.Errorf("f.Process error = %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
r, ok := f.reassemblers[id]
|
||||
if !ok {
|
||||
t.Fatalf("Reassemberr not found")
|
||||
}
|
||||
f.release(r, test.timeout)
|
||||
|
||||
if result != test.wantResult {
|
||||
t.Errorf("got result = %d, want = %d", result, test.wantResult)
|
||||
}
|
||||
if callbackReasonIsTimeout != test.wantCallbackReasonIsTimeout {
|
||||
t.Errorf("got callbackReasonIsTimeout = %t, want = %t", callbackReasonIsTimeout, test.wantCallbackReasonIsTimeout)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ type reassembler struct {
|
||||
heap fragHeap
|
||||
done bool
|
||||
creationTime int64
|
||||
callback func(bool)
|
||||
}
|
||||
|
||||
func newReassembler(id FragmentID, clock tcpip.Clock) *reassembler {
|
||||
@@ -123,3 +124,24 @@ func (r *reassembler) checkDoneOrMark() bool {
|
||||
r.mu.Unlock()
|
||||
return prev
|
||||
}
|
||||
|
||||
func (r *reassembler) setCallback(c func(bool)) bool {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if r.callback != nil {
|
||||
return false
|
||||
}
|
||||
r.callback = c
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *reassembler) release(timedOut bool) {
|
||||
r.mu.Lock()
|
||||
callback := r.callback
|
||||
r.callback = nil
|
||||
r.mu.Unlock()
|
||||
|
||||
if callback != nil {
|
||||
callback(timedOut)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,3 +105,26 @@ func TestUpdateHoles(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetCallback(t *testing.T) {
|
||||
result := 0
|
||||
reasonTimeout := false
|
||||
|
||||
cb1 := func(timedOut bool) { result = 1; reasonTimeout = timedOut }
|
||||
cb2 := func(timedOut bool) { result = 2; reasonTimeout = timedOut }
|
||||
|
||||
r := newReassembler(FragmentID{}, &faketime.NullClock{})
|
||||
if !r.setCallback(cb1) {
|
||||
t.Errorf("setCallback failed")
|
||||
}
|
||||
if r.setCallback(cb2) {
|
||||
t.Errorf("setCallback should fail if one is already set")
|
||||
}
|
||||
r.release(true)
|
||||
if result != 1 {
|
||||
t.Errorf("got result = %d, want = 1", result)
|
||||
}
|
||||
if !reasonTimeout {
|
||||
t.Errorf("got reasonTimeout = %t, want = true", reasonTimeout)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ go_test(
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
"//pkg/tcpip/checker",
|
||||
"//pkg/tcpip/faketime",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/link/channel",
|
||||
"//pkg/tcpip/link/sniffer",
|
||||
|
||||
@@ -237,6 +237,13 @@ type icmpReasonProtoUnreachable struct{}
|
||||
|
||||
func (*icmpReasonProtoUnreachable) isICMPReason() {}
|
||||
|
||||
// icmpReasonReassemblyTimeout is an error where insufficient fragments are
|
||||
// received to complete reassembly of a packet within a configured time after
|
||||
// the reception of the first-arriving fragment of that packet.
|
||||
type icmpReasonReassemblyTimeout struct{}
|
||||
|
||||
func (*icmpReasonReassemblyTimeout) isICMPReason() {}
|
||||
|
||||
// returnError takes an error descriptor and generates the appropriate ICMP
|
||||
// error packet for IPv4 and sends it back to the remote device that sent
|
||||
// the problematic packet. It incorporates as much of that packet as
|
||||
@@ -377,17 +384,24 @@ func (p *protocol) returnError(r *stack.Route, reason icmpReason, pkt *stack.Pac
|
||||
icmpPkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
|
||||
|
||||
icmpHdr := header.ICMPv4(icmpPkt.TransportHeader().Push(header.ICMPv4MinimumSize))
|
||||
var counter *tcpip.StatCounter
|
||||
switch reason.(type) {
|
||||
case *icmpReasonPortUnreachable:
|
||||
icmpHdr.SetType(header.ICMPv4DstUnreachable)
|
||||
icmpHdr.SetCode(header.ICMPv4PortUnreachable)
|
||||
counter = sent.DstUnreachable
|
||||
case *icmpReasonProtoUnreachable:
|
||||
icmpHdr.SetType(header.ICMPv4DstUnreachable)
|
||||
icmpHdr.SetCode(header.ICMPv4ProtoUnreachable)
|
||||
counter = sent.DstUnreachable
|
||||
case *icmpReasonReassemblyTimeout:
|
||||
icmpHdr.SetType(header.ICMPv4TimeExceeded)
|
||||
icmpHdr.SetCode(header.ICMPv4ReassemblyTimeout)
|
||||
counter = sent.TimeExceeded
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
|
||||
}
|
||||
icmpHdr.SetType(header.ICMPv4DstUnreachable)
|
||||
icmpHdr.SetChecksum(header.ICMPv4Checksum(icmpHdr, icmpPkt.Data))
|
||||
counter := sent.DstUnreachable
|
||||
|
||||
if err := route.WritePacket(
|
||||
nil, /* gso */
|
||||
|
||||
@@ -38,7 +38,7 @@ const (
|
||||
// Considering that it is an old recommendation, we use the same reassembly
|
||||
// timeout that linux defines, which is 30 seconds:
|
||||
// https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/include/net/ip.h#L138
|
||||
reassembleTimeout = 30 * time.Second
|
||||
ReassembleTimeout = 30 * time.Second
|
||||
|
||||
// ProtocolNumber is the ipv4 protocol number.
|
||||
ProtocolNumber = header.IPv4ProtocolNumber
|
||||
@@ -520,6 +520,28 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
|
||||
r.Stats().IP.MalformedFragmentsReceived.Increment()
|
||||
return
|
||||
}
|
||||
|
||||
// Set up a callback in case we need to send a Time Exceeded Message, as per
|
||||
// RFC 792:
|
||||
//
|
||||
// If a host reassembling a fragmented datagram cannot complete the
|
||||
// reassembly due to missing fragments within its time limit it discards
|
||||
// the datagram, and it may send a time exceeded message.
|
||||
//
|
||||
// If fragment zero is not available then no time exceeded need be sent at
|
||||
// all.
|
||||
var releaseCB func(bool)
|
||||
if start == 0 {
|
||||
pkt := pkt.Clone()
|
||||
r := r.Clone()
|
||||
releaseCB = func(timedOut bool) {
|
||||
if timedOut {
|
||||
_ = e.protocol.returnError(&r, &icmpReasonReassemblyTimeout{}, pkt)
|
||||
}
|
||||
r.Release()
|
||||
}
|
||||
}
|
||||
|
||||
var ready bool
|
||||
var err error
|
||||
proto := h.Protocol()
|
||||
@@ -537,6 +559,7 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
|
||||
h.More(),
|
||||
proto,
|
||||
pkt.Data,
|
||||
releaseCB,
|
||||
)
|
||||
if err != nil {
|
||||
r.Stats().IP.MalformedPacketsReceived.Increment()
|
||||
@@ -856,7 +879,7 @@ func NewProtocol(s *stack.Stack) stack.NetworkProtocol {
|
||||
ids: ids,
|
||||
hashIV: hashIV,
|
||||
defaultTTL: DefaultTTL,
|
||||
fragmentation: fragmentation.NewFragmentation(fragmentblockSize, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, reassembleTimeout, s.Clock()),
|
||||
fragmentation: fragmentation.NewFragmentation(fragmentblockSize, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, ReassembleTimeout, s.Clock()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/faketime"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/sniffer"
|
||||
@@ -788,7 +789,6 @@ func TestInvalidFragments(t *testing.T) {
|
||||
autoChecksum bool // if true, the Checksum field will be overwritten.
|
||||
}
|
||||
|
||||
// These packets have both IHL and TotalLength set to 0.
|
||||
tests := []struct {
|
||||
name string
|
||||
fragments []fragmentData
|
||||
@@ -1028,7 +1028,6 @@ func TestInvalidFragments(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||
ipv4.NewProtocol,
|
||||
@@ -1071,6 +1070,259 @@ func TestInvalidFragments(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFragmentReassemblyTimeout(t *testing.T) {
|
||||
const (
|
||||
nicID = 1
|
||||
linkAddr = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
|
||||
addr1 = "\x0a\x00\x00\x01"
|
||||
addr2 = "\x0a\x00\x00\x02"
|
||||
tos = 0
|
||||
ident = 1
|
||||
ttl = 48
|
||||
protocol = 99
|
||||
data = "TEST_FRAGMENT_REASSEMBLY_TIMEOUT"
|
||||
)
|
||||
|
||||
type fragmentData struct {
|
||||
ipv4fields header.IPv4Fields
|
||||
payload []byte
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fragments []fragmentData
|
||||
expectICMP bool
|
||||
}{
|
||||
{
|
||||
name: "first fragment only",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: header.IPv4MinimumSize + 16,
|
||||
ID: ident,
|
||||
Flags: header.IPv4FlagMoreFragments,
|
||||
FragmentOffset: 0,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "two first fragments",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: header.IPv4MinimumSize + 16,
|
||||
ID: ident,
|
||||
Flags: header.IPv4FlagMoreFragments,
|
||||
FragmentOffset: 0,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: header.IPv4MinimumSize + 16,
|
||||
ID: ident,
|
||||
Flags: header.IPv4FlagMoreFragments,
|
||||
FragmentOffset: 0,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "second fragment only",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
|
||||
ID: ident,
|
||||
Flags: 0,
|
||||
FragmentOffset: 8,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
},
|
||||
expectICMP: false,
|
||||
},
|
||||
{
|
||||
name: "two fragments with a gap",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: header.IPv4MinimumSize + 8,
|
||||
ID: ident,
|
||||
Flags: header.IPv4FlagMoreFragments,
|
||||
FragmentOffset: 0,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[:8],
|
||||
},
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
|
||||
ID: ident,
|
||||
Flags: 0,
|
||||
FragmentOffset: 16,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "two fragments with a gap in reverse order",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: uint16(header.IPv4MinimumSize + len(data) - 16),
|
||||
ID: ident,
|
||||
Flags: 0,
|
||||
FragmentOffset: 16,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
{
|
||||
ipv4fields: header.IPv4Fields{
|
||||
IHL: header.IPv4MinimumSize,
|
||||
TOS: tos,
|
||||
TotalLength: header.IPv4MinimumSize + 8,
|
||||
ID: ident,
|
||||
Flags: header.IPv4FlagMoreFragments,
|
||||
FragmentOffset: 0,
|
||||
TTL: ttl,
|
||||
Protocol: protocol,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
payload: []byte(data)[:8],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
clock := faketime.NewManualClock()
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||
ipv4.NewProtocol,
|
||||
},
|
||||
Clock: clock,
|
||||
})
|
||||
e := channel.New(1, 1500, linkAddr)
|
||||
if err := s.CreateNIC(nicID, e); err != nil {
|
||||
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
||||
}
|
||||
if err := s.AddAddress(nicID, ipv4.ProtocolNumber, addr2); err != nil {
|
||||
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv4ProtocolNumber, addr2, err)
|
||||
}
|
||||
s.SetRouteTable([]tcpip.Route{{
|
||||
Destination: header.IPv4EmptySubnet,
|
||||
NIC: nicID,
|
||||
}})
|
||||
|
||||
var firstFragmentSent buffer.View
|
||||
for _, f := range test.fragments {
|
||||
pktSize := header.IPv4MinimumSize
|
||||
hdr := buffer.NewPrependable(pktSize)
|
||||
|
||||
ip := header.IPv4(hdr.Prepend(pktSize))
|
||||
ip.Encode(&f.ipv4fields)
|
||||
|
||||
ip.SetChecksum(0)
|
||||
ip.SetChecksum(^ip.CalculateChecksum())
|
||||
|
||||
vv := hdr.View().ToVectorisedView()
|
||||
vv.AppendView(f.payload)
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Data: vv,
|
||||
})
|
||||
|
||||
if firstFragmentSent == nil && ip.FragmentOffset() == 0 {
|
||||
firstFragmentSent = stack.PayloadSince(pkt.NetworkHeader())
|
||||
}
|
||||
|
||||
e.InjectInbound(header.IPv4ProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
clock.Advance(ipv4.ReassembleTimeout)
|
||||
|
||||
reply, ok := e.Read()
|
||||
if !test.expectICMP {
|
||||
if ok {
|
||||
t.Fatalf("unexpected ICMP error message received: %#v", reply)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected ICMP error message missing")
|
||||
}
|
||||
if firstFragmentSent == nil {
|
||||
t.Fatalf("unexpected ICMP error message received: %#v", reply)
|
||||
}
|
||||
|
||||
checker.IPv4(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(addr2),
|
||||
checker.DstAddr(addr1),
|
||||
checker.IPFullLength(uint16(header.IPv4MinimumSize+header.ICMPv4MinimumSize+firstFragmentSent.Size())),
|
||||
checker.IPv4HeaderLength(header.IPv4MinimumSize),
|
||||
checker.ICMPv4(
|
||||
checker.ICMPv4Type(header.ICMPv4TimeExceeded),
|
||||
checker.ICMPv4Code(header.ICMPv4ReassemblyTimeout),
|
||||
checker.ICMPv4Checksum(),
|
||||
checker.ICMPv4Payload([]byte(firstFragmentSent)),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestReceiveFragments feeds fragments in through the incoming packet path to
|
||||
// test reassembly
|
||||
func TestReceiveFragments(t *testing.T) {
|
||||
|
||||
@@ -36,6 +36,7 @@ go_test(
|
||||
"//pkg/tcpip",
|
||||
"//pkg/tcpip/buffer",
|
||||
"//pkg/tcpip/checker",
|
||||
"//pkg/tcpip/faketime",
|
||||
"//pkg/tcpip/header",
|
||||
"//pkg/tcpip/link/channel",
|
||||
"//pkg/tcpip/link/sniffer",
|
||||
|
||||
@@ -744,6 +744,13 @@ type icmpReasonPortUnreachable struct{}
|
||||
|
||||
func (*icmpReasonPortUnreachable) isICMPReason() {}
|
||||
|
||||
// icmpReasonReassemblyTimeout is an error where insufficient fragments are
|
||||
// received to complete reassembly of a packet within a configured time after
|
||||
// the reception of the first-arriving fragment of that packet.
|
||||
type icmpReasonReassemblyTimeout struct{}
|
||||
|
||||
func (*icmpReasonReassemblyTimeout) isICMPReason() {}
|
||||
|
||||
// returnError takes an error descriptor and generates the appropriate ICMP
|
||||
// error packet for IPv6 and sends it.
|
||||
func (p *protocol) returnError(r *stack.Route, reason icmpReason, pkt *stack.PacketBuffer) *tcpip.Error {
|
||||
@@ -836,7 +843,9 @@ func (p *protocol) returnError(r *stack.Route, reason icmpReason, pkt *stack.Pac
|
||||
if payloadLen > available {
|
||||
payloadLen = available
|
||||
}
|
||||
payload := buffer.NewVectorisedView(pkt.Size(), pkt.Views())
|
||||
payload := network.ToVectorisedView()
|
||||
payload.AppendView(transport)
|
||||
payload.Append(pkt.Data)
|
||||
payload.CapLength(payloadLen)
|
||||
|
||||
newPkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
@@ -857,6 +866,10 @@ func (p *protocol) returnError(r *stack.Route, reason icmpReason, pkt *stack.Pac
|
||||
icmpHdr.SetType(header.ICMPv6DstUnreachable)
|
||||
icmpHdr.SetCode(header.ICMPv6PortUnreachable)
|
||||
counter = sent.DstUnreachable
|
||||
case *icmpReasonReassemblyTimeout:
|
||||
icmpHdr.SetType(header.ICMPv6TimeExceeded)
|
||||
icmpHdr.SetCode(header.ICMPv6ReassemblyTimeout)
|
||||
counter = sent.TimeExceeded
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported ICMP type %T", reason))
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ const (
|
||||
//
|
||||
// Linux also uses 60 seconds for reassembly timeout:
|
||||
// https://github.com/torvalds/linux/blob/47ec5303d73ea344e84f46660fff693c57641386/include/net/ipv6.h#L456
|
||||
reassembleTimeout = 60 * time.Second
|
||||
ReassembleTimeout = 60 * time.Second
|
||||
|
||||
// ProtocolNumber is the ipv6 protocol number.
|
||||
ProtocolNumber = header.IPv6ProtocolNumber
|
||||
@@ -777,6 +777,8 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
|
||||
continue
|
||||
}
|
||||
|
||||
fragmentFieldOffset := it.ParseOffset()
|
||||
|
||||
// Don't consume the iterator if we have the first fragment because we
|
||||
// will use it to validate that the first fragment holds the upper layer
|
||||
// header.
|
||||
@@ -834,17 +836,59 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
|
||||
return
|
||||
}
|
||||
|
||||
// As per RFC 2460 Section 4.5:
|
||||
//
|
||||
// If the length of a fragment, as derived from the fragment packet's
|
||||
// Payload Length field, is not a multiple of 8 octets and the M flag
|
||||
// of that fragment is 1, then that fragment must be discarded and an
|
||||
// ICMP Parameter Problem, Code 0, message should be sent to the source
|
||||
// of the fragment, pointing to the Payload Length field of the
|
||||
// fragment packet.
|
||||
if extHdr.More() && fragmentPayloadLen%header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit != 0 {
|
||||
r.Stats().IP.MalformedPacketsReceived.Increment()
|
||||
r.Stats().IP.MalformedFragmentsReceived.Increment()
|
||||
_ = e.protocol.returnError(r, &icmpReasonParameterProblem{
|
||||
code: header.ICMPv6ErroneousHeader,
|
||||
pointer: header.IPv6PayloadLenOffset,
|
||||
}, pkt)
|
||||
return
|
||||
}
|
||||
|
||||
// The packet is a fragment, let's try to reassemble it.
|
||||
start := extHdr.FragmentOffset() * header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit
|
||||
|
||||
// Drop the fragment if the size of the reassembled payload would exceed
|
||||
// the maximum payload size.
|
||||
// As per RFC 2460 Section 4.5:
|
||||
//
|
||||
// If the length and offset of a fragment are such that the Payload
|
||||
// Length of the packet reassembled from that fragment would exceed
|
||||
// 65,535 octets, then that fragment must be discarded and an ICMP
|
||||
// Parameter Problem, Code 0, message should be sent to the source of
|
||||
// the fragment, pointing to the Fragment Offset field of the fragment
|
||||
// packet.
|
||||
if int(start)+fragmentPayloadLen > header.IPv6MaximumPayloadSize {
|
||||
r.Stats().IP.MalformedPacketsReceived.Increment()
|
||||
r.Stats().IP.MalformedFragmentsReceived.Increment()
|
||||
_ = e.protocol.returnError(r, &icmpReasonParameterProblem{
|
||||
code: header.ICMPv6ErroneousHeader,
|
||||
pointer: fragmentFieldOffset,
|
||||
}, pkt)
|
||||
return
|
||||
}
|
||||
|
||||
// Set up a callback in case we need to send a Time Exceeded Message as
|
||||
// per RFC 2460 Section 4.5.
|
||||
var releaseCB func(bool)
|
||||
if start == 0 {
|
||||
pkt := pkt.Clone()
|
||||
r := r.Clone()
|
||||
releaseCB = func(timedOut bool) {
|
||||
if timedOut {
|
||||
_ = e.protocol.returnError(&r, &icmpReasonReassemblyTimeout{}, pkt)
|
||||
}
|
||||
r.Release()
|
||||
}
|
||||
}
|
||||
|
||||
// Note that pkt doesn't have its transport header set after reassembly,
|
||||
// and won't until DeliverNetworkPacket sets it.
|
||||
data, proto, ready, err := e.protocol.fragmentation.Process(
|
||||
@@ -860,6 +904,7 @@ func (e *endpoint) HandlePacket(r *stack.Route, pkt *stack.PacketBuffer) {
|
||||
extHdr.More(),
|
||||
uint8(rawPayload.Identifier),
|
||||
rawPayload.Buf,
|
||||
releaseCB,
|
||||
)
|
||||
if err != nil {
|
||||
r.Stats().IP.MalformedPacketsReceived.Increment()
|
||||
@@ -1535,7 +1580,7 @@ func NewProtocolWithOptions(opts Options) stack.NetworkProtocolFactory {
|
||||
return func(s *stack.Stack) stack.NetworkProtocol {
|
||||
p := &protocol{
|
||||
stack: s,
|
||||
fragmentation: fragmentation.NewFragmentation(header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, reassembleTimeout, s.Clock()),
|
||||
fragmentation: fragmentation.NewFragmentation(header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit, fragmentation.HighFragThreshold, fragmentation.LowFragThreshold, ReassembleTimeout, s.Clock()),
|
||||
ids: ids,
|
||||
hashIV: hashIV,
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/tcpip"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/buffer"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/checker"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/faketime"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/header"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/link/channel"
|
||||
"gvisor.dev/gvisor/pkg/tcpip/network/testutil"
|
||||
@@ -1912,16 +1913,19 @@ func TestReceiveIPv6Fragments(t *testing.T) {
|
||||
|
||||
func TestInvalidIPv6Fragments(t *testing.T) {
|
||||
const (
|
||||
nicID = 1
|
||||
fragmentExtHdrLen = 8
|
||||
addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
|
||||
linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
|
||||
nicID = 1
|
||||
hoplimit = 255
|
||||
ident = 1
|
||||
data = "TEST_INVALID_IPV6_FRAGMENTS"
|
||||
)
|
||||
|
||||
payloadGen := func(payloadLen int) []byte {
|
||||
payload := make([]byte, payloadLen)
|
||||
for i := 0; i < len(payload); i++ {
|
||||
payload[i] = 0x30
|
||||
}
|
||||
return payload
|
||||
type fragmentData struct {
|
||||
ipv6Fields header.IPv6Fields
|
||||
ipv6FragmentFields header.IPv6FragmentFields
|
||||
payload []byte
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
@@ -1929,31 +1933,64 @@ func TestInvalidIPv6Fragments(t *testing.T) {
|
||||
fragments []fragmentData
|
||||
wantMalformedIPPackets uint64
|
||||
wantMalformedFragments uint64
|
||||
expectICMP bool
|
||||
expectICMPType header.ICMPv6Type
|
||||
expectICMPCode header.ICMPv6Code
|
||||
expectICMPTypeSpecific uint32
|
||||
}{
|
||||
{
|
||||
name: "fragments reassembled into a payload exceeding the max IPv6 payload size",
|
||||
name: "fragment size is not a multiple of 8 and the M flag is true",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
srcAddr: addr1,
|
||||
dstAddr: addr2,
|
||||
nextHdr: fragmentExtHdrID,
|
||||
data: buffer.NewVectorisedView(
|
||||
fragmentExtHdrLen+(header.IPv6MaximumPayloadSize+1)-16,
|
||||
[]buffer.View{
|
||||
// Fragment extension header.
|
||||
// Fragment offset = 8190, More = false, ID = 1
|
||||
buffer.View([]byte{uint8(header.UDPProtocolNumber), 0,
|
||||
((header.IPv6MaximumPayloadSize + 1) - 16) >> 8,
|
||||
((header.IPv6MaximumPayloadSize + 1) - 16) & math.MaxUint8,
|
||||
0, 0, 0, 1}),
|
||||
// Payload length = 16
|
||||
payloadGen(16),
|
||||
},
|
||||
),
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 9,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0 >> 3,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:9],
|
||||
},
|
||||
},
|
||||
wantMalformedIPPackets: 1,
|
||||
wantMalformedFragments: 1,
|
||||
expectICMP: true,
|
||||
expectICMPType: header.ICMPv6ParamProblem,
|
||||
expectICMPCode: header.ICMPv6ErroneousHeader,
|
||||
expectICMPTypeSpecific: header.IPv6PayloadLenOffset,
|
||||
},
|
||||
{
|
||||
name: "fragments reassembled into a payload exceeding the max IPv6 payload size",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: ((header.IPv6MaximumPayloadSize + 1) - 16) >> 3,
|
||||
M: false,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
wantMalformedIPPackets: 1,
|
||||
wantMalformedFragments: 1,
|
||||
expectICMP: true,
|
||||
expectICMPType: header.ICMPv6ParamProblem,
|
||||
expectICMPCode: header.ICMPv6ErroneousHeader,
|
||||
expectICMPTypeSpecific: header.IPv6MinimumSize + 2, /* offset for 'Fragment Offset' in the fragment header */
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1964,33 +2001,40 @@ func TestInvalidIPv6Fragments(t *testing.T) {
|
||||
NewProtocol,
|
||||
},
|
||||
})
|
||||
e := channel.New(0, 1500, linkAddr1)
|
||||
e := channel.New(1, 1500, linkAddr1)
|
||||
if err := s.CreateNIC(nicID, e); err != nil {
|
||||
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
||||
}
|
||||
if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
|
||||
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, ProtocolNumber, addr2, err)
|
||||
}
|
||||
s.SetRouteTable([]tcpip.Route{{
|
||||
Destination: header.IPv6EmptySubnet,
|
||||
NIC: nicID,
|
||||
}})
|
||||
|
||||
var expectICMPPayload buffer.View
|
||||
for _, f := range test.fragments {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize)
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize)
|
||||
|
||||
// Serialize IPv6 fixed header.
|
||||
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize))
|
||||
ip.Encode(&header.IPv6Fields{
|
||||
PayloadLength: uint16(f.data.Size()),
|
||||
NextHeader: f.nextHdr,
|
||||
HopLimit: 255,
|
||||
SrcAddr: f.srcAddr,
|
||||
DstAddr: f.dstAddr,
|
||||
})
|
||||
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize))
|
||||
ip.Encode(&f.ipv6Fields)
|
||||
|
||||
fragHDR := header.IPv6Fragment(hdr.View()[header.IPv6MinimumSize:])
|
||||
fragHDR.Encode(&f.ipv6FragmentFields)
|
||||
|
||||
vv := hdr.View().ToVectorisedView()
|
||||
vv.Append(f.data)
|
||||
vv.AppendView(f.payload)
|
||||
|
||||
e.InjectInbound(ProtocolNumber, stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Data: vv,
|
||||
}))
|
||||
})
|
||||
|
||||
if test.expectICMP {
|
||||
expectICMPPayload = stack.PayloadSince(pkt.NetworkHeader())
|
||||
}
|
||||
|
||||
e.InjectInbound(ProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
if got, want := s.Stats().IP.MalformedPacketsReceived.Value(), test.wantMalformedIPPackets; got != want {
|
||||
@@ -1999,6 +2043,287 @@ func TestInvalidIPv6Fragments(t *testing.T) {
|
||||
if got, want := s.Stats().IP.MalformedFragmentsReceived.Value(), test.wantMalformedFragments; got != want {
|
||||
t.Errorf("got Stats.IP.MalformedFragmentsReceived = %d, want = %d", got, want)
|
||||
}
|
||||
|
||||
reply, ok := e.Read()
|
||||
if !test.expectICMP {
|
||||
if ok {
|
||||
t.Fatalf("unexpected ICMP error message received: %#v", reply)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected ICMP error message missing")
|
||||
}
|
||||
|
||||
checker.IPv6(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(addr2),
|
||||
checker.DstAddr(addr1),
|
||||
checker.IPFullLength(uint16(header.IPv6MinimumSize+header.ICMPv6MinimumSize+expectICMPPayload.Size())),
|
||||
checker.ICMPv6(
|
||||
checker.ICMPv6Type(test.expectICMPType),
|
||||
checker.ICMPv6Code(test.expectICMPCode),
|
||||
checker.ICMPv6TypeSpecific(test.expectICMPTypeSpecific),
|
||||
checker.ICMPv6Payload([]byte(expectICMPPayload)),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFragmentReassemblyTimeout(t *testing.T) {
|
||||
const (
|
||||
addr1 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
addr2 = "\x0a\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02"
|
||||
linkAddr1 = tcpip.LinkAddress("\x0a\x0b\x0c\x0d\x0e\x0e")
|
||||
nicID = 1
|
||||
hoplimit = 255
|
||||
ident = 1
|
||||
data = "TEST_FRAGMENT_REASSEMBLY_TIMEOUT"
|
||||
)
|
||||
|
||||
type fragmentData struct {
|
||||
ipv6Fields header.IPv6Fields
|
||||
ipv6FragmentFields header.IPv6FragmentFields
|
||||
payload []byte
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
fragments []fragmentData
|
||||
expectICMP bool
|
||||
}{
|
||||
{
|
||||
name: "first fragment only",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "two first fragments",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "second fragment only",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 8,
|
||||
M: false,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
},
|
||||
expectICMP: false,
|
||||
},
|
||||
{
|
||||
name: "two fragments with a gap",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 8,
|
||||
M: false,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
{
|
||||
name: "two fragments with a gap in reverse order",
|
||||
fragments: []fragmentData{
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: uint16(header.IPv6FragmentHeaderSize + len(data) - 16),
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 8,
|
||||
M: false,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[16:],
|
||||
},
|
||||
{
|
||||
ipv6Fields: header.IPv6Fields{
|
||||
PayloadLength: header.IPv6FragmentHeaderSize + 16,
|
||||
NextHeader: header.IPv6FragmentHeader,
|
||||
HopLimit: hoplimit,
|
||||
SrcAddr: addr1,
|
||||
DstAddr: addr2,
|
||||
},
|
||||
ipv6FragmentFields: header.IPv6FragmentFields{
|
||||
NextHeader: uint8(header.UDPProtocolNumber),
|
||||
FragmentOffset: 0,
|
||||
M: true,
|
||||
Identification: ident,
|
||||
},
|
||||
payload: []byte(data)[:16],
|
||||
},
|
||||
},
|
||||
expectICMP: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
clock := faketime.NewManualClock()
|
||||
s := stack.New(stack.Options{
|
||||
NetworkProtocols: []stack.NetworkProtocolFactory{
|
||||
NewProtocol,
|
||||
},
|
||||
Clock: clock,
|
||||
})
|
||||
|
||||
e := channel.New(1, 1500, linkAddr1)
|
||||
if err := s.CreateNIC(nicID, e); err != nil {
|
||||
t.Fatalf("CreateNIC(%d, _) = %s", nicID, err)
|
||||
}
|
||||
if err := s.AddAddress(nicID, ProtocolNumber, addr2); err != nil {
|
||||
t.Fatalf("AddAddress(%d, %d, %s) = %s", nicID, header.IPv6ProtocolNumber, addr2, err)
|
||||
}
|
||||
s.SetRouteTable([]tcpip.Route{{
|
||||
Destination: header.IPv6EmptySubnet,
|
||||
NIC: nicID,
|
||||
}})
|
||||
|
||||
var firstFragmentSent buffer.View
|
||||
for _, f := range test.fragments {
|
||||
hdr := buffer.NewPrependable(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize)
|
||||
|
||||
ip := header.IPv6(hdr.Prepend(header.IPv6MinimumSize + header.IPv6FragmentHeaderSize))
|
||||
ip.Encode(&f.ipv6Fields)
|
||||
|
||||
fragHDR := header.IPv6Fragment(hdr.View()[header.IPv6MinimumSize:])
|
||||
fragHDR.Encode(&f.ipv6FragmentFields)
|
||||
|
||||
vv := hdr.View().ToVectorisedView()
|
||||
vv.AppendView(f.payload)
|
||||
|
||||
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
|
||||
Data: vv,
|
||||
})
|
||||
|
||||
if firstFragmentSent == nil && fragHDR.FragmentOffset() == 0 {
|
||||
firstFragmentSent = stack.PayloadSince(pkt.NetworkHeader())
|
||||
}
|
||||
|
||||
e.InjectInbound(ProtocolNumber, pkt)
|
||||
}
|
||||
|
||||
clock.Advance(ReassembleTimeout)
|
||||
|
||||
reply, ok := e.Read()
|
||||
if !test.expectICMP {
|
||||
if ok {
|
||||
t.Fatalf("unexpected ICMP error message received: %#v", reply)
|
||||
}
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
t.Fatal("expected ICMP error message missing")
|
||||
}
|
||||
if firstFragmentSent == nil {
|
||||
t.Fatalf("unexpected ICMP error message received: %#v", reply)
|
||||
}
|
||||
|
||||
checker.IPv6(t, stack.PayloadSince(reply.Pkt.NetworkHeader()),
|
||||
checker.SrcAddr(addr2),
|
||||
checker.DstAddr(addr1),
|
||||
checker.IPFullLength(uint16(header.IPv6MinimumSize+header.ICMPv6MinimumSize+firstFragmentSent.Size())),
|
||||
checker.ICMPv6(
|
||||
checker.ICMPv6Type(header.ICMPv6TimeExceeded),
|
||||
checker.ICMPv6Code(header.ICMPv6ReassemblyTimeout),
|
||||
checker.ICMPv6Payload([]byte(firstFragmentSent)),
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user