mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
04e7902bb5
commit
9d3ea997e2
@@ -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(
|
||||
|
||||
@@ -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 ðer
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user