summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact')
-rw-r--r--test/packetimpact/dut/posix_server.cc10
-rw-r--r--test/packetimpact/netdevs/BUILD10
-rw-r--r--test/packetimpact/netdevs/netdevs.go17
-rw-r--r--test/packetimpact/netdevs/netdevs_test.go227
-rw-r--r--test/packetimpact/runner/BUILD1
-rw-r--r--test/packetimpact/runner/defs.bzl15
-rw-r--r--test/packetimpact/runner/packetimpact_test.go151
-rw-r--r--test/packetimpact/testbench/BUILD4
-rw-r--r--test/packetimpact/testbench/connections.go195
-rw-r--r--test/packetimpact/testbench/dut.go6
-rw-r--r--test/packetimpact/testbench/layers.go188
-rw-r--r--test/packetimpact/testbench/layers_test.go118
-rw-r--r--test/packetimpact/testbench/testbench.go34
-rw-r--r--test/packetimpact/tests/BUILD41
-rw-r--r--test/packetimpact/tests/icmpv6_param_problem_test.go8
-rw-r--r--test/packetimpact/tests/ipv6_fragment_reassembly_test.go168
-rw-r--r--test/packetimpact/tests/ipv6_unknown_options_action_test.go6
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go139
-rw-r--r--test/packetimpact/tests/udp_discard_mcast_source_addr_test.go92
-rw-r--r--test/packetimpact/tests/udp_recv_mcast_bcast_test.go (renamed from test/packetimpact/tests/udp_recv_multicast_test.go)33
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go110
21 files changed, 1379 insertions, 194 deletions
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
index a1a5c3612..29d4cc6fe 100644
--- a/test/packetimpact/dut/posix_server.cc
+++ b/test/packetimpact/dut/posix_server.cc
@@ -53,7 +53,10 @@
response_in6->set_flowinfo(ntohl(addr_in6->sin6_flowinfo));
response_in6->mutable_addr()->assign(
reinterpret_cast<const char *>(&addr_in6->sin6_addr.s6_addr), 16);
- response_in6->set_scope_id(ntohl(addr_in6->sin6_scope_id));
+ // sin6_scope_id is stored in host byte order.
+ //
+ // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
+ response_in6->set_scope_id(addr_in6->sin6_scope_id);
return ::grpc::Status::OK;
}
}
@@ -89,7 +92,10 @@
addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo());
proto_in6.addr().copy(
reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
- addr_in6->sin6_scope_id = htonl(proto_in6.scope_id());
+ // sin6_scope_id is stored in host byte order.
+ //
+ // https://www.gnu.org/software/libc/manual/html_node/Internet-Address-Formats.html
+ addr_in6->sin6_scope_id = proto_in6.scope_id();
*addr_len = sizeof(*addr_in6);
break;
}
diff --git a/test/packetimpact/netdevs/BUILD b/test/packetimpact/netdevs/BUILD
index 422bb9b0c..8d1193fed 100644
--- a/test/packetimpact/netdevs/BUILD
+++ b/test/packetimpact/netdevs/BUILD
@@ -1,4 +1,4 @@
-load("//tools:defs.bzl", "go_library")
+load("//tools:defs.bzl", "go_library", "go_test")
package(
licenses = ["notice"],
@@ -13,3 +13,11 @@ go_library(
"//pkg/tcpip/header",
],
)
+
+go_test(
+ name = "netdevs_test",
+ size = "small",
+ srcs = ["netdevs_test.go"],
+ library = ":netdevs",
+ deps = ["@com_github_google_go_cmp//cmp:go_default_library"],
+)
diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go
index d2c9cfeaf..eecfe0730 100644
--- a/test/packetimpact/netdevs/netdevs.go
+++ b/test/packetimpact/netdevs/netdevs.go
@@ -19,6 +19,7 @@ import (
"fmt"
"net"
"regexp"
+ "strconv"
"strings"
"gvisor.dev/gvisor/pkg/tcpip"
@@ -27,6 +28,7 @@ import (
// A DeviceInfo represents a network device.
type DeviceInfo struct {
+ ID uint32
MAC net.HardwareAddr
IPv4Addr net.IP
IPv4Net *net.IPNet
@@ -35,7 +37,7 @@ type DeviceInfo struct {
}
var (
- deviceLine = regexp.MustCompile(`^\s*\d+: (\w+)`)
+ deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`)
linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`)
@@ -43,6 +45,11 @@ var (
// ParseDevices parses the output from `ip addr show` into a map from device
// name to information about the device.
+//
+// Note: if multiple IPv6 addresses are assigned to a device, the last address
+// displayed by `ip addr show` will be used. This is fine for packetimpact
+// because we will always only have at most one IPv6 address assigned to each
+// device.
func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
var currentDevice string
var currentInfo DeviceInfo
@@ -52,8 +59,12 @@ func ParseDevices(cmdOutput string) (map[string]DeviceInfo, error) {
if currentDevice != "" {
deviceInfos[currentDevice] = currentInfo
}
- currentInfo = DeviceInfo{}
- currentDevice = m[1]
+ id, err := strconv.ParseUint(m[1], 10, 32)
+ if err != nil {
+ return nil, fmt.Errorf("parsing device ID %s: %w", m[1], err)
+ }
+ currentInfo = DeviceInfo{ID: uint32(id)}
+ currentDevice = m[2]
} else if m := linkLine.FindStringSubmatch(line); m != nil {
mac, err := net.ParseMAC(m[1])
if err != nil {
diff --git a/test/packetimpact/netdevs/netdevs_test.go b/test/packetimpact/netdevs/netdevs_test.go
new file mode 100644
index 000000000..24ad12198
--- /dev/null
+++ b/test/packetimpact/netdevs/netdevs_test.go
@@ -0,0 +1,227 @@
+// 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 netdevs
+
+import (
+ "fmt"
+ "net"
+ "testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+func mustParseMAC(s string) net.HardwareAddr {
+ mac, err := net.ParseMAC(s)
+ if err != nil {
+ panic(fmt.Sprintf("failed to parse test MAC %q: %s", s, err))
+ }
+ return mac
+}
+
+func TestParseDevices(t *testing.T) {
+ for _, v := range []struct {
+ desc string
+ cmdOutput string
+ want map[string]DeviceInfo
+ }{
+ {
+ desc: "v4 and v6",
+ cmdOutput: `
+1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ valid_lft forever preferred_lft forever
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:c0ff:fea8:902/64 scope link tentative
+ valid_lft forever preferred_lft forever
+2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 223.245.225.10/24 brd 223.245.225.255 scope global eth2
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
+ valid_lft forever preferred_lft forever
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "lo": DeviceInfo{
+ ID: 1,
+ MAC: mustParseMAC("00:00:00:00:00:00"),
+ IPv4Addr: net.IPv4(127, 0, 0, 1),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(127, 0, 0, 0),
+ Mask: net.CIDRMask(8, 32),
+ },
+ IPv6Addr: net.ParseIP("::1"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("::1"),
+ Mask: net.CIDRMask(128, 128),
+ },
+ },
+ "eth0": DeviceInfo{
+ ID: 2613,
+ MAC: mustParseMAC("02:42:c0:a8:09:02"),
+ IPv4Addr: net.IPv4(192, 168, 9, 2),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(192, 168, 9, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:c0ff:fea8:902"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ "eth1": DeviceInfo{
+ ID: 2617,
+ MAC: mustParseMAC("02:42:da:33:13:0a"),
+ IPv4Addr: net.IPv4(218, 51, 19, 10),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(218, 51, 19, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:daff:fe33:130a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ "eth2": DeviceInfo{
+ ID: 2615,
+ MAC: mustParseMAC("02:42:df:f5:e1:0a"),
+ IPv4Addr: net.IPv4(223, 245, 225, 10),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(223, 245, 225, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ },
+ },
+ {
+ desc: "v4 only",
+ cmdOutput: `
+2613: eth0@if2614: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:c0:a8:09:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 192.168.9.2/24 brd 192.168.9.255 scope global eth0
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "eth0": DeviceInfo{
+ ID: 2613,
+ MAC: mustParseMAC("02:42:c0:a8:09:02"),
+ IPv4Addr: net.IPv4(192, 168, 9, 2),
+ IPv4Net: &net.IPNet{
+ IP: net.IPv4(192, 168, 9, 0),
+ Mask: net.CIDRMask(24, 32),
+ },
+ },
+ },
+ },
+ {
+ desc: "v6 only",
+ cmdOutput: `
+2615: eth2@if2616: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:df:f5:e1:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet6 fe80::42:dfff:fef5:e10a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ want: map[string]DeviceInfo{
+ "eth2": DeviceInfo{
+ ID: 2615,
+ MAC: mustParseMAC("02:42:df:f5:e1:0a"),
+ IPv6Addr: net.ParseIP("fe80::42:dfff:fef5:e10a"),
+ IPv6Net: &net.IPNet{
+ IP: net.ParseIP("fe80::"),
+ Mask: net.CIDRMask(64, 128),
+ },
+ },
+ },
+ },
+ } {
+ t.Run(v.desc, func(t *testing.T) {
+ got, err := ParseDevices(v.cmdOutput)
+ if err != nil {
+ t.Errorf("ParseDevices(\n%s\n) got unexpected error: %s", v.cmdOutput, err)
+ }
+ if diff := cmp.Diff(v.want, got); diff != "" {
+ t.Errorf("ParseDevices(\n%s\n) got output diff (-want, +got):\n%s", v.cmdOutput, diff)
+ }
+ })
+ }
+}
+
+func TestParseDevicesErrors(t *testing.T) {
+ for _, v := range []struct {
+ desc string
+ cmdOutput string
+ }{
+ {
+ desc: "invalid MAC addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a:ffffffff brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid v4 addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 1234.4321.424242.0/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid v6 addr",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10/24 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80:ffffffff::42:daff:fe33:130a/64 scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ {
+ desc: "invalid CIDR missing prefixlen",
+ cmdOutput: `
+2617: eth1@if2618: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
+ link/ether 02:42:da:33:13:0a brd ff:ff:ff:ff:ff:ff link-netnsid 0
+ inet 218.51.19.10 brd 218.51.19.255 scope global eth1
+ valid_lft forever preferred_lft forever
+ inet6 fe80::42:daff:fe33:130a scope link tentative
+ valid_lft forever preferred_lft forever`,
+ },
+ } {
+ t.Run(v.desc, func(t *testing.T) {
+ if _, err := ParseDevices(v.cmdOutput); err == nil {
+ t.Errorf("ParseDevices(\n%s\n) succeeded unexpectedly, want error", v.cmdOutput)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/runner/BUILD b/test/packetimpact/runner/BUILD
index 0b68a760a..bad4f0183 100644
--- a/test/packetimpact/runner/BUILD
+++ b/test/packetimpact/runner/BUILD
@@ -16,5 +16,6 @@ go_test(
deps = [
"//pkg/test/dockerutil",
"//test/packetimpact/netdevs",
+ "@com_github_docker_docker//api/types/mount:go_default_library",
],
)
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 77cdfea12..79b3c9162 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -55,7 +55,11 @@ _packetimpact_test = rule(
implementation = _packetimpact_test_impl,
)
-PACKETIMPACT_TAGS = ["local", "manual"]
+PACKETIMPACT_TAGS = [
+ "local",
+ "manual",
+ "packetimpact",
+]
def packetimpact_linux_test(
name,
@@ -75,7 +79,7 @@ def packetimpact_linux_test(
name = name + "_linux_test",
testbench_binary = testbench_binary,
flags = ["--dut_platform", "linux"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
+ tags = PACKETIMPACT_TAGS,
**kwargs
)
@@ -101,7 +105,7 @@ def packetimpact_netstack_test(
# This is the default runtime unless
# "--test_arg=--runtime=OTHER_RUNTIME" is used to override the value.
flags = ["--dut_platform", "netstack", "--runtime=runsc-d"] + expect_failure_flag,
- tags = PACKETIMPACT_TAGS + ["packetimpact"],
+ tags = PACKETIMPACT_TAGS,
**kwargs
)
@@ -121,7 +125,10 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_linux_failure
name = testbench_binary,
size = size,
pure = pure,
- tags = PACKETIMPACT_TAGS,
+ tags = [
+ "local",
+ "manual",
+ ],
**kwargs
)
packetimpact_linux_test(
diff --git a/test/packetimpact/runner/packetimpact_test.go b/test/packetimpact/runner/packetimpact_test.go
index c0a2620de..1a0221893 100644
--- a/test/packetimpact/runner/packetimpact_test.go
+++ b/test/packetimpact/runner/packetimpact_test.go
@@ -16,6 +16,7 @@
package packetimpact_test
import (
+ "context"
"flag"
"fmt"
"io/ioutil"
@@ -29,6 +30,7 @@ import (
"testing"
"time"
+ "github.com/docker/docker/api/types/mount"
"gvisor.dev/gvisor/pkg/test/dockerutil"
"gvisor.dev/gvisor/test/packetimpact/netdevs"
)
@@ -94,15 +96,16 @@ func TestOne(t *testing.T) {
}
}
dockerutil.EnsureSupportedDockerVersion()
+ ctx := context.Background()
// Create the networks needed for the test. One control network is needed for
// the gRPC control packets and one test network on which to transmit the test
// packets.
- ctrlNet := dockerutil.NewDockerNetwork(logger("ctrlNet"))
- testNet := dockerutil.NewDockerNetwork(logger("testNet"))
- for _, dn := range []*dockerutil.DockerNetwork{ctrlNet, testNet} {
+ ctrlNet := dockerutil.NewNetwork(ctx, logger("ctrlNet"))
+ testNet := dockerutil.NewNetwork(ctx, logger("testNet"))
+ for _, dn := range []*dockerutil.Network{ctrlNet, testNet} {
for {
- if err := createDockerNetwork(dn); err != nil {
+ if err := createDockerNetwork(ctx, dn); err != nil {
t.Log("creating docker network:", err)
const wait = 100 * time.Millisecond
t.Logf("sleeping %s and will try creating docker network again", wait)
@@ -113,11 +116,19 @@ func TestOne(t *testing.T) {
}
break
}
- defer func(dn *dockerutil.DockerNetwork) {
- if err := dn.Cleanup(); err != nil {
+ defer func(dn *dockerutil.Network) {
+ if err := dn.Cleanup(ctx); err != nil {
t.Errorf("unable to cleanup container %s: %s", dn.Name, err)
}
}(dn)
+ // Sanity check.
+ inspect, err := dn.Inspect(ctx)
+ if err != nil {
+ t.Fatalf("failed to inspect network %s: %v", dn.Name, err)
+ } else if inspect.Name != dn.Name {
+ t.Fatalf("name mismatch for network want: %s got: %s", dn.Name, inspect.Name)
+ }
+
}
tmpDir, err := ioutil.TempDir("", "container-output")
@@ -128,42 +139,51 @@ func TestOne(t *testing.T) {
const testOutputDir = "/tmp/testoutput"
- runOpts := dockerutil.RunOpts{
- Image: "packetimpact",
- CapAdd: []string{"NET_ADMIN"},
- Extra: []string{"--sysctl", "net.ipv6.conf.all.disable_ipv6=0", "--rm", "-v", tmpDir + ":" + testOutputDir},
- Foreground: true,
- }
-
// Create the Docker container for the DUT.
- dut := dockerutil.MakeDocker(logger("dut"))
+ dut := dockerutil.MakeContainer(ctx, logger("dut"))
if *dutPlatform == "linux" {
dut.Runtime = ""
}
+ runOpts := dockerutil.RunOpts{
+ Image: "packetimpact",
+ CapAdd: []string{"NET_ADMIN"},
+ Mounts: []mount.Mount{mount.Mount{
+ Type: mount.TypeBind,
+ Source: tmpDir,
+ Target: testOutputDir,
+ ReadOnly: false,
+ }},
+ }
+
const containerPosixServerBinary = "/packetimpact/posix_server"
dut.CopyFiles(&runOpts, "/packetimpact", "/test/packetimpact/dut/posix_server")
- if err := dut.Create(runOpts, containerPosixServerBinary, "--ip=0.0.0.0", "--port="+ctrlPort); err != nil {
- t.Fatalf("unable to create container %s: %s", dut.Name, err)
+ conf, hostconf, _ := dut.ConfigsFrom(runOpts, containerPosixServerBinary, "--ip=0.0.0.0", "--port="+ctrlPort)
+ hostconf.AutoRemove = true
+ hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"}
+
+ if err := dut.CreateFrom(ctx, conf, hostconf, nil); err != nil {
+ t.Fatalf("unable to create container %s: %v", dut.Name, err)
}
- defer dut.CleanUp()
+
+ defer dut.CleanUp(ctx)
// Add ctrlNet as eth1 and testNet as eth2.
const testNetDev = "eth2"
- if err := addNetworks(dut, dutAddr, []*dockerutil.DockerNetwork{ctrlNet, testNet}); err != nil {
+ if err := addNetworks(ctx, dut, dutAddr, []*dockerutil.Network{ctrlNet, testNet}); err != nil {
t.Fatal(err)
}
- if err := dut.Start(); err != nil {
+ if err := dut.Start(ctx); err != nil {
t.Fatalf("unable to start container %s: %s", dut.Name, err)
}
- if _, err := dut.WaitForOutput("Server listening.*\n", 60*time.Second); err != nil {
+ if _, err := dut.WaitForOutput(ctx, "Server listening.*\n", 60*time.Second); err != nil {
t.Fatalf("%s on container %s never listened: %s", containerPosixServerBinary, dut.Name, err)
}
- dutTestDevice, dutDeviceInfo, err := deviceByIP(dut, addressInSubnet(dutAddr, *testNet.Subnet))
+ dutTestDevice, dutDeviceInfo, err := deviceByIP(ctx, dut, addressInSubnet(dutAddr, *testNet.Subnet))
if err != nil {
t.Fatal(err)
}
@@ -173,11 +193,11 @@ func TestOne(t *testing.T) {
// Netstack as DUT doesn't assign IPv6 addresses automatically so do it if
// needed.
if remoteIPv6 == nil {
- if _, err := dut.Exec(dockerutil.RunOpts{}, "ip", "addr", "add", netdevs.MACToIP(remoteMAC).String(), "scope", "link", "dev", dutTestDevice); err != nil {
+ if _, err := dut.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "add", netdevs.MACToIP(remoteMAC).String(), "scope", "link", "dev", dutTestDevice); err != nil {
t.Fatalf("unable to ip addr add on container %s: %s", dut.Name, err)
}
// Now try again, to make sure that it worked.
- _, dutDeviceInfo, err = deviceByIP(dut, addressInSubnet(dutAddr, *testNet.Subnet))
+ _, dutDeviceInfo, err = deviceByIP(ctx, dut, addressInSubnet(dutAddr, *testNet.Subnet))
if err != nil {
t.Fatal(err)
}
@@ -188,16 +208,20 @@ func TestOne(t *testing.T) {
}
// Create the Docker container for the testbench.
- testbench := dockerutil.MakeDocker(logger("testbench"))
+ testbench := dockerutil.MakeContainer(ctx, logger("testbench"))
testbench.Runtime = "" // The testbench always runs on Linux.
tbb := path.Base(*testbenchBinary)
containerTestbenchBinary := "/packetimpact/" + tbb
runOpts = dockerutil.RunOpts{
- Image: "packetimpact",
- CapAdd: []string{"NET_ADMIN"},
- Extra: []string{"--sysctl", "net.ipv6.conf.all.disable_ipv6=0", "--rm", "-v", tmpDir + ":" + testOutputDir},
- Foreground: true,
+ Image: "packetimpact",
+ CapAdd: []string{"NET_ADMIN"},
+ Mounts: []mount.Mount{mount.Mount{
+ Type: mount.TypeBind,
+ Source: tmpDir,
+ Target: testOutputDir,
+ ReadOnly: false,
+ }},
}
testbench.CopyFiles(&runOpts, "/packetimpact", "/test/packetimpact/tests/"+tbb)
@@ -227,39 +251,42 @@ func TestOne(t *testing.T) {
}
}()
- if err := testbench.Create(runOpts, snifferArgs...); err != nil {
+ conf, hostconf, _ = testbench.ConfigsFrom(runOpts, snifferArgs...)
+ hostconf.AutoRemove = true
+ hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"}
+
+ if err := testbench.CreateFrom(ctx, conf, hostconf, nil); err != nil {
t.Fatalf("unable to create container %s: %s", testbench.Name, err)
}
- defer testbench.CleanUp()
+ defer testbench.CleanUp(ctx)
// Add ctrlNet as eth1 and testNet as eth2.
- if err := addNetworks(testbench, testbenchAddr, []*dockerutil.DockerNetwork{ctrlNet, testNet}); err != nil {
+ if err := addNetworks(ctx, testbench, testbenchAddr, []*dockerutil.Network{ctrlNet, testNet}); err != nil {
t.Fatal(err)
}
- if err := testbench.Start(); err != nil {
+ if err := testbench.Start(ctx); err != nil {
t.Fatalf("unable to start container %s: %s", testbench.Name, err)
}
// Kill so that it will flush output.
defer func() {
- // Wait 1 second before killing tcpdump to give it time to flush
- // any packets. On linux tests killing it immediately can
- // sometimes result in partial pcaps.
time.Sleep(1 * time.Second)
- testbench.Exec(dockerutil.RunOpts{}, "killall", snifferArgs[0])
+ testbench.Exec(ctx, dockerutil.ExecOpts{}, "killall", snifferArgs[0])
}()
- if _, err := testbench.WaitForOutput(snifferRegex, 60*time.Second); err != nil {
+ if _, err := testbench.WaitForOutput(ctx, snifferRegex, 60*time.Second); err != nil {
t.Fatalf("sniffer on %s never listened: %s", dut.Name, err)
}
// Because the Linux kernel receives the SYN-ACK but didn't send the SYN it
- // will issue a RST. To prevent this IPtables can be used to filter out all
+ // will issue an RST. To prevent this IPtables can be used to filter out all
// incoming packets. The raw socket that packetimpact tests use will still see
// everything.
- if _, err := testbench.Exec(dockerutil.RunOpts{}, "iptables", "-A", "INPUT", "-i", testNetDev, "-j", "DROP"); err != nil {
- t.Fatalf("unable to Exec iptables on container %s: %s", testbench.Name, err)
+ for _, bin := range []string{"iptables", "ip6tables"} {
+ if logs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, bin, "-A", "INPUT", "-i", testNetDev, "-p", "tcp", "-j", "DROP"); err != nil {
+ t.Fatalf("unable to Exec %s on container %s: %s, logs from testbench:\n%s", bin, testbench.Name, err, logs)
+ }
}
// FIXME(b/156449515): Some piece of the system has a race. The old
@@ -279,23 +306,41 @@ func TestOne(t *testing.T) {
"--local_ipv4", addressInSubnet(testbenchAddr, *testNet.Subnet).String(),
"--remote_ipv6", remoteIPv6.String(),
"--remote_mac", remoteMAC.String(),
+ "--remote_interface_id", fmt.Sprintf("%d", dutDeviceInfo.ID),
"--device", testNetDev,
"--dut_type", *dutPlatform,
)
- _, err = testbench.Exec(dockerutil.RunOpts{}, testArgs...)
- if !*expectFailure && err != nil {
- t.Fatal("test failed:", err)
- }
- if *expectFailure && err == nil {
- t.Fatal("test failure expected but the test succeeded, enable the test and mark the corresponding bug as fixed")
+ testbenchLogs, err := testbench.Exec(ctx, dockerutil.ExecOpts{}, testArgs...)
+ if (err != nil) != *expectFailure {
+ var dutLogs string
+ if logs, err := dut.Logs(ctx); err != nil {
+ dutLogs = fmt.Sprintf("failed to fetch DUT logs: %s", err)
+ } else {
+ dutLogs = logs
+ }
+
+ t.Errorf(`test error: %v, expect failure: %t
+
+====== Begin of DUT Logs ======
+
+%s
+
+====== End of DUT Logs ======
+
+====== Begin of Testbench Logs ======
+
+%s
+
+====== End of Testbench Logs ======`,
+ err, *expectFailure, dutLogs, testbenchLogs)
}
}
-func addNetworks(d *dockerutil.Docker, addr net.IP, networks []*dockerutil.DockerNetwork) error {
+func addNetworks(ctx context.Context, d *dockerutil.Container, addr net.IP, networks []*dockerutil.Network) error {
for _, dn := range networks {
ip := addressInSubnet(addr, *dn.Subnet)
// Connect to the network with the specified IP address.
- if err := dn.Connect(d, "--ip", ip.String()); err != nil {
+ if err := dn.Connect(ctx, d, ip.String(), ""); err != nil {
return fmt.Errorf("unable to connect container %s to network %s: %w", d.Name, dn.Name, err)
}
}
@@ -313,9 +358,9 @@ func addressInSubnet(addr net.IP, subnet net.IPNet) net.IP {
return net.IP(octets)
}
-// makeDockerNetwork makes a randomly-named network that will start with the
+// createDockerNetwork makes a randomly-named network that will start with the
// namePrefix. The network will be a random /24 subnet.
-func createDockerNetwork(n *dockerutil.DockerNetwork) error {
+func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error {
randSource := rand.NewSource(time.Now().UnixNano())
r1 := rand.New(randSource)
// Class C, 192.0.0.0 to 223.255.255.255, transitionally has mask 24.
@@ -324,12 +369,12 @@ func createDockerNetwork(n *dockerutil.DockerNetwork) error {
IP: ip,
Mask: ip.DefaultMask(),
}
- return n.Create()
+ return n.Create(ctx)
}
// deviceByIP finds a deviceInfo and device name from an IP address.
-func deviceByIP(d *dockerutil.Docker, ip net.IP) (string, netdevs.DeviceInfo, error) {
- out, err := d.Exec(dockerutil.RunOpts{}, "ip", "addr", "show")
+func deviceByIP(ctx context.Context, d *dockerutil.Container, ip net.IP) (string, netdevs.DeviceInfo, error) {
+ out, err := d.Exec(ctx, dockerutil.ExecOpts{}, "ip", "addr", "show")
if err != nil {
return "", netdevs.DeviceInfo{}, fmt.Errorf("listing devices on %s container: %w", d.Name, err)
}
diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD
index d19ec07d4..5a0ee1367 100644
--- a/test/packetimpact/testbench/BUILD
+++ b/test/packetimpact/testbench/BUILD
@@ -23,8 +23,8 @@ go_library(
"//pkg/usermem",
"//test/packetimpact/netdevs",
"//test/packetimpact/proto:posix_server_go_proto",
- "@com_github_google_go-cmp//cmp:go_default_library",
- "@com_github_google_go-cmp//cmp/cmpopts:go_default_library",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@com_github_google_go_cmp//cmp/cmpopts:go_default_library",
"@com_github_mohae_deepcopy//:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//keepalive:go_default_library",
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 8b4a4d905..87ce58c24 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -41,16 +41,19 @@ func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
return 0, fmt.Errorf("sockaddr type %T does not contain port", sa)
}
-// pickPort makes a new socket and returns the socket FD and port. The domain should be AF_INET or AF_INET6. The caller must close the FD when done with
+// pickPort makes a new socket and returns the socket FD and port. The domain
+// should be AF_INET or AF_INET6. The caller must close the FD when done with
// the port if there is no error.
-func pickPort(domain, typ int) (int, uint16, error) {
- fd, err := unix.Socket(domain, typ, 0)
+func pickPort(domain, typ int) (fd int, port uint16, err error) {
+ fd, err = unix.Socket(domain, typ, 0)
if err != nil {
- return -1, 0, err
+ return -1, 0, fmt.Errorf("creating socket: %w", err)
}
defer func() {
if err != nil {
- err = multierr.Append(err, unix.Close(fd))
+ if cerr := unix.Close(fd); cerr != nil {
+ err = multierr.Append(err, fmt.Errorf("failed to close socket %d: %w", fd, cerr))
+ }
}
}()
var sa unix.Sockaddr
@@ -60,22 +63,22 @@ func pickPort(domain, typ int) (int, uint16, error) {
copy(sa4.Addr[:], net.ParseIP(LocalIPv4).To4())
sa = &sa4
case unix.AF_INET6:
- var sa6 unix.SockaddrInet6
+ sa6 := unix.SockaddrInet6{ZoneId: uint32(LocalInterfaceID)}
copy(sa6.Addr[:], net.ParseIP(LocalIPv6).To16())
sa = &sa6
default:
return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
}
if err = unix.Bind(fd, sa); err != nil {
- return -1, 0, err
+ return -1, 0, fmt.Errorf("binding to %+v: %w", sa, err)
}
sa, err = unix.Getsockname(fd)
if err != nil {
- return -1, 0, err
+ return -1, 0, fmt.Errorf("Getsocketname(%d): %w", fd, err)
}
- port, err := portFromSockaddr(sa)
+ port, err = portFromSockaddr(sa)
if err != nil {
- return -1, 0, err
+ return -1, 0, fmt.Errorf("extracting port from socket address %+v: %w", sa, err)
}
return fd, port, nil
}
@@ -378,7 +381,7 @@ var _ layerState = (*udpState)(nil)
func newUDPState(domain int, out, in UDP) (*udpState, error) {
portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("picking port: %w", err)
}
s := udpState{
out: UDP{SrcPort: &localPort},
@@ -916,14 +919,14 @@ func (conn *UDPIPv4) SendIP(ip IPv4, udp UDP, additionalLayers ...Layer) {
func (conn *UDPIPv4) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
conn.t.Helper()
layer, err := (*Connection)(conn).Expect(&udp, timeout)
- if layer == nil {
+ if err != nil {
return nil, err
}
gotUDP, ok := layer.(*UDP)
if !ok {
conn.t.Fatalf("expected %s to be UDP", layer)
}
- return gotUDP, err
+ return gotUDP, nil
}
// ExpectData is a convenient method that expects a Layer and the Layer after
@@ -948,3 +951,169 @@ func (conn *UDPIPv4) Close() {
func (conn *UDPIPv4) Drain() {
conn.sniffer.Drain()
}
+
+// UDPIPv6 maintains the state for all the layers in a UDP/IPv6 connection.
+type UDPIPv6 Connection
+
+// NewUDPIPv6 creates a new UDPIPv6 connection with reasonable defaults.
+func NewUDPIPv6(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv6 {
+ etherState, err := newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make etherState: %s", err)
+ }
+ ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ if err != nil {
+ t.Fatalf("can't make IPv6State: %s", err)
+ }
+ udpState, err := newUDPState(unix.AF_INET6, outgoingUDP, incomingUDP)
+ if err != nil {
+ t.Fatalf("can't make udpState: %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 UDPIPv6{
+ layerStates: []layerState{etherState, ipv6State, udpState},
+ injector: injector,
+ sniffer: sniffer,
+ t: t,
+ }
+}
+
+func (conn *UDPIPv6) udpState() *udpState {
+ state, ok := conn.layerStates[2].(*udpState)
+ if !ok {
+ conn.t.Fatalf("got transport-layer state type=%T, expected udpState", conn.layerStates[2])
+ }
+ return state
+}
+
+func (conn *UDPIPv6) ipv6State() *ipv6State {
+ state, ok := conn.layerStates[1].(*ipv6State)
+ if !ok {
+ conn.t.Fatalf("got network-layer state type=%T, expected ipv6State", conn.layerStates[1])
+ }
+ return state
+}
+
+// LocalAddr gets the local socket address of this connection.
+func (conn *UDPIPv6) LocalAddr() *unix.SockaddrInet6 {
+ sa := &unix.SockaddrInet6{
+ Port: int(*conn.udpState().out.SrcPort),
+ // Local address is in perspective to the remote host, so it's scoped to the
+ // ID of the remote interface.
+ ZoneId: uint32(RemoteInterfaceID),
+ }
+ copy(sa.Addr[:], *conn.ipv6State().out.SrcAddr)
+ return sa
+}
+
+// Send sends a packet with reasonable defaults, potentially overriding the UDP
+// layer and adding additionLayers.
+func (conn *UDPIPv6) Send(udp UDP, additionalLayers ...Layer) {
+ (*Connection)(conn).send(Layers{&udp}, additionalLayers...)
+}
+
+// SendIPv6 sends a packet with reasonable defaults, potentially overriding the
+// UDP and IPv6 headers and adding additionLayers.
+func (conn *UDPIPv6) SendIPv6(ip IPv6, udp UDP, additionalLayers ...Layer) {
+ (*Connection)(conn).send(Layers{&ip, &udp}, additionalLayers...)
+}
+
+// Expect expects a frame with the UDP layer matching the provided UDP within
+// the timeout specified. If it doesn't arrive in time, an error is returned.
+func (conn *UDPIPv6) Expect(udp UDP, timeout time.Duration) (*UDP, error) {
+ conn.t.Helper()
+ layer, err := (*Connection)(conn).Expect(&udp, timeout)
+ if err != nil {
+ return nil, err
+ }
+ gotUDP, ok := layer.(*UDP)
+ if !ok {
+ conn.t.Fatalf("expected %s to be UDP", layer)
+ }
+ return gotUDP, nil
+}
+
+// ExpectData is a convenient method that expects a Layer and the Layer after
+// it. If it doens't arrive in time, it returns nil.
+func (conn *UDPIPv6) ExpectData(udp UDP, payload Payload, timeout time.Duration) (Layers, error) {
+ conn.t.Helper()
+ expected := make([]Layer, len(conn.layerStates))
+ expected[len(expected)-1] = &udp
+ if payload.length() != 0 {
+ expected = append(expected, &payload)
+ }
+ return (*Connection)(conn).ExpectFrame(expected, timeout)
+}
+
+// Close frees associated resources held by the UDPIPv6 connection.
+func (conn *UDPIPv6) Close() {
+ (*Connection)(conn).Close()
+}
+
+// Drain drains the sniffer's receive buffer by receiving packets until there's
+// nothing else to receive.
+func (conn *UDPIPv6) Drain() {
+ conn.sniffer.Drain()
+}
+
+// TCPIPv6 maintains the state for all the layers in a TCP/IPv6 connection.
+type TCPIPv6 Connection
+
+// NewTCPIPv6 creates a new TCPIPv6 connection with reasonable defaults.
+func NewTCPIPv6(t *testing.T, outgoingTCP, incomingTCP TCP) TCPIPv6 {
+ etherState, err := newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make etherState: %s", err)
+ }
+ ipv6State, err := newIPv6State(IPv6{}, IPv6{})
+ if err != nil {
+ t.Fatalf("can't make ipv6State: %s", err)
+ }
+ tcpState, err := newTCPState(unix.AF_INET6, outgoingTCP, incomingTCP)
+ if err != nil {
+ t.Fatalf("can't make tcpState: %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 TCPIPv6{
+ layerStates: []layerState{etherState, ipv6State, tcpState},
+ injector: injector,
+ sniffer: sniffer,
+ t: t,
+ }
+}
+
+func (conn *TCPIPv6) SrcPort() uint16 {
+ state := conn.layerStates[2].(*tcpState)
+ return *state.out.SrcPort
+}
+
+// ExpectData is a convenient method that expects a Layer and the Layer after
+// it. If it doens't arrive in time, it returns nil.
+func (conn *TCPIPv6) ExpectData(tcp *TCP, payload *Payload, timeout time.Duration) (Layers, error) {
+ expected := make([]Layer, len(conn.layerStates))
+ expected[len(expected)-1] = tcp
+ if payload != nil {
+ expected = append(expected, payload)
+ }
+ return (*Connection)(conn).ExpectFrame(expected, timeout)
+}
+
+// Close frees associated resources held by the TCPIPv6 connection.
+func (conn *TCPIPv6) Close() {
+ (*Connection)(conn).Close()
+}
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 2a2afecb5..51be13759 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -87,7 +87,7 @@ func (dut *DUT) sockaddrToProto(sa unix.Sockaddr) *pb.Sockaddr {
},
}
}
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
+ dut.t.Fatalf("can't parse Sockaddr struct: %+v", sa)
return nil
}
@@ -106,8 +106,9 @@ func (dut *DUT) protoToSockaddr(sa *pb.Sockaddr) unix.Sockaddr {
ZoneId: s.In6.GetScopeId(),
}
copy(ret.Addr[:], s.In6.GetAddr())
+ return &ret
}
- dut.t.Fatalf("can't parse Sockaddr: %+v", sa)
+ dut.t.Fatalf("can't parse Sockaddr proto: %+v", sa)
return nil
}
@@ -126,6 +127,7 @@ func (dut *DUT) CreateBoundSocket(typ, proto int32, addr net.IP) (int32, uint16)
fd = dut.Socket(unix.AF_INET6, typ, proto)
sa := unix.SockaddrInet6{}
copy(sa.Addr[:], addr.To16())
+ sa.ZoneId = uint32(RemoteInterfaceID)
dut.Bind(fd, &sa)
} else {
dut.t.Fatalf("unknown ip addr type for remoteIP")
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index a8121b0da..24aa46cce 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -15,6 +15,7 @@
package testbench
import (
+ "encoding/binary"
"encoding/hex"
"fmt"
"reflect"
@@ -470,21 +471,11 @@ func (l *IPv6) ToBytes() ([]byte, error) {
if l.NextHeader != nil {
fields.NextHeader = *l.NextHeader
} else {
- switch n := l.next().(type) {
- case *TCP:
- fields.NextHeader = uint8(header.TCPProtocolNumber)
- case *UDP:
- fields.NextHeader = uint8(header.UDPProtocolNumber)
- case *ICMPv6:
- fields.NextHeader = uint8(header.ICMPv6ProtocolNumber)
- case *IPv6HopByHopOptionsExtHdr:
- fields.NextHeader = uint8(header.IPv6HopByHopOptionsExtHdrIdentifier)
- case *IPv6DestinationOptionsExtHdr:
- fields.NextHeader = uint8(header.IPv6DestinationOptionsExtHdrIdentifier)
- default:
- // TODO(b/150301488): Support more protocols as needed.
- return nil, fmt.Errorf("ToBytes can't deduce the IPv6 header's next protocol: %#v", n)
+ nh, err := nextHeaderByLayer(l.next())
+ if err != nil {
+ return nil, err
}
+ fields.NextHeader = nh
}
if l.HopLimit != nil {
fields.HopLimit = *l.HopLimit
@@ -514,6 +505,8 @@ func nextIPv6PayloadParser(nextHeader uint8) layerParser {
return parseIPv6HopByHopOptionsExtHdr
case header.IPv6DestinationOptionsExtHdrIdentifier:
return parseIPv6DestinationOptionsExtHdr
+ case header.IPv6FragmentExtHdrIdentifier:
+ return parseIPv6FragmentExtHdr
}
return parsePayload
}
@@ -566,14 +559,56 @@ type IPv6DestinationOptionsExtHdr struct {
Options []byte
}
+// IPv6FragmentExtHdr can construct and match an IPv6 Fragment Extension Header.
+type IPv6FragmentExtHdr struct {
+ LayerBase
+ NextHeader *header.IPv6ExtensionHeaderIdentifier
+ FragmentOffset *uint16
+ MoreFragments *bool
+ Identification *uint32
+}
+
+// nextHeaderByLayer finds the correct next header protocol value for layer l.
+func nextHeaderByLayer(l Layer) (uint8, error) {
+ if l == nil {
+ return uint8(header.IPv6NoNextHeaderIdentifier), nil
+ }
+ switch l.(type) {
+ case *TCP:
+ return uint8(header.TCPProtocolNumber), nil
+ case *UDP:
+ return uint8(header.UDPProtocolNumber), nil
+ case *ICMPv6:
+ return uint8(header.ICMPv6ProtocolNumber), nil
+ case *Payload:
+ return uint8(header.IPv6NoNextHeaderIdentifier), nil
+ case *IPv6HopByHopOptionsExtHdr:
+ return uint8(header.IPv6HopByHopOptionsExtHdrIdentifier), nil
+ case *IPv6DestinationOptionsExtHdr:
+ return uint8(header.IPv6DestinationOptionsExtHdrIdentifier), nil
+ case *IPv6FragmentExtHdr:
+ return uint8(header.IPv6FragmentExtHdrIdentifier), nil
+ default:
+ // TODO(b/161005083): Support more protocols as needed.
+ return 0, fmt.Errorf("failed to deduce the IPv6 header's next protocol: %T", l)
+ }
+}
+
// ipv6OptionsExtHdrToBytes serializes an options extension header into bytes.
-func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier, options []byte) []byte {
+func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier, nextLayer Layer, options []byte) ([]byte, error) {
length := len(options) + 2
+ if length%8 != 0 {
+ return nil, fmt.Errorf("IPv6 extension headers must be a multiple of 8 octets long, but the length given: %d, options: %s", length, hex.Dump(options))
+ }
bytes := make([]byte, length)
- if nextHeader == nil {
- bytes[0] = byte(header.IPv6NoNextHeaderIdentifier)
- } else {
+ if nextHeader != nil {
bytes[0] = byte(*nextHeader)
+ } else {
+ nh, err := nextHeaderByLayer(nextLayer)
+ if err != nil {
+ return nil, err
+ }
+ bytes[0] = nh
}
// ExtHdrLen field is the length of the extension header
// in 8-octet unit, ignoring the first 8 octets.
@@ -581,7 +616,7 @@ func ipv6OptionsExtHdrToBytes(nextHeader *header.IPv6ExtensionHeaderIdentifier,
// https://tools.ietf.org/html/rfc2460#section-4.6
bytes[1] = uint8((length - 8) / 8)
copy(bytes[2:], options)
- return bytes
+ return bytes, nil
}
// IPv6ExtHdrIdent is a helper routine that allocates a new
@@ -591,14 +626,45 @@ func IPv6ExtHdrIdent(id header.IPv6ExtensionHeaderIdentifier) *header.IPv6Extens
return &id
}
-// ToBytes implements Layer.ToBytes
+// ToBytes implements Layer.ToBytes.
func (l *IPv6HopByHopOptionsExtHdr) ToBytes() ([]byte, error) {
- return ipv6OptionsExtHdrToBytes(l.NextHeader, l.Options), nil
+ return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
}
-// ToBytes implements Layer.ToBytes
+// ToBytes implements Layer.ToBytes.
func (l *IPv6DestinationOptionsExtHdr) ToBytes() ([]byte, error) {
- return ipv6OptionsExtHdrToBytes(l.NextHeader, l.Options), nil
+ return ipv6OptionsExtHdrToBytes(l.NextHeader, l.next(), l.Options)
+}
+
+// ToBytes implements Layer.ToBytes.
+func (l *IPv6FragmentExtHdr) ToBytes() ([]byte, error) {
+ var offset, mflag uint16
+ var ident uint32
+ bytes := make([]byte, header.IPv6FragmentExtHdrLength)
+ if l.NextHeader != nil {
+ bytes[0] = byte(*l.NextHeader)
+ } else {
+ nh, err := nextHeaderByLayer(l.next())
+ if err != nil {
+ return nil, err
+ }
+ bytes[0] = nh
+ }
+ bytes[1] = 0 // reserved
+ if l.MoreFragments != nil && *l.MoreFragments {
+ mflag = 1
+ }
+ if l.FragmentOffset != nil {
+ offset = *l.FragmentOffset
+ }
+ if l.Identification != nil {
+ ident = *l.Identification
+ }
+ offsetAndMflag := offset<<3 | mflag
+ binary.BigEndian.PutUint16(bytes[2:], offsetAndMflag)
+ binary.BigEndian.PutUint32(bytes[4:], ident)
+
+ return bytes, nil
}
// parseIPv6ExtHdr parses an IPv6 extension header and returns the NextHeader
@@ -631,6 +697,26 @@ func parseIPv6DestinationOptionsExtHdr(b []byte) (Layer, layerParser) {
return &IPv6DestinationOptionsExtHdr{NextHeader: &nextHeader, Options: options}, nextParser
}
+// Bool is a helper routine that allocates a new
+// bool value to store v and returns a pointer to it.
+func Bool(v bool) *bool {
+ return &v
+}
+
+// parseIPv6FragmentExtHdr parses the bytes assuming that they start
+// with an IPv6 Fragment Extension Header.
+func parseIPv6FragmentExtHdr(b []byte) (Layer, layerParser) {
+ nextHeader := b[0]
+ var extHdr header.IPv6FragmentExtHdr
+ copy(extHdr[:], b[2:])
+ return &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6ExtensionHeaderIdentifier(nextHeader)),
+ FragmentOffset: Uint16(extHdr.FragmentOffset()),
+ MoreFragments: Bool(extHdr.More()),
+ Identification: Uint32(extHdr.ID()),
+ }, nextIPv6PayloadParser(nextHeader)
+}
+
func (l *IPv6HopByHopOptionsExtHdr) length() int {
return len(l.Options) + 2
}
@@ -667,13 +753,31 @@ func (l *IPv6DestinationOptionsExtHdr) String() string {
return stringLayer(l)
}
+func (*IPv6FragmentExtHdr) length() int {
+ return header.IPv6FragmentExtHdrLength
+}
+
+func (l *IPv6FragmentExtHdr) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *IPv6FragmentExtHdr) merge(other Layer) error {
+ return mergeLayer(l, other)
+}
+
+func (l *IPv6FragmentExtHdr) String() string {
+ return stringLayer(l)
+}
+
// ICMPv6 can construct and match an ICMPv6 encapsulation.
type ICMPv6 struct {
LayerBase
- Type *header.ICMPv6Type
- Code *byte
- Checksum *uint16
- NDPPayload []byte
+ Type *header.ICMPv6Type
+ Code *byte
+ Checksum *uint16
+ Payload []byte
}
func (l *ICMPv6) String() string {
@@ -684,7 +788,7 @@ func (l *ICMPv6) String() string {
// ToBytes implements Layer.ToBytes.
func (l *ICMPv6) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv6HeaderSize+len(l.NDPPayload))
+ b := make([]byte, header.ICMPv6HeaderSize+len(l.Payload))
h := header.ICMPv6(b)
if l.Type != nil {
h.SetType(*l.Type)
@@ -692,7 +796,7 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
- copy(h.NDPPayload(), l.NDPPayload)
+ copy(h.NDPPayload(), l.Payload)
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} else {
@@ -701,7 +805,11 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
// We need to search forward to find the IPv6 header.
for prev := l.Prev(); prev != nil; prev = prev.Prev() {
if ipv6, ok := prev.(*IPv6); ok {
- h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, buffer.VectorisedView{}))
+ payload, err := payload(l)
+ if err != nil {
+ return nil, err
+ }
+ h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, payload))
break
}
}
@@ -725,10 +833,10 @@ func Byte(v byte) *byte {
func parseICMPv6(b []byte) (Layer, layerParser) {
h := header.ICMPv6(b)
icmpv6 := ICMPv6{
- Type: ICMPv6Type(h.Type()),
- Code: Byte(h.Code()),
- Checksum: Uint16(h.Checksum()),
- NDPPayload: h.NDPPayload(),
+ Type: ICMPv6Type(h.Type()),
+ Code: Byte(h.Code()),
+ Checksum: Uint16(h.Checksum()),
+ Payload: h.NDPPayload(),
}
return &icmpv6, nil
}
@@ -738,7 +846,7 @@ func (l *ICMPv6) match(other Layer) bool {
}
func (l *ICMPv6) length() int {
- return header.ICMPv6HeaderSize + len(l.NDPPayload)
+ return header.ICMPv6HeaderSize + len(l.Payload)
}
// merge overrides the values in l with the values from other but only in fields
@@ -904,12 +1012,14 @@ func payload(l Layer) (buffer.VectorisedView, error) {
func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) {
totalLength := uint16(totalLength(l))
var xsum uint16
- switch s := l.Prev().(type) {
+ switch p := l.Prev().(type) {
case *IPv4:
- xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
+ xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
+ case *IPv6:
+ xsum = header.PseudoHeaderChecksum(protoNumber, *p.SrcAddr, *p.DstAddr, totalLength)
default:
- // TODO(b/150301488): Support more protocols, like IPv6.
- return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
+ // TODO(b/161246171): Support more protocols.
+ return 0, fmt.Errorf("checksum for protocol %d is not supported when previous layer is %T", protoNumber, p)
}
payloadBytes, err := payload(l)
if err != nil {
diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go
index 382a983a1..a2a763034 100644
--- a/test/packetimpact/testbench/layers_test.go
+++ b/test/packetimpact/testbench/layers_test.go
@@ -593,10 +593,107 @@ func TestIPv6ExtHdrOptions(t *testing.T) {
Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
},
&ICMPv6{
- Type: ICMPv6Type(header.ICMPv6ParamProblem),
- Code: Byte(0),
- Checksum: Uint16(0x5f98),
- NDPPayload: []byte{0x00, 0x00, 0x00, 0x06},
+ Type: ICMPv6Type(header.ICMPv6ParamProblem),
+ Code: Byte(0),
+ Checksum: Uint16(0x5f98),
+ Payload: []byte{0x00, 0x00, 0x00, 0x06},
+ },
+ },
+ },
+ {
+ description: "IPv6/HopByHop/Fragment",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // HopByHop Options
+ 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x2a,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6HopByHopOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(false),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: nil,
+ },
+ },
+ },
+ {
+ description: "IPv6/DestOpt/Fragment/Payload",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x3c, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // Destination Options
+ 0x2c, 0x00, 0x05, 0x02, 0x00, 0x00, 0x01, 0x00,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
+ // Sample Data
+ 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6DestinationOptionsExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6FragmentExtHdrIdentifier),
+ Options: []byte{0x05, 0x02, 0x00, 0x00, 0x01, 0x00},
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(true),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: []byte("Sample Data"),
+ },
+ },
+ },
+ {
+ description: "IPv6/Fragment/Payload",
+ wantBytes: []byte{
+ // IPv6 Header
+ 0x60, 0x00, 0x00, 0x00, 0x00, 0x13, 0x2c, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x01, 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef,
+ // Fragment ExtHdr
+ 0x3b, 0x00, 0x03, 0x21, 0x00, 0x00, 0x00, 0x2a,
+ // Sample Data
+ 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
+ },
+ wantLayers: []Layer{
+ &IPv6{
+ SrcAddr: Address(tcpip.Address(net.ParseIP("::1"))),
+ DstAddr: Address(tcpip.Address(net.ParseIP("fe80::dead:beef"))),
+ },
+ &IPv6FragmentExtHdr{
+ NextHeader: IPv6ExtHdrIdent(header.IPv6NoNextHeaderIdentifier),
+ FragmentOffset: Uint16(100),
+ MoreFragments: Bool(true),
+ Identification: Uint32(42),
+ },
+ &Payload{
+ Bytes: []byte("Sample Data"),
},
},
},
@@ -606,6 +703,19 @@ func TestIPv6ExtHdrOptions(t *testing.T) {
if !layers.match(tt.wantLayers) {
t.Fatalf("match failed with diff: %s", layers.diff(tt.wantLayers))
}
+ // Make sure we can generate correct next header values and checksums
+ for _, layer := range layers {
+ switch layer := layer.(type) {
+ case *IPv6HopByHopOptionsExtHdr:
+ layer.NextHeader = nil
+ case *IPv6DestinationOptionsExtHdr:
+ layer.NextHeader = nil
+ case *IPv6FragmentExtHdr:
+ layer.NextHeader = nil
+ case *ICMPv6:
+ layer.Checksum = nil
+ }
+ }
gotBytes, err := layers.ToBytes()
if err != nil {
t.Fatalf("ToBytes() failed on %s: %s", &layers, err)
diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go
index d64f32a5b..242464e3a 100644
--- a/test/packetimpact/testbench/testbench.go
+++ b/test/packetimpact/testbench/testbench.go
@@ -31,23 +31,37 @@ var (
DUTType = ""
// Device is the local device on the test network.
Device = ""
+
// LocalIPv4 is the local IPv4 address on the test network.
LocalIPv4 = ""
+ // RemoteIPv4 is the DUT's IPv4 address on the test network.
+ RemoteIPv4 = ""
+ // IPv4PrefixLength is the network prefix length of the IPv4 test network.
+ IPv4PrefixLength = 0
+
// LocalIPv6 is the local IPv6 address on the test network.
LocalIPv6 = ""
+ // RemoteIPv6 is the DUT's IPv6 address on the test network.
+ RemoteIPv6 = ""
+
+ // LocalInterfaceID is the ID of the local interface on the test network.
+ LocalInterfaceID uint32
+ // RemoteInterfaceID is the ID of the remote interface on the test network.
+ //
+ // Not using uint32 because package flag does not support uint32.
+ RemoteInterfaceID uint64
+
// LocalMAC is the local MAC address on the test network.
LocalMAC = ""
+ // RemoteMAC is the DUT's MAC address on the test network.
+ RemoteMAC = ""
+
// POSIXServerIP is the POSIX server's IP address on the control network.
POSIXServerIP = ""
// POSIXServerPort is the UDP port the POSIX server is bound to on the
// control network.
POSIXServerPort = 40000
- // RemoteIPv4 is the DUT's IPv4 address on the test network.
- RemoteIPv4 = ""
- // RemoteIPv6 is the DUT's IPv6 address on the test network.
- RemoteIPv6 = ""
- // RemoteMAC is the DUT's MAC address on the test network.
- RemoteMAC = ""
+
// RPCKeepalive is the gRPC keepalive.
RPCKeepalive = 10 * time.Second
// RPCTimeout is the gRPC timeout.
@@ -68,6 +82,7 @@ func RegisterFlags(fs *flag.FlagSet) {
fs.StringVar(&RemoteMAC, "remote_mac", RemoteMAC, "remote mac address for test packets")
fs.StringVar(&Device, "device", Device, "local device for test packets")
fs.StringVar(&DUTType, "dut_type", DUTType, "type of device under test")
+ fs.Uint64Var(&RemoteInterfaceID, "remote_interface_id", RemoteInterfaceID, "remote interface ID for test packets")
}
// genPseudoFlags populates flag-like global config based on real flags.
@@ -90,6 +105,13 @@ func genPseudoFlags() error {
LocalMAC = deviceInfo.MAC.String()
LocalIPv6 = deviceInfo.IPv6Addr.String()
+ LocalInterfaceID = deviceInfo.ID
+
+ if deviceInfo.IPv4Net != nil {
+ IPv4PrefixLength, _ = deviceInfo.IPv4Net.Mask.Size()
+ } else {
+ IPv4PrefixLength, _ = net.ParseIP(LocalIPv4).DefaultMask().Size()
+ }
return nil
}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 85749c559..27905dcff 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -18,8 +18,6 @@ packetimpact_go_test(
packetimpact_go_test(
name = "ipv4_id_uniqueness",
srcs = ["ipv4_id_uniqueness_test.go"],
- # TODO(b/157506701) Fix netstack then remove the line below.
- expect_netstack_failure = True,
deps = [
"//pkg/abi/linux",
"//pkg/tcpip/header",
@@ -29,8 +27,19 @@ packetimpact_go_test(
)
packetimpact_go_test(
- name = "udp_recv_multicast",
- srcs = ["udp_recv_multicast_test.go"],
+ name = "udp_discard_mcast_source_addr",
+ srcs = ["udp_discard_mcast_source_addr_test.go"],
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "udp_recv_mcast_bcast",
+ srcs = ["udp_recv_mcast_bcast_test.go"],
# TODO(b/152813495): Fix netstack then remove the line below.
expect_netstack_failure = True,
deps = [
@@ -211,6 +220,16 @@ packetimpact_go_test(
)
packetimpact_go_test(
+ name = "tcp_network_unreachable",
+ srcs = ["tcp_network_unreachable_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
name = "tcp_cork_mss",
srcs = ["tcp_cork_mss_test.go"],
deps = [
@@ -257,6 +276,20 @@ packetimpact_go_test(
)
packetimpact_go_test(
+ name = "ipv6_fragment_reassembly",
+ srcs = ["ipv6_fragment_reassembly_test.go"],
+ # TODO(b/160919104): Fix netstack then remove the line below.
+ expect_netstack_failure = True,
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/buffer",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
name = "udp_send_recv_dgram",
srcs = ["udp_send_recv_dgram_test.go"],
deps = [
diff --git a/test/packetimpact/tests/icmpv6_param_problem_test.go b/test/packetimpact/tests/icmpv6_param_problem_test.go
index 4d1d9a7f5..8dfd26ee8 100644
--- a/test/packetimpact/tests/icmpv6_param_problem_test.go
+++ b/test/packetimpact/tests/icmpv6_param_problem_test.go
@@ -41,8 +41,8 @@ func TestICMPv6ParamProblemTest(t *testing.T) {
NextHeader: testbench.Uint8(254),
}
icmpv6 := testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
- NDPPayload: []byte("hello world"),
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
+ Payload: []byte("hello world"),
}
toSend := (*testbench.Connection)(&conn).CreateFrame(testbench.Layers{&ipv6}, &icmpv6)
@@ -62,8 +62,8 @@ func TestICMPv6ParamProblemTest(t *testing.T) {
binary.BigEndian.PutUint32(b, header.IPv6NextHeaderOffset)
expectedPayload = append(b, expectedPayload...)
expectedICMPv6 := testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
- NDPPayload: expectedPayload,
+ Type: testbench.ICMPv6Type(header.ICMPv6ParamProblem),
+ Payload: expectedPayload,
}
paramProblem := testbench.Layers{
diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
new file mode 100644
index 000000000..7b462c8e2
--- /dev/null
+++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
@@ -0,0 +1,168 @@
+// 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_reassembly_test
+
+import (
+ "bytes"
+ "encoding/binary"
+ "encoding/hex"
+ "flag"
+ "net"
+ "testing"
+ "time"
+
+ "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()
+
+ 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.Byte(0),
+ 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(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.Byte(0),
+ Payload: icmpv6EchoPayload,
+ Checksum: &cksum,
+ })
+
+ icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
+
+ conn.Send(testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16((firstPayloadLength + header.ICMPv6EchoMinimumSize) / 8),
+ MoreFragments: testbench.Bool(false),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: secondPayloadToSend,
+ })
+
+ gotEchoReplyFirstPart, err := conn.ExpectFrame(testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ FragmentOffset: testbench.Uint16(0),
+ MoreFragments: testbench.Bool(true),
+ },
+ &testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
+ Code: testbench.Byte(0),
+ },
+ }, 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(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))
+ }
+}
diff --git a/test/packetimpact/tests/ipv6_unknown_options_action_test.go b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
index d301d8829..100b30ad7 100644
--- a/test/packetimpact/tests/ipv6_unknown_options_action_test.go
+++ b/test/packetimpact/tests/ipv6_unknown_options_action_test.go
@@ -171,9 +171,9 @@ func TestIPv6UnknownOptionAction(t *testing.T) {
&tb.Ether{},
&tb.IPv6{},
&tb.ICMPv6{
- Type: tb.ICMPv6Type(header.ICMPv6ParamProblem),
- Code: tb.Byte(2),
- NDPPayload: icmpv6Payload,
+ Type: tb.ICMPv6Type(header.ICMPv6ParamProblem),
+ Code: tb.Byte(2),
+ Payload: icmpv6Payload,
},
}, time.Second)
if tt.wantICMPv6 && err != nil {
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
new file mode 100644
index 000000000..868a08da8
--- /dev/null
+++ b/test/packetimpact/tests/tcp_network_unreachable_test.go
@@ -0,0 +1,139 @@
+// 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 tcp_synsent_reset_test
+
+import (
+ "context"
+ "flag"
+ "net"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+// TestTCPSynSentUnreachable verifies that TCP connections fail immediately when
+// an ICMP destination unreachable message is sent in response to the inital
+// SYN.
+func TestTCPSynSentUnreachable(t *testing.T) {
+ // Create the DUT and connection.
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ clientFD, clientPort := dut.CreateBoundSocket(unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv4))
+ port := uint16(9001)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{SrcPort: &port, DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort, DstPort: &port})
+ defer conn.Close()
+
+ // Bring the DUT to SYN-SENT state with a non-blocking connect.
+ ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout)
+ defer cancel()
+ sa := unix.SockaddrInet4{Port: int(port)}
+ copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv4)).To4())
+ if _, err := dut.ConnectWithErrno(ctx, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) {
+ t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err)
+ }
+
+ // Get the SYN.
+ tcpLayers, err := conn.ExpectData(&testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second)
+ if err != nil {
+ t.Fatalf("expected SYN: %s", err)
+ }
+
+ // Send a host unreachable message.
+ rawConn := (*testbench.Connection)(&conn)
+ layers := rawConn.CreateFrame(nil)
+ layers = layers[:len(layers)-1]
+ const ipLayer = 1
+ const tcpLayer = ipLayer + 1
+ ip, ok := tcpLayers[ipLayer].(*testbench.IPv4)
+ if !ok {
+ t.Fatalf("expected %s to be IPv4", tcpLayers[ipLayer])
+ }
+ tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
+ if !ok {
+ t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
+ }
+ var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable), Code: testbench.Uint8(header.ICMPv4HostUnreachable)}
+ layers = append(layers, &icmpv4, ip, tcp)
+ rawConn.SendFrameStateless(layers)
+
+ if _, err = dut.ConnectWithErrno(ctx, clientFD, &sa); err != syscall.Errno(unix.EHOSTUNREACH) {
+ t.Errorf("expected connect to fail with EHOSTUNREACH, but got %v", err)
+ }
+}
+
+// TestTCPSynSentUnreachable6 verifies that TCP connections fail immediately when
+// an ICMP destination unreachable message is sent in response to the inital
+// SYN.
+func TestTCPSynSentUnreachable6(t *testing.T) {
+ // Create the DUT and connection.
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ clientFD, clientPort := dut.CreateBoundSocket(unix.SOCK_STREAM|unix.SOCK_NONBLOCK, unix.IPPROTO_TCP, net.ParseIP(testbench.RemoteIPv6))
+ conn := testbench.NewTCPIPv6(t, testbench.TCP{DstPort: &clientPort}, testbench.TCP{SrcPort: &clientPort})
+ defer conn.Close()
+
+ // Bring the DUT to SYN-SENT state with a non-blocking connect.
+ ctx, cancel := context.WithTimeout(context.Background(), testbench.RPCTimeout)
+ defer cancel()
+ sa := unix.SockaddrInet6{
+ Port: int(conn.SrcPort()),
+ ZoneId: uint32(testbench.RemoteInterfaceID),
+ }
+ copy(sa.Addr[:], net.IP(net.ParseIP(testbench.LocalIPv6)).To16())
+ if _, err := dut.ConnectWithErrno(ctx, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) {
+ t.Errorf("expected connect to fail with EINPROGRESS, but got %v", err)
+ }
+
+ // Get the SYN.
+ tcpLayers, err := conn.ExpectData(&testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second)
+ if err != nil {
+ t.Fatalf("expected SYN: %s", err)
+ }
+
+ // Send a host unreachable message.
+ rawConn := (*testbench.Connection)(&conn)
+ layers := rawConn.CreateFrame(nil)
+ layers = layers[:len(layers)-1]
+ const ipLayer = 1
+ const tcpLayer = ipLayer + 1
+ ip, ok := tcpLayers[ipLayer].(*testbench.IPv6)
+ if !ok {
+ t.Fatalf("expected %s to be IPv6", tcpLayers[ipLayer])
+ }
+ tcp, ok := tcpLayers[tcpLayer].(*testbench.TCP)
+ if !ok {
+ t.Fatalf("expected %s to be TCP", tcpLayers[tcpLayer])
+ }
+ var icmpv6 testbench.ICMPv6 = testbench.ICMPv6{
+ Type: testbench.ICMPv6Type(header.ICMPv6DstUnreachable),
+ Code: testbench.Uint8(header.ICMPv6NetworkUnreachable),
+ // Per RFC 4443 3.1, the payload contains 4 zeroed bytes.
+ Payload: []byte{0, 0, 0, 0},
+ }
+ layers = append(layers, &icmpv6, ip, tcp)
+ rawConn.SendFrameStateless(layers)
+
+ if _, err = dut.ConnectWithErrno(ctx, clientFD, &sa); err != syscall.Errno(unix.ENETUNREACH) {
+ t.Errorf("expected connect to fail with ENETUNREACH, but got %v", err)
+ }
+}
diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
new file mode 100644
index 000000000..b0315e67c
--- /dev/null
+++ b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go
@@ -0,0 +1,92 @@
+// 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 udp_discard_mcast_source_addr_test
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "net"
+ "syscall"
+ "testing"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+var oneSecond = unix.Timeval{Sec: 1, Usec: 0}
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv4))
+ defer dut.Close(remoteFD)
+ dut.SetSockOptTimeval(remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
+ conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ for _, mcastAddr := range []net.IP{
+ net.IPv4allsys,
+ net.IPv4allrouter,
+ net.IPv4(224, 0, 1, 42),
+ net.IPv4(232, 1, 2, 3),
+ } {
+ t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
+ conn.SendIP(
+ testbench.IPv4{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To4()))},
+ testbench.UDP{},
+ )
+
+ ret, payload, errno := dut.RecvWithErrno(context.Background(), remoteFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
+ }
+ })
+ }
+}
+
+func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(testbench.RemoteIPv6))
+ defer dut.Close(remoteFD)
+ dut.SetSockOptTimeval(remoteFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &oneSecond)
+ conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ for _, mcastAddr := range []net.IP{
+ net.IPv6interfacelocalallnodes,
+ net.IPv6linklocalallnodes,
+ net.IPv6linklocalallrouters,
+ net.ParseIP("fe01::42"),
+ net.ParseIP("fe02::4242"),
+ } {
+ t.Run(fmt.Sprintf("srcaddr=%s", mcastAddr), func(t *testing.T) {
+ conn.SendIPv6(
+ testbench.IPv6{SrcAddr: testbench.Address(tcpip.Address(mcastAddr.To16()))},
+ testbench.UDP{},
+ )
+ ret, payload, errno := dut.RecvWithErrno(context.Background(), remoteFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/udp_recv_multicast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
index 77a9bfa1d..263a54291 100644
--- a/test/packetimpact/tests/udp_recv_multicast_test.go
+++ b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package udp_recv_multicast_test
+package udp_recv_mcast_bcast_test
import (
"flag"
@@ -28,13 +28,36 @@ func init() {
testbench.RegisterFlags(flag.CommandLine)
}
-func TestUDPRecvMulticast(t *testing.T) {
+func TestUDPRecvMulticastBroadcast(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
+ boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.IPv4(0, 0, 0, 0))
defer dut.Close(boundFD)
conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
defer conn.Close()
- conn.SendIP(testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(net.ParseIP("224.0.0.1").To4()))}, testbench.UDP{})
- dut.Recv(boundFD, 100, 0)
+
+ for _, bcastAddr := range []net.IP{
+ broadcastAddr(net.ParseIP(testbench.RemoteIPv4), net.CIDRMask(testbench.IPv4PrefixLength, 32)),
+ net.IPv4(255, 255, 255, 255),
+ net.IPv4(224, 0, 0, 1),
+ } {
+ payload := testbench.GenerateRandomPayload(t, 1<<10)
+ conn.SendIP(
+ testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(bcastAddr.To4()))},
+ testbench.UDP{},
+ &testbench.Payload{Bytes: payload},
+ )
+ t.Logf("Receiving packet sent to address: %s", bcastAddr)
+ if got, want := string(dut.Recv(boundFD, int32(len(payload)), 0)), string(payload); got != want {
+ t.Errorf("received payload does not match sent payload got: %s, want: %s", got, want)
+ }
+ }
+}
+
+func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
+ ip4 := ip.To4()
+ for i := range ip4 {
+ ip4[i] |= ^mask[i]
+ }
+ return ip4
}
diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go
index 224feef85..bd53ad90b 100644
--- a/test/packetimpact/tests/udp_send_recv_dgram_test.go
+++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go
@@ -28,62 +28,74 @@ func init() {
testbench.RegisterFlags(flag.CommandLine)
}
-func TestUDPRecv(t *testing.T) {
+type udpConn interface {
+ Send(testbench.UDP, ...testbench.Layer)
+ ExpectData(testbench.UDP, testbench.Payload, time.Duration) (testbench.Layers, error)
+ Drain()
+ Close()
+}
+
+func TestUDP(t *testing.T) {
dut := testbench.NewDUT(t)
defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close()
- testCases := []struct {
- name string
- payload []byte
- }{
- {"emptypayload", nil},
- {"small payload", []byte("hello world")},
- {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- conn.Send(testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
- if got, want := string(dut.Recv(boundFD, int32(len(tc.payload)), 0)), string(tc.payload); got != want {
- t.Fatalf("received payload does not match sent payload got: %s, want: %s", got, want)
+ for _, isIPv4 := range []bool{true, false} {
+ ipVersionName := "IPv6"
+ if isIPv4 {
+ ipVersionName = "IPv4"
+ }
+ t.Run(ipVersionName, func(t *testing.T) {
+ var addr string
+ if isIPv4 {
+ addr = testbench.RemoteIPv4
+ } else {
+ addr = testbench.RemoteIPv6
}
- })
- }
-}
+ boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP(addr))
+ defer dut.Close(boundFD)
-func TestUDPSend(t *testing.T) {
- dut := testbench.NewDUT(t)
- defer dut.TearDown()
- boundFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
- defer dut.Close(boundFD)
- conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close()
+ var conn udpConn
+ var localAddr unix.Sockaddr
+ if isIPv4 {
+ v4Conn := testbench.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ localAddr = v4Conn.LocalAddr()
+ conn = &v4Conn
+ } else {
+ v6Conn := testbench.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
+ localAddr = v6Conn.LocalAddr()
+ conn = &v6Conn
+ }
+ defer conn.Close()
- testCases := []struct {
- name string
- payload []byte
- }{
- {"emptypayload", nil},
- {"small payload", []byte("hello world")},
- {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
- }
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- conn.Drain()
- if got, want := int(dut.SendTo(boundFD, tc.payload, 0, conn.LocalAddr())), len(tc.payload); got != want {
- t.Fatalf("short write got: %d, want: %d", got, want)
+ testCases := []struct {
+ name string
+ payload []byte
+ }{
+ {"emptypayload", nil},
+ {"small payload", []byte("hello world")},
+ {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
+ // Even though UDP allows larger dgrams we don't test it here as
+ // they need to be fragmented and written out as individual
+ // frames.
}
- if _, err := conn.ExpectData(testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, 1*time.Second); err != nil {
- t.Fatal(err)
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Run("Send", func(t *testing.T) {
+ conn.Send(testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
+ if got, want := string(dut.Recv(boundFD, int32(len(tc.payload)), 0)), string(tc.payload); got != want {
+ t.Fatalf("received payload does not match sent payload got: %s, want: %s", got, want)
+ }
+ })
+ t.Run("Recv", func(t *testing.T) {
+ conn.Drain()
+ if got, want := int(dut.SendTo(boundFD, tc.payload, 0, localAddr)), len(tc.payload); got != want {
+ t.Fatalf("short write got: %d, want: %d", got, want)
+ }
+ if _, err := conn.ExpectData(testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, time.Second); err != nil {
+ t.Fatal(err)
+ }
+ })
+ })
}
})
}