testing: speed up painfully slow packetimpact test

This is really just a combinatorial explosion of tests run serially. We should
at least parallelize them. Doing so reduces the buildkite runtime of `make
packetimpact-tests` to half ([before], [after]).

For reviewiers: this is just splitting one test file into 3 plus a single
shared file. It's pretty much all branch+delete chunks.

[before]: https://buildkite.com/gvisor/pipeline/builds/29972#018fc0f8-6d6b-4e9f-8717-8278aa94dce4
[after]: https://buildkite.com/gvisor/pipeline/builds/29980#018fca5b-d95d-4991-8ec3-e16d82510372

PiperOrigin-RevId: 639194987
This commit is contained in:
Kevin Krakauer
2024-05-31 17:02:12 -07:00
committed by gVisor bot
parent 04e7902bb5
commit 9d3ea997e2
7 changed files with 930 additions and 811 deletions
+9 -1
View File
@@ -304,7 +304,15 @@ ALL_TESTS = [
name = "tcp_fin_retransmission",
),
PacketimpactTestInfo(
name = "generic_dgram_socket_send_recv",
name = "icmpv4_socket_send_recv",
timeout = "long",
),
PacketimpactTestInfo(
name = "icmpv6_socket_send_recv",
timeout = "long",
),
PacketimpactTestInfo(
name = "udp_socket_send_recv",
timeout = "long",
),
PacketimpactTestInfo(
+35 -2
View File
@@ -412,8 +412,41 @@ packetimpact_testbench(
)
packetimpact_testbench(
name = "generic_dgram_socket_send_recv",
srcs = ["generic_dgram_socket_send_recv_test.go"],
name = "icmpv4_socket_send_recv",
srcs = [
"generic_dgram_socket_send_recv.go",
"icmpv4_socket_send_recv_test.go",
],
deps = [
"//pkg/tcpip",
"//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 = "icmpv6_socket_send_recv",
srcs = [
"generic_dgram_socket_send_recv.go",
"icmpv6_socket_send_recv_test.go",
],
deps = [
"//pkg/tcpip",
"//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 = "udp_socket_send_recv",
srcs = [
"generic_dgram_socket_send_recv.go",
"udp_socket_send_recv_test.go",
],
deps = [
"//pkg/tcpip",
"//pkg/tcpip/header",
@@ -0,0 +1,137 @@
// Copyright 2021 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 generic_dgram_socket_send_recv_test
import (
"flag"
"fmt"
"net"
"testing"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
const (
// Even though sockets allow larger datagrams we don't test it here as they
// need to be fragmented and written out as individual frames.
maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize
maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize
maxUDPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize
maxUDPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize
)
func init() {
testbench.Initialize(flag.CommandLine)
}
func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer {
t.Helper()
dst := func() tcpip.LinkAddress {
if isBroadcast(dut, sendTo) {
dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
// When sending to broadcast (subnet or limited), the expected ethernet
// address is also broadcast.
return header.EthernetBroadcastAddress
}
if sendTo.IsMulticast() {
if sendTo4 := sendTo.To4(); sendTo4 != nil {
return header.EthernetAddressFromMulticastIPv4Address(tcpip.AddrFrom4Slice(sendTo4))
}
return header.EthernetAddressFromMulticastIPv6Address(tcpip.AddrFrom16Slice(sendTo.To16()))
}
return ""
}()
var ether testbench.Ether
if len(dst) != 0 {
ether.DstAddr = &dst
}
return &ether
}
type protocolTest interface {
Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
}
func runAllCombinations(t *testing.T, proto protocolTest) {
dut := testbench.NewDUT(t)
subnetBroadcast := dut.Net.SubnetBroadcast()
// Test every combination of bound/unbound, broadcast/multicast/unicast
// bound/destination address, and bound/not-bound to device.
for _, bindTo := range []net.IP{
nil, // Do not bind.
net.IPv4zero,
net.IPv4bcast,
net.IPv4allsys,
net.IPv6zero,
subnetBroadcast,
dut.Net.RemoteIPv4,
dut.Net.RemoteIPv6,
} {
t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) {
for _, sendTo := range []net.IP{
net.IPv4bcast,
net.IPv4allsys,
subnetBroadcast,
dut.Net.LocalIPv4,
dut.Net.LocalIPv6,
dut.Net.RemoteIPv4,
dut.Net.RemoteIPv6,
} {
t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) {
for _, bindToDevice := range []bool{true, false} {
t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) {
t.Run("Send", func(t *testing.T) {
proto.Send(t, dut, bindTo, sendTo, bindToDevice)
})
t.Run("Receive", func(t *testing.T) {
proto.Receive(t, dut, bindTo, sendTo, bindToDevice)
})
})
}
})
}
})
}
}
func isBroadcast(dut testbench.DUT, ip net.IP) bool {
return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast())
}
func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool {
return isBroadcast(dut, ip) || ip.IsMulticast()
}
func sameIPVersion(a, b net.IP) bool {
return (a.To4() == nil) == (b.To4() == nil)
}
func isRemoteAddr(dut testbench.DUT, ip net.IP) bool {
return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6)
}
func isInTestSubnetV4(dut testbench.DUT, ip net.IP) bool {
network := net.IPNet{
IP: dut.Net.LocalIPv4,
Mask: net.CIDRMask(dut.Net.IPv4PrefixLength, net.IPv4len*8),
}
return network.Contains(ip)
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
// Copyright 2021 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 generic_dgram_socket_send_recv_test
import (
"context"
"net"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
func TestICMPv4(t *testing.T) {
runAllCombinations(t, &icmpV4Test{})
}
type icmpV4TestEnv struct {
socketFD int32
ident uint16
conn testbench.IPv4Conn
layers testbench.Layers
}
type icmpV4Test struct{}
func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv {
t.Helper()
// Tell the DUT to create a socket.
var socketFD int32
var ident uint16
if bindTo != nil {
socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo)
} else {
// An unbound socket will auto-bind to INADDR_ANY.
socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
}
t.Cleanup(func() {
dut.Close(t, socketFD)
})
if bindToDevice {
dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
}
// Create a socket on the test runner.
conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
t.Cleanup(func() {
conn.Close(t)
})
dstAddr := sendTo.To4()
return icmpV4TestEnv{
socketFD: socketFD,
ident: ident,
conn: conn,
layers: testbench.Layers{
expectedEthLayer(t, dut, socketFD, sendTo),
&testbench.IPv4{
DstAddr: &dstAddr,
},
},
}
}
func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
// ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
// addresses.
return
}
isV4 := sendTo.To4() != nil
// TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow
// sending to broadcast and multicast addresses.
if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) {
// expectPacket cannot be false. In some cases the packet will send, but
// with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and
// not worth the effort to create a special case when this occurs.
t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.")
}
expectNetworkUnreachable := true
// We don't expect ENETUNREACH if any of the follwing is true:
// 1. bindTo is specfied.
if !bindTo.Equal(net.IPv4zero) {
expectNetworkUnreachable = false
}
// 2. We are binding to a device.
if bindToDevice {
expectNetworkUnreachable = false
}
// 3. sendTo is neither 224.0.0.1 nor 255.255.255.255.
if !sendTo.Equal(net.IPv4bcast) && !sendTo.Equal(net.IPv4allsys) {
expectNetworkUnreachable = false
}
expectPacket := true
// We don't expect an incoming packet if any of the following is true:
// 1. sendTo is not an ipv4 address.
if !isV4 {
expectPacket = false
}
// 2. sendTo is the dut itself.
if sendTo.Equal(dut.Net.RemoteIPv4) {
expectPacket = false
}
// 3. we are expecting ENETUNREACH.
if expectNetworkUnreachable {
expectPacket = false
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
} {
t.Run(name, func(t *testing.T) {
icmpLayer := &testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4Echo),
Payload: payload,
}
bytes, err := icmpLayer.ToBytes()
if err != nil {
t.Fatalf("icmpLayer.ToBytes() = %s", err)
}
destSockaddr := unix.SockaddrInet4{}
copy(destSockaddr.Addr[:], sendTo.To4())
// Tell the DUT to send a packet out the ICMP socket.
ret, err := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
if expectNetworkUnreachable {
if !(ret == -1 && err == unix.ENETUNREACH) {
t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (-1, %s)", ret, err, unix.ENETUNREACH)
}
} else {
if !(int(ret) == len(bytes) && err == unix.Errno(0)) {
t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (%d, 0)", ret, err, len(bytes))
}
}
// Verify the test runner received an ICMP packet with the correctly
// set "ident".
if env.ident != 0 {
icmpLayer.Ident = &env.ident
}
want := append(env.layers, icmpLayer)
if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
} else if ok && !expectPacket {
matchedFrame := got[len(got)-1]
t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
}
})
}
}
func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
// ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
// addresses.
return
}
expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4)
// TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
// restricts ICMP sockets to receive only from unicast addresses.
if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) {
expectPacket = true
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
} {
t.Run(name, func(t *testing.T) {
icmpLayer := &testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4EchoReply),
Payload: payload,
}
if env.ident != 0 {
icmpLayer.Ident = &env.ident
}
// Send an ICMPv4 packet from the test runner to the DUT.
frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
env.conn.SendFrame(t, frame)
// Verify the behavior of the ICMP socket on the DUT.
if expectPacket {
payload, err := icmpLayer.ToBytes()
if err != nil {
t.Fatalf("icmpLayer.ToBytes() = %s", err)
}
// Receive one extra byte to assert the length of the
// packet received in the case where the packet contains
// more data than expected.
len := int32(len(payload)) + 1
got, want := dut.Recv(t, env.socketFD, len, 0), payload
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
}
} else {
// Expected receive error, set a short receive timeout.
dut.SetSockOptTimeval(
t,
env.socketFD,
unix.SOL_SOCKET,
unix.SO_RCVTIMEO,
&unix.Timeval{
Sec: 1,
Usec: 0,
},
)
ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0)
if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
}
}
})
}
}
@@ -0,0 +1,237 @@
// Copyright 2021 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 generic_dgram_socket_send_recv_test
import (
"context"
"net"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
func TestICMPv6(t *testing.T) {
runAllCombinations(t, &icmpV6Test{})
}
type icmpV6TestEnv struct {
socketFD int32
ident uint16
conn testbench.IPv6Conn
layers testbench.Layers
}
// icmpV6Test and icmpV4Test look substantially similar at first look, but have
// enough subtle differences in setup and test expectations to discourage
// refactoring:
// - Different IP layers
// - Different testbench.Connections
// - Different UNIX domain and proto arguments
// - Different expectPacket and wantErrno for send and receive
type icmpV6Test struct{}
func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv {
t.Helper()
// Tell the DUT to create a socket.
var socketFD int32
var ident uint16
if bindTo != nil {
socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo)
} else {
// An unbound socket will auto-bind to IN6ADDR_ANY_INIT.
socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
}
t.Cleanup(func() {
dut.Close(t, socketFD)
})
if bindToDevice {
dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
}
// Create a socket on the test runner.
conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
t.Cleanup(func() {
conn.Close(t)
})
dstAddr := sendTo.To16()
return icmpV6TestEnv{
socketFD: socketFD,
ident: ident,
conn: conn,
layers: testbench.Layers{
expectedEthLayer(t, dut, socketFD, sendTo),
&testbench.IPv6{
DstAddr: &dstAddr,
},
},
}
}
func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
if bindTo.To4() != nil || bindTo.IsMulticast() {
// ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
return
}
expectPacket := sendTo.Equal(dut.Net.LocalIPv6)
wantErrno := unix.Errno(0)
if sendTo.To4() != nil {
wantErrno = unix.EINVAL
// TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets
// return EINVAL after calling sendto with an IPv4 address.
if dut.Uname.IsGvisor() || dut.Uname.IsFuchsia() {
wantErrno = unix.ENETUNREACH
if !bindTo.Equal(dut.Net.RemoteIPv6) && (bindToDevice || isInTestSubnetV4(dut, sendTo)) {
wantErrno = unix.Errno(0)
}
}
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
} {
t.Run(name, func(t *testing.T) {
icmpLayer := &testbench.ICMPv6{
Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
Payload: payload,
}
bytes, err := icmpLayer.ToBytes()
if err != nil {
t.Fatalf("icmpLayer.ToBytes() = %s", err)
}
destSockaddr := unix.SockaddrInet6{
ZoneId: dut.Net.RemoteDevID,
}
copy(destSockaddr.Addr[:], sendTo.To16())
// Tell the DUT to send a packet out the ICMPv6 socket.
gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
if gotErrno != wantErrno {
t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
}
if wantErrno != 0 {
return
}
if got, want := int(gotRet), len(bytes); got != want {
t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
}
// Verify the test runner received an ICMPv6 packet with the
// correctly set "ident".
if env.ident != 0 {
icmpLayer.Ident = &env.ident
}
want := append(env.layers, icmpLayer)
if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
} else if ok && !expectPacket {
matchedFrame := got[len(got)-1]
t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
}
})
}
}
func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
if bindTo.To4() != nil || bindTo.IsMulticast() {
// ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
return
}
expectPacket := true
switch {
case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6):
case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6):
case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes):
default:
expectPacket = false
}
// TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
// restricts ICMP sockets to receive only from unicast addresses.
if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) {
expectPacket = false
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
} {
t.Run(name, func(t *testing.T) {
icmpLayer := &testbench.ICMPv6{
Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
Payload: payload,
}
if env.ident != 0 {
icmpLayer.Ident = &env.ident
}
// Send an ICMPv6 packet from the test runner to the DUT.
frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
env.conn.SendFrame(t, frame)
// Verify the behavior of the ICMPv6 socket on the DUT.
if expectPacket {
payload, err := icmpLayer.ToBytes()
if err != nil {
t.Fatalf("icmpLayer.ToBytes() = %s", err)
}
// Receive one extra byte to assert the length of the
// packet received in the case where the packet contains
// more data than expected.
len := int32(len(payload)) + 1
got, want := dut.Recv(t, env.socketFD, len, 0), payload
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
}
} else {
// Expected receive error, set a short receive timeout.
dut.SetSockOptTimeval(
t,
env.socketFD,
unix.SOL_SOCKET,
unix.SO_RCVTIMEO,
&unix.Timeval{
Sec: 1,
Usec: 0,
},
)
ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0)
if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
}
}
})
}
}
@@ -0,0 +1,265 @@
// Copyright 2021 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 generic_dgram_socket_send_recv_test
import (
"context"
"net"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
func maxUDPPayloadSize(addr net.IP) int {
if addr.To4() != nil {
return maxUDPv4PayloadSize
}
return maxUDPv6PayloadSize
}
func TestUDP(t *testing.T) {
runAllCombinations(t, &udpTest{})
}
type udpConn interface {
SrcPort(*testing.T) uint16
SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool)
Close(*testing.T)
}
type udpTestEnv struct {
socketFD int32
conn udpConn
layers testbench.Layers
}
type udpTest struct{}
func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv {
t.Helper()
var (
socketFD int32
outgoingUDP, incomingUDP testbench.UDP
)
// Tell the DUT to create a socket.
if bindTo != nil {
var remotePort uint16
socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo)
outgoingUDP.DstPort = &remotePort
incomingUDP.SrcPort = &remotePort
} else {
// An unbound socket will auto-bind to INADDR_ANY.
socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
}
t.Cleanup(func() {
dut.Close(t, socketFD)
})
if bindToDevice {
dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
}
// Create a socket on the test runner.
var conn udpConn
var ipLayer testbench.Layer
if addr := sendTo.To4(); addr != nil {
udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
conn = &udpConn
ipLayer = &testbench.IPv4{
DstAddr: testbench.Address(tcpip.AddrFrom4Slice(addr)),
}
} else {
udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
conn = &udpConn
ipLayer = &testbench.IPv6{
DstAddr: testbench.Address(tcpip.AddrFrom16Slice(sendTo.To16())),
}
}
t.Cleanup(func() {
conn.Close(t)
})
return udpTestEnv{
socketFD: socketFD,
conn: conn,
layers: testbench.Layers{
expectedEthLayer(t, dut, socketFD, sendTo),
ipLayer,
&incomingUDP,
},
}
}
func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
wantErrno := unix.Errno(0)
if sendTo.To4() == nil {
// If sendTo is an IPv6 address.
if bindTo.To4() != nil {
// But bindTo is an IPv4 address, we expect EAFNOSUPPORT.
wantErrno = unix.EAFNOSUPPORT
// TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets
// returns EAFNOSUPPORT after calling sendto with an IPv6 address.
if dut.Uname.IsGvisor() {
wantErrno = unix.EINVAL
}
}
} else {
// If sendTo is an IPv4 address.
if bindTo.Equal(dut.Net.RemoteIPv6) {
// if bindTo is dut's IPv6 address, we expect ENETUNREACH.
wantErrno = unix.ENETUNREACH
}
if !bindToDevice && !bindTo.Equal(dut.Net.RemoteIPv4) && (sendTo.Equal(net.IPv4bcast) || sendTo.Equal(net.IPv4allsys)) {
// if not binding to a device, bindTo is not dut's IPv4 addression and sendTo is
// 255.255.255.255 or 224.0.0.1, we expect ENETUNERACH.
wantErrno = unix.ENETUNREACH
}
}
expectPacket := true
// We don't expect an incoming packet if:
// 1. sendTo is dut itself.
if isRemoteAddr(dut, sendTo) {
expectPacket = false
}
// 2. we expect an error when sending the packet.
if wantErrno != unix.Errno(0) {
expectPacket = false
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)),
} {
t.Run(name, func(t *testing.T) {
var destSockaddr unix.Sockaddr
if sendTo4 := sendTo.To4(); sendTo4 != nil {
addr := unix.SockaddrInet4{
Port: int(env.conn.SrcPort(t)),
}
copy(addr.Addr[:], sendTo4)
destSockaddr = &addr
} else {
addr := unix.SockaddrInet6{
Port: int(env.conn.SrcPort(t)),
ZoneId: dut.Net.RemoteDevID,
}
copy(addr.Addr[:], sendTo.To16())
destSockaddr = &addr
}
// Tell the DUT to send a packet out the UDP socket.
gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr)
if gotErrno != wantErrno {
t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
}
if wantErrno != 0 {
return
}
if got, want := int(gotRet), len(payload); got != want {
t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
}
// Verify the test runner received a UDP packet with the
// correct payload.
want := append(env.layers, &testbench.Payload{
Bytes: payload,
})
if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
} else if ok && !expectPacket {
matchedFrame := got[len(got)-1]
t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
}
})
}
}
func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
subnetBroadcast := dut.Net.SubnetBroadcast()
expectPacket := true
switch {
case bindTo.Equal(sendTo):
case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4):
case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo):
case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo):
case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast):
default:
expectPacket = false
}
// TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor
// restricts ICMP sockets to receive only from unicast addresses.
if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) {
expectPacket = true
}
env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
maxPayloadSize := maxUDPPayloadSize(bindTo)
for name, payload := range map[string][]byte{
"empty": nil,
"small": []byte("hello world"),
"random1k": testbench.GenerateRandomPayload(t, maxPayloadSize),
} {
t.Run(name, func(t *testing.T) {
// Send a UDP packet from the test runner to the DUT.
env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload})
// Verify the behavior of the ICMP socket on the DUT.
if expectPacket {
// Receive one extra byte to assert the length of the
// packet received in the case where the packet contains
// more data than expected.
len := int32(len(payload)) + 1
got, want := dut.Recv(t, env.socketFD, len, 0), payload
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
}
} else {
// Expected receive error, set a short receive timeout.
dut.SetSockOptTimeval(
t,
env.socketFD,
unix.SOL_SOCKET,
unix.SO_RCVTIMEO,
&unix.Timeval{
Sec: 1,
Usec: 0,
},
)
ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0)
if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
}
}
})
}
}