From 373fd8310032ef19e05f8cc77a1eeb6fcb438da8 Mon Sep 17 00:00:00 2001 From: Toshi Kikuchi Date: Mon, 16 Nov 2020 13:11:36 -0800 Subject: Add packetimpact tests for ICMPv6 Error message for fragment Updates #4427 PiperOrigin-RevId: 342703931 --- test/packetimpact/runner/defs.bzl | 3 + test/packetimpact/tests/BUILD | 14 + .../tests/ipv6_fragment_icmp_error_test.go | 360 +++++++++++++++++++++ 3 files changed, 377 insertions(+) create mode 100644 test/packetimpact/tests/ipv6_fragment_icmp_error_test.go diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index c03c2c62c..1038e3c8d 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -257,6 +257,9 @@ ALL_TESTS = [ PacketimpactTestInfo( name = "ipv6_fragment_reassembly", ), + PacketimpactTestInfo( + name = "ipv6_fragment_icmp_error", + ), PacketimpactTestInfo( name = "udp_send_recv_dgram", ), diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index c30c77a17..33bd070c1 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -322,6 +322,20 @@ packetimpact_testbench( ], ) +packetimpact_testbench( + name = "ipv6_fragment_icmp_error", + srcs = ["ipv6_fragment_icmp_error_test.go"], + deps = [ + "//pkg/tcpip", + "//pkg/tcpip/buffer", + "//pkg/tcpip/header", + "//pkg/tcpip/network/ipv6", + "//test/packetimpact/testbench", + "@com_github_google_go_cmp//cmp:go_default_library", + "@org_golang_x_sys//unix:go_default_library", + ], +) + packetimpact_testbench( name = "udp_send_recv_dgram", srcs = ["udp_send_recv_dgram_test.go"], diff --git a/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go new file mode 100644 index 000000000..e058fb0d8 --- /dev/null +++ b/test/packetimpact/tests/ipv6_fragment_icmp_error_test.go @@ -0,0 +1,360 @@ +// 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 ipv6_fragment_icmp_error_test + +import ( + "flag" + "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/pkg/tcpip/network/ipv6" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +const ( + data = "IPV6_PROTOCOL_TESTER_FOR_FRAGMENT" + fragmentID = 1 + reassemblyTimeout = ipv6.ReassembleTimeout + 5*time.Second +) + +func init() { + testbench.RegisterFlags(flag.CommandLine) +} + +func fragmentedICMPEchoRequest(t *testing.T, conn *testbench.Connection, firstPayloadLength uint16, payload []byte, secondFragmentOffset uint16) ([]testbench.Layers, [][]byte) { + t.Helper() + + icmpv6Header := header.ICMPv6(make([]byte, header.ICMPv6EchoMinimumSize)) + icmpv6Header.SetType(header.ICMPv6EchoRequest) + icmpv6Header.SetCode(header.ICMPv6UnusedCode) + icmpv6Header.SetIdent(0) + icmpv6Header.SetSequence(0) + cksum := header.ICMPv6Checksum( + icmpv6Header, + tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16()), + tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16()), + buffer.NewVectorisedView(len(payload), []buffer.View{payload}), + ) + icmpv6Header.SetChecksum(cksum) + icmpv6Bytes := append([]byte(icmpv6Header), payload...) + + icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber) + + firstFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, + &testbench.IPv6FragmentExtHdr{ + NextHeader: &icmpv6ProtoNum, + FragmentOffset: testbench.Uint16(0), + MoreFragments: testbench.Bool(true), + Identification: testbench.Uint32(fragmentID), + }, + &testbench.Payload{ + Bytes: icmpv6Bytes[:header.ICMPv6PayloadOffset+firstPayloadLength], + }, + ) + firstIPv6 := firstFragment[1:] + firstIPv6Bytes, err := firstIPv6.ToBytes() + if err != nil { + t.Fatalf("failed to convert first %s to bytes: %s", firstIPv6, err) + } + + secondFragment := conn.CreateFrame(t, testbench.Layers{&testbench.IPv6{}}, + &testbench.IPv6FragmentExtHdr{ + NextHeader: &icmpv6ProtoNum, + FragmentOffset: testbench.Uint16(secondFragmentOffset), + MoreFragments: testbench.Bool(false), + Identification: testbench.Uint32(fragmentID), + }, + &testbench.Payload{ + Bytes: icmpv6Bytes[header.ICMPv6PayloadOffset+firstPayloadLength:], + }, + ) + secondIPv6 := secondFragment[1:] + secondIPv6Bytes, err := secondIPv6.ToBytes() + if err != nil { + t.Fatalf("failed to convert second %s to bytes: %s", secondIPv6, err) + } + + return []testbench.Layers{firstFragment, secondFragment}, [][]byte{firstIPv6Bytes, secondIPv6Bytes} +} + +func TestIPv6ICMPEchoRequestFragmentReassembly(t *testing.T) { + tests := []struct { + name string + firstPayloadLength uint16 + payload []byte + secondFragmentOffset uint16 + sendFrameOrder []int + }{ + { + name: "reassemble two fragments", + firstPayloadLength: 8, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, + sendFrameOrder: []int{1, 2}, + }, + { + name: "reassemble two fragments in reverse order", + firstPayloadLength: 8, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, + sendFrameOrder: []int{2, 1}, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + conn := (*testbench.Connection)(&ipv6Conn) + defer ipv6Conn.Close(t) + + fragments, _ := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + + for _, i := range test.sendFrameOrder { + conn.SendFrame(t, fragments[i-1]) + } + + gotEchoReply, err := ipv6Conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv6{}, + &testbench.ICMPv6{ + Type: testbench.ICMPv6Type(header.ICMPv6EchoReply), + Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode), + }, + }, time.Second) + if err != nil { + t.Fatalf("didn't receive an ICMPv6 Echo Reply: %s", err) + } + gotPayload, err := gotEchoReply[len(gotEchoReply)-1].ToBytes() + if err != nil { + t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) + } + icmpPayload := gotPayload[header.ICMPv6EchoMinimumSize:] + wantPayload := test.payload + if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { + t.Fatalf("payload mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestIPv6FragmentReassemblyTimeout(t *testing.T) { + type icmpFramePattern struct { + typ header.ICMPv6Type + code header.ICMPv6Code + } + + type icmpReassemblyTimeoutDetail struct { + payloadFragment int // 1: first fragment, 2: second fragnemt. + } + + tests := []struct { + name string + firstPayloadLength uint16 + payload []byte + secondFragmentOffset uint16 + sendFrameOrder []int + replyFilter icmpFramePattern + expectErrorReply bool + expectICMPReassemblyTimeout icmpReassemblyTimeoutDetail + }{ + { + name: "reassembly timeout (first fragment only)", + firstPayloadLength: 8, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, + sendFrameOrder: []int{1}, + replyFilter: icmpFramePattern{ + typ: header.ICMPv6TimeExceeded, + code: header.ICMPv6ReassemblyTimeout, + }, + expectErrorReply: true, + expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ + payloadFragment: 1, + }, + }, + { + name: "reassembly timeout (second fragment only)", + firstPayloadLength: 8, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, + sendFrameOrder: []int{2}, + replyFilter: icmpFramePattern{ + typ: header.ICMPv6TimeExceeded, + code: header.ICMPv6ReassemblyTimeout, + }, + expectErrorReply: false, + }, + { + name: "reassembly timeout (two fragments with a gap)", + firstPayloadLength: 8, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 16) / 8, + sendFrameOrder: []int{1, 2}, + replyFilter: icmpFramePattern{ + typ: header.ICMPv6TimeExceeded, + code: header.ICMPv6ReassemblyTimeout, + }, + expectErrorReply: true, + expectICMPReassemblyTimeout: icmpReassemblyTimeoutDetail{ + payloadFragment: 1, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + conn := (*testbench.Connection)(&ipv6Conn) + defer ipv6Conn.Close(t) + + fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + + for _, i := range test.sendFrameOrder { + conn.SendFrame(t, fragments[i-1]) + } + + gotErrorMessage, err := ipv6Conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv6{}, + &testbench.ICMPv6{ + Type: testbench.ICMPv6Type(test.replyFilter.typ), + Code: testbench.ICMPv6Code(test.replyFilter.code), + }, + }, reassemblyTimeout) + if !test.expectErrorReply { + if err == nil { + t.Fatalf("shouldn't receive an ICMPv6 Error Message with type=%d and code=%d", test.replyFilter.typ, test.replyFilter.code) + } + return + } + if err != nil { + t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) + } + gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() + if err != nil { + t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) + } + icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] + wantPayload := ipv6Bytes[test.expectICMPReassemblyTimeout.payloadFragment-1] + if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { + t.Fatalf("payload mismatch (-want +got):\n%s", diff) + } + }) + } +} + +func TestIPv6FragmentParamProblem(t *testing.T) { + type icmpFramePattern struct { + typ header.ICMPv6Type + code header.ICMPv6Code + } + + type icmpParamProblemDetail struct { + pointer uint32 + payloadFragment int // 1: first fragment, 2: second fragnemt. + } + + tests := []struct { + name string + firstPayloadLength uint16 + payload []byte + secondFragmentOffset uint16 + sendFrameOrder []int + replyFilter icmpFramePattern + expectICMPParamProblem icmpParamProblemDetail + }{ + { + name: "payload size not a multiple of 8", + firstPayloadLength: 9, + payload: []byte(data)[:20], + secondFragmentOffset: (header.ICMPv6EchoMinimumSize + 8) / 8, + sendFrameOrder: []int{1}, + replyFilter: icmpFramePattern{ + typ: header.ICMPv6ParamProblem, + code: header.ICMPv6ErroneousHeader, + }, + expectICMPParamProblem: icmpParamProblemDetail{ + pointer: 4, + payloadFragment: 1, + }, + }, + { + name: "payload length error", + firstPayloadLength: 16, + payload: []byte(data)[:33], + secondFragmentOffset: 65520 / 8, + sendFrameOrder: []int{1, 2}, + replyFilter: icmpFramePattern{ + typ: header.ICMPv6ParamProblem, + code: header.ICMPv6ErroneousHeader, + }, + expectICMPParamProblem: icmpParamProblemDetail{ + pointer: 42, + payloadFragment: 2, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + dut := testbench.NewDUT(t) + defer dut.TearDown() + ipv6Conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{}) + conn := (*testbench.Connection)(&ipv6Conn) + defer ipv6Conn.Close(t) + + fragments, ipv6Bytes := fragmentedICMPEchoRequest(t, conn, test.firstPayloadLength, test.payload, test.secondFragmentOffset) + + for _, i := range test.sendFrameOrder { + conn.SendFrame(t, fragments[i-1]) + } + + gotErrorMessage, err := ipv6Conn.ExpectFrame(t, testbench.Layers{ + &testbench.Ether{}, + &testbench.IPv6{}, + &testbench.ICMPv6{ + Type: testbench.ICMPv6Type(test.replyFilter.typ), + Code: testbench.ICMPv6Code(test.replyFilter.code), + }, + }, time.Second) + if err != nil { + t.Fatalf("didn't receive an ICMPv6 Error Message with type=%d and code=%d: err", test.replyFilter.typ, test.replyFilter.code, err) + } + gotPayload, err := gotErrorMessage[len(gotErrorMessage)-1].ToBytes() + if err != nil { + t.Fatalf("failed to convert ICMPv6 to bytes: %s", err) + } + gotPointer := header.ICMPv6(gotPayload).TypeSpecific() + wantPointer := test.expectICMPParamProblem.pointer + if gotPointer != wantPointer { + t.Fatalf("got pointer = %d, want = %d", gotPointer, wantPointer) + } + icmpPayload := gotPayload[header.ICMPv6ErrorHeaderSize:] + wantPayload := ipv6Bytes[test.expectICMPParamProblem.payloadFragment-1] + if diff := cmp.Diff(wantPayload, icmpPayload); diff != "" { + t.Fatalf("payload mismatch (-want +got):\n%s", diff) + } + }) + } +} -- cgit v1.2.3