diff options
Diffstat (limited to 'test/packetimpact')
-rw-r--r-- | test/packetimpact/runner/defs.bzl | 3 | ||||
-rw-r--r-- | test/packetimpact/testbench/connections.go | 55 | ||||
-rw-r--r-- | test/packetimpact/testbench/dut_client.go | 2 | ||||
-rw-r--r-- | test/packetimpact/testbench/layers.go | 40 | ||||
-rw-r--r-- | test/packetimpact/tests/BUILD | 13 | ||||
-rw-r--r-- | test/packetimpact/tests/ipv4_fragment_reassembly_test.go | 142 | ||||
-rw-r--r-- | test/packetimpact/tests/ipv6_fragment_reassembly_test.go | 237 | ||||
-rw-r--r-- | test/packetimpact/tests/tcp_network_unreachable_test.go | 4 |
8 files changed, 359 insertions, 137 deletions
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 1546d0d51..c03c2c62c 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -252,6 +252,9 @@ ALL_TESTS = [ expect_netstack_failure = True, ), PacketimpactTestInfo( + name = "ipv4_fragment_reassembly", + ), + PacketimpactTestInfo( name = "ipv6_fragment_reassembly", ), PacketimpactTestInfo( diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index a90046f69..8fa585804 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -839,6 +839,61 @@ func (conn *TCPIPv4) Drain(t *testing.T) { conn.sniffer.Drain(t) } +// IPv4Conn maintains the state for all the layers in a IPv4 connection. +type IPv4Conn Connection + +// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults. +func NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn { + t.Helper() + + etherState, err := newEtherState(Ether{}, Ether{}) + if err != nil { + t.Fatalf("can't make EtherState: %s", err) + } + ipv4State, err := newIPv4State(outgoingIPv4, incomingIPv4) + if err != nil { + t.Fatalf("can't make IPv4State: %s", err) + } + + injector, err := NewInjector(t) + if err != nil { + t.Fatalf("can't make injector: %s", err) + } + sniffer, err := NewSniffer(t) + if err != nil { + t.Fatalf("can't make sniffer: %s", err) + } + + return IPv4Conn{ + layerStates: []layerState{etherState, ipv4State}, + injector: injector, + sniffer: sniffer, + } +} + +// Send sends a frame with ipv4 overriding the IPv4 layer defaults and +// additionalLayers added after it. +func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) { + t.Helper() + + (*Connection)(c).send(t, Layers{&ipv4}, additionalLayers...) +} + +// Close cleans up any resources held. +func (c *IPv4Conn) Close(t *testing.T) { + t.Helper() + + (*Connection)(c).Close(t) +} + +// ExpectFrame expects a frame that matches the provided Layers within the +// timeout specified. If it doesn't arrive in time, an error is returned. +func (c *IPv4Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) { + t.Helper() + + return (*Connection)(c).ExpectFrame(t, frame, timeout) +} + // IPv6Conn maintains the state for all the layers in a IPv6 connection. type IPv6Conn Connection diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go index d0e68c5da..0fc3d97b4 100644 --- a/test/packetimpact/testbench/dut_client.go +++ b/test/packetimpact/testbench/dut_client.go @@ -19,7 +19,7 @@ import ( pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto" ) -// PosixClient is a gRPC client for the Posix service. +// POSIXClient is a gRPC client for the Posix service. type POSIXClient pb.PosixClient // NewPOSIXClient makes a new gRPC client for the POSIX service. diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go index a35562ca8..af7a2ba4e 100644 --- a/test/packetimpact/testbench/layers.go +++ b/test/packetimpact/testbench/layers.go @@ -879,6 +879,9 @@ type ICMPv4 struct { Type *header.ICMPv4Type Code *header.ICMPv4Code Checksum *uint16 + Ident *uint16 + Sequence *uint16 + Payload []byte } func (l *ICMPv4) String() string { @@ -887,7 +890,7 @@ func (l *ICMPv4) String() string { // ToBytes implements Layer.ToBytes. func (l *ICMPv4) ToBytes() ([]byte, error) { - b := make([]byte, header.ICMPv4MinimumSize) + b := make([]byte, header.ICMPv4MinimumSize+len(l.Payload)) h := header.ICMPv4(b) if l.Type != nil { h.SetType(*l.Type) @@ -895,15 +898,33 @@ func (l *ICMPv4) ToBytes() ([]byte, error) { if l.Code != nil { h.SetCode(*l.Code) } + if copied := copy(h.Payload(), l.Payload); copied != len(l.Payload) { + panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", len(h.Payload()), len(l.Payload))) + } + if l.Ident != nil { + h.SetIdent(*l.Ident) + } + if l.Sequence != nil { + h.SetSequence(*l.Sequence) + } + + // The checksum must be handled last because the ICMPv4 header fields are + // included in the computation. if l.Checksum != nil { h.SetChecksum(*l.Checksum) - return h, nil - } - payload, err := payload(l) - if err != nil { - return nil, err + } else { + // Compute the checksum based on the ICMPv4.Payload and also the subsequent + // layers. + payload, err := payload(l) + if err != nil { + return nil, err + } + var vv buffer.VectorisedView + vv.AppendView(buffer.View(l.Payload)) + vv.Append(payload) + h.SetChecksum(header.ICMPv4Checksum(h, vv)) } - h.SetChecksum(header.ICMPv4Checksum(h, payload)) + return h, nil } @@ -915,8 +936,11 @@ func parseICMPv4(b []byte) (Layer, layerParser) { Type: ICMPv4Type(h.Type()), Code: ICMPv4Code(h.Code()), Checksum: Uint16(h.Checksum()), + Ident: Uint16(h.Ident()), + Sequence: Uint16(h.Sequence()), + Payload: h.Payload(), } - return &icmpv4, parsePayload + return &icmpv4, nil } func (l *ICMPv4) match(other Layer) bool { diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 8c2de5a9f..c30c77a17 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -298,6 +298,18 @@ packetimpact_testbench( ) packetimpact_testbench( + name = "ipv4_fragment_reassembly", + srcs = ["ipv4_fragment_reassembly_test.go"], + deps = [ + "//pkg/tcpip/buffer", + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_testbench( name = "ipv6_fragment_reassembly", srcs = ["ipv6_fragment_reassembly_test.go"], deps = [ @@ -305,6 +317,7 @@ packetimpact_testbench( "//pkg/tcpip/buffer", "//pkg/tcpip/header", "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go new file mode 100644 index 000000000..65c0df140 --- /dev/null +++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go @@ -0,0 +1,142 @@ +// Copyright 2020 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 ipv4_fragment_reassembly_test + +import ( + "flag" + "math/rand" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "gvisor.dev/gvisor/pkg/tcpip/buffer" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.RegisterFlags(flag.CommandLine) +} + +type fragmentInfo struct { + offset uint16 + size uint16 + more uint8 +} + +func TestIPv4FragmentReassembly(t *testing.T) { + const fragmentID = 42 + icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber) + + tests := []struct { + description string + ipPayloadLen int + fragments []fragmentInfo + expectReply bool + }{ + { + description: "basic reassembly", + ipPayloadLen: 2000, + fragments: []fragmentInfo{ + {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, + {offset: 1000, size: 1000, more: 0}, + }, + expectReply: true, + }, + { + description: "out of order fragments", + ipPayloadLen: 2000, + fragments: []fragmentInfo{ + {offset: 1000, size: 1000, more: 0}, + {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments}, + }, + expectReply: true, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + conn := testbench.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{}) + defer conn.Close(t) + + data := make([]byte, test.ipPayloadLen) + icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize]) + icmp.SetType(header.ICMPv4Echo) + icmp.SetCode(header.ICMPv4UnusedCode) + icmp.SetChecksum(0) + icmp.SetSequence(0) + icmp.SetIdent(0) + originalPayload := data[header.ICMPv4MinimumSize:] + if _, err := rand.Read(originalPayload); err != nil { + t.Fatalf("rand.Read: %s", err) + } + cksum := header.ICMPv4Checksum( + icmp, + buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}), + ) + icmp.SetChecksum(cksum) + + for _, fragment := range test.fragments { + conn.Send(t, + testbench.IPv4{ + Protocol: &icmpv4ProtoNum, + FragmentOffset: testbench.Uint16(fragment.offset), + Flags: testbench.Uint8(fragment.more), + ID: testbench.Uint16(fragmentID), + }, + &testbench.Payload{ + Bytes: data[fragment.offset:][:fragment.size], + }) + } + + var bytesReceived int + reassembledPayload := make([]byte, test.ipPayloadLen) + for { + incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv4{}, + &testbench.ICMPv4{}, + }, time.Second) + if err != nil { + // Either an unexpected frame was received, or none at all. + if bytesReceived < test.ipPayloadLen { + t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) + } + break + } + if !test.expectReply { + t.Fatalf("unexpected reply received:\n%s", incomingFrame) + } + ipPayload, err := incomingFrame[2 /* ICMPv4 */].ToBytes() + if err != nil { + t.Fatalf("failed to parse ICMPv4 header: incomingPacket[2].ToBytes() = (_, %s)", err) + } + offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset + if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { + t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) + } + bytesReceived += len(ipPayload) + } + + if test.expectReply { + if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" { + t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) + } + } + }) + } +} diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go index a24c85566..4a29de688 100644 --- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go +++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go @@ -15,154 +15,137 @@ package ipv6_fragment_reassembly_test import ( - "bytes" - "encoding/binary" - "encoding/hex" "flag" + "math/rand" "net" "testing" "time" + "github.com/google/go-cmp/cmp" "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/buffer" "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/test/packetimpact/testbench" ) -const ( - // The payload length for the first fragment we send. This number - // is a multiple of 8 near 750 (half of 1500). - firstPayloadLength = 752 - // The ID field for our outgoing fragments. - fragmentID = 1 - // A node must be able to accept a fragmented packet that, - // after reassembly, is as large as 1500 octets. - reassemblyCap = 1500 -) - func init() { testbench.RegisterFlags(flag.CommandLine) } -func TestIPv6FragmentReassembly(t *testing.T) { - dut := testbench.NewDUT(t) - defer dut.TearDown() - conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) - defer conn.Close(t) - - firstPayloadToSend := make([]byte, firstPayloadLength) - for i := range firstPayloadToSend { - firstPayloadToSend[i] = 'A' - } - - secondPayloadLength := reassemblyCap - firstPayloadLength - header.ICMPv6EchoMinimumSize - secondPayloadToSend := firstPayloadToSend[:secondPayloadLength] - - icmpv6EchoPayload := make([]byte, 4) - binary.BigEndian.PutUint16(icmpv6EchoPayload[0:], 0) - binary.BigEndian.PutUint16(icmpv6EchoPayload[2:], 0) - icmpv6EchoPayload = append(icmpv6EchoPayload, firstPayloadToSend...) - - lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()) - rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()) - icmpv6 := testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - Payload: icmpv6EchoPayload, - } - icmpv6Bytes, err := icmpv6.ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6: %s", err) - } - cksum := header.ICMPv6Checksum( - header.ICMPv6(icmpv6Bytes), - lIP, - rIP, - buffer.NewVectorisedView(len(secondPayloadToSend), []buffer.View{secondPayloadToSend}), - ) - - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), - Identification: testbench.Uint32(fragmentID), - }, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - Payload: icmpv6EchoPayload, - Checksum: &cksum, - }) +type fragmentInfo struct { + offset uint16 + size uint16 + more bool +} +func TestIPv6FragmentReassembly(t *testing.T) { + const fragmentID = 42 icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) - conn.Send(t, testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16((firstPayloadLength + header.ICMPv6EchoMinimumSize) / 8), - MoreFragments: testbench.Bool(false), - Identification: testbench.Uint32(fragmentID), + tests := []struct { + description string + ipPayloadLen int + fragments []fragmentInfo + expectReply bool + }{ + { + description: "basic reassembly", + ipPayloadLen: 1500, + fragments: []fragmentInfo{ + {offset: 0, size: 760, more: true}, + {offset: 760, size: 740, more: false}, + }, + expectReply: true, }, - &testbench.Payload{ - Bytes: secondPayloadToSend, - }) - - gotEchoReplyFirstPart, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - FragmentOffset: testbench.Uint16(0), - MoreFragments: testbench.Bool(true), + { + description: "out of order fragments", + ipPayloadLen: 3000, + fragments: []fragmentInfo{ + {offset: 0, size: 1024, more: true}, + {offset: 2048, size: 952, more: false}, + {offset: 1024, size: 1024, more: true}, + }, + expectReply: true, }, - &testbench.ICMPv6{ - Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), - Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), - }, - }, time.Second) - if err != nil { - t.Fatalf("expected a fragmented ICMPv6 Echo Reply, but got none: %s", err) } - id := *gotEchoReplyFirstPart[2].(*testbench.IPv6FragmentExtHdr).Identification - gotFirstPayload, err := gotEchoReplyFirstPart[len(gotEchoReplyFirstPart)-1].ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6: %s", err) - } - icmpPayload := gotFirstPayload[header.ICMPv6EchoMinimumSize:] - receivedLen := len(icmpPayload) - wantSecondPayloadLen := reassemblyCap - header.ICMPv6EchoMinimumSize - receivedLen - wantFirstPayload := make([]byte, receivedLen) - for i := range wantFirstPayload { - wantFirstPayload[i] = 'A' - } - wantSecondPayload := wantFirstPayload[:wantSecondPayloadLen] - if !bytes.Equal(icmpPayload, wantFirstPayload) { - t.Fatalf("received unexpected payload, got: %s, want: %s", - hex.Dump(icmpPayload), - hex.Dump(wantFirstPayload)) - } - - gotEchoReplySecondPart, err := conn.ExpectFrame(t, testbench.Layers{ - &testbench.Ether{}, - &testbench.IPv6{}, - &testbench.IPv6FragmentExtHdr{ - NextHeader: &icmpv6ProtoNum, - FragmentOffset: testbench.Uint16(uint16((receivedLen + header.ICMPv6EchoMinimumSize) / 8)), - MoreFragments: testbench.Bool(false), - Identification: &id, - }, - &testbench.ICMPv6{}, - }, time.Second) - if err != nil { - t.Fatalf("expected the rest of ICMPv6 Echo Reply, but got none: %s", err) - } - secondPayload, err := gotEchoReplySecondPart[len(gotEchoReplySecondPart)-1].ToBytes() - if err != nil { - t.Fatalf("failed to serialize ICMPv6 Echo Reply: %s", err) - } - if !bytes.Equal(secondPayload, wantSecondPayload) { - t.Fatalf("received unexpected payload, got: %s, want: %s", - hex.Dump(secondPayload), - hex.Dump(wantSecondPayload)) + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + defer conn.Close(t) + + lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()) + rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()) + + data := make([]byte, test.ipPayloadLen) + icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize]) + icmp.SetType(header.ICMPv6EchoRequest) + icmp.SetCode(header.ICMPv6UnusedCode) + icmp.SetChecksum(0) + originalPayload := data[header.ICMPv6HeaderSize:] + if _, err := rand.Read(originalPayload); err != nil { + t.Fatalf("rand.Read: %s", err) + } + + cksum := header.ICMPv6Checksum( + icmp, + lIP, + rIP, + buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}), + ) + icmp.SetChecksum(cksum) + + for _, fragment := range test.fragments { + conn.Send(t, testbench.IPv6{}, + &testbench.IPv6FragmentExtHdr{ + NextHeader: &icmpv6ProtoNum, + FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit), + MoreFragments: testbench.Bool(fragment.more), + Identification: testbench.Uint32(fragmentID), + }, + &testbench.Payload{ + Bytes: data[fragment.offset:][:fragment.size], + }) + } + + var bytesReceived int + reassembledPayload := make([]byte, test.ipPayloadLen) + for { + incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv6{}, + &testbench.IPv6FragmentExtHdr{}, + &testbench.ICMPv6{}, + }, time.Second) + if err != nil { + // Either an unexpected frame was received, or none at all. + if bytesReceived < test.ipPayloadLen { + t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err) + } + break + } + if !test.expectReply { + t.Fatalf("unexpected reply received:\n%s", incomingFrame) + } + ipPayload, err := incomingFrame[3 /* ICMPv6 */].ToBytes() + if err != nil { + t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err) + } + offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset + offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit + if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) { + t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload)) + } + bytesReceived += len(ipPayload) + } + + if test.expectReply { + if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" { + t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff) + } + } + }) } } diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go index 2f57dff19..8a1fe1279 100644 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ b/test/packetimpact/tests/tcp_network_unreachable_test.go @@ -74,7 +74,9 @@ func TestTCPSynSentUnreachable(t *testing.T) { } var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{ Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), - Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable)} + Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable), + } + layers = append(layers, &icmpv4, ip, tcp) rawConn.SendFrameStateless(t, layers) |