gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/packetimpact/tests/generic_dgram_socket_send_recv_test.go (about)

     1  // Copyright 2021 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package generic_dgram_socket_send_recv_test
    16  
    17  import (
    18  	"context"
    19  	"flag"
    20  	"fmt"
    21  	"net"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/google/go-cmp/cmp"
    26  	"golang.org/x/sys/unix"
    27  	"gvisor.dev/gvisor/pkg/tcpip"
    28  	"gvisor.dev/gvisor/pkg/tcpip/header"
    29  	"gvisor.dev/gvisor/test/packetimpact/testbench"
    30  )
    31  
    32  const (
    33  	// Even though sockets allow larger datagrams we don't test it here as they
    34  	// need to be fragmented and written out as individual frames.
    35  
    36  	maxICMPv4PayloadSize = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.ICMPv4MinimumSize
    37  	maxICMPv6PayloadSize = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.ICMPv6MinimumSize
    38  	maxUDPv4PayloadSize  = header.IPv4MinimumMTU - header.EthernetMinimumSize - header.IPv4MinimumSize - header.UDPMinimumSize
    39  	maxUDPv6PayloadSize  = header.IPv6MinimumMTU - header.EthernetMinimumSize - header.IPv6MinimumSize - header.UDPMinimumSize
    40  )
    41  
    42  func maxUDPPayloadSize(addr net.IP) int {
    43  	if addr.To4() != nil {
    44  		return maxUDPv4PayloadSize
    45  	}
    46  	return maxUDPv6PayloadSize
    47  }
    48  
    49  func init() {
    50  	testbench.Initialize(flag.CommandLine)
    51  }
    52  
    53  func expectedEthLayer(t *testing.T, dut testbench.DUT, socketFD int32, sendTo net.IP) testbench.Layer {
    54  	t.Helper()
    55  	dst := func() tcpip.LinkAddress {
    56  		if isBroadcast(dut, sendTo) {
    57  			dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
    58  
    59  			// When sending to broadcast (subnet or limited), the expected ethernet
    60  			// address is also broadcast.
    61  			return header.EthernetBroadcastAddress
    62  		}
    63  		if sendTo.IsMulticast() {
    64  			if sendTo4 := sendTo.To4(); sendTo4 != nil {
    65  				return header.EthernetAddressFromMulticastIPv4Address(tcpip.AddrFrom4Slice(sendTo4))
    66  			}
    67  			return header.EthernetAddressFromMulticastIPv6Address(tcpip.AddrFrom16Slice(sendTo.To16()))
    68  		}
    69  		return ""
    70  	}()
    71  	var ether testbench.Ether
    72  	if len(dst) != 0 {
    73  		ether.DstAddr = &dst
    74  	}
    75  	return &ether
    76  }
    77  
    78  type protocolTest interface {
    79  	Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
    80  	Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool)
    81  }
    82  
    83  func TestICMPv4(t *testing.T) {
    84  	runAllCombinations(t, &icmpV4Test{})
    85  }
    86  
    87  func TestICMPv6(t *testing.T) {
    88  	runAllCombinations(t, &icmpV6Test{})
    89  }
    90  
    91  func TestUDP(t *testing.T) {
    92  	runAllCombinations(t, &udpTest{})
    93  }
    94  
    95  func runAllCombinations(t *testing.T, proto protocolTest) {
    96  	dut := testbench.NewDUT(t)
    97  	subnetBroadcast := dut.Net.SubnetBroadcast()
    98  	// Test every combination of bound/unbound, broadcast/multicast/unicast
    99  	// bound/destination address, and bound/not-bound to device.
   100  	for _, bindTo := range []net.IP{
   101  		nil, // Do not bind.
   102  		net.IPv4zero,
   103  		net.IPv4bcast,
   104  		net.IPv4allsys,
   105  		net.IPv6zero,
   106  		subnetBroadcast,
   107  		dut.Net.RemoteIPv4,
   108  		dut.Net.RemoteIPv6,
   109  	} {
   110  		t.Run(fmt.Sprintf("bindTo=%s", bindTo), func(t *testing.T) {
   111  			for _, sendTo := range []net.IP{
   112  				net.IPv4bcast,
   113  				net.IPv4allsys,
   114  				subnetBroadcast,
   115  				dut.Net.LocalIPv4,
   116  				dut.Net.LocalIPv6,
   117  				dut.Net.RemoteIPv4,
   118  				dut.Net.RemoteIPv6,
   119  			} {
   120  				t.Run(fmt.Sprintf("sendTo=%s", sendTo), func(t *testing.T) {
   121  					for _, bindToDevice := range []bool{true, false} {
   122  						t.Run(fmt.Sprintf("bindToDevice=%t", bindToDevice), func(t *testing.T) {
   123  							t.Run("Send", func(t *testing.T) {
   124  								proto.Send(t, dut, bindTo, sendTo, bindToDevice)
   125  							})
   126  							t.Run("Receive", func(t *testing.T) {
   127  								proto.Receive(t, dut, bindTo, sendTo, bindToDevice)
   128  							})
   129  						})
   130  					}
   131  				})
   132  			}
   133  		})
   134  	}
   135  }
   136  
   137  type icmpV4TestEnv struct {
   138  	socketFD int32
   139  	ident    uint16
   140  	conn     testbench.IPv4Conn
   141  	layers   testbench.Layers
   142  }
   143  
   144  type icmpV4Test struct{}
   145  
   146  func (test *icmpV4Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV4TestEnv {
   147  	t.Helper()
   148  
   149  	// Tell the DUT to create a socket.
   150  	var socketFD int32
   151  	var ident uint16
   152  
   153  	if bindTo != nil {
   154  		socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMP, bindTo)
   155  	} else {
   156  		// An unbound socket will auto-bind to INADDR_ANY.
   157  		socketFD = dut.Socket(t, unix.AF_INET, unix.SOCK_DGRAM, unix.IPPROTO_ICMP)
   158  	}
   159  	t.Cleanup(func() {
   160  		dut.Close(t, socketFD)
   161  	})
   162  
   163  	if bindToDevice {
   164  		dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
   165  	}
   166  
   167  	// Create a socket on the test runner.
   168  	conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
   169  	t.Cleanup(func() {
   170  		conn.Close(t)
   171  	})
   172  
   173  	dstAddr := sendTo.To4()
   174  	return icmpV4TestEnv{
   175  		socketFD: socketFD,
   176  		ident:    ident,
   177  		conn:     conn,
   178  		layers: testbench.Layers{
   179  			expectedEthLayer(t, dut, socketFD, sendTo),
   180  			&testbench.IPv4{
   181  				DstAddr: &dstAddr,
   182  			},
   183  		},
   184  	}
   185  }
   186  
   187  func (test *icmpV4Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   188  	if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
   189  		// ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
   190  		// addresses.
   191  		return
   192  	}
   193  
   194  	isV4 := sendTo.To4() != nil
   195  
   196  	// TODO(gvisor.dev/issue/5681): Remove this case once ICMP sockets allow
   197  	// sending to broadcast and multicast addresses.
   198  	if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && isV4 && isBroadcastOrMulticast(dut, sendTo) {
   199  		// expectPacket cannot be false. In some cases the packet will send, but
   200  		// with IPv4 destination incorrectly set to RemoteIPv4. It's all bad and
   201  		// not worth the effort to create a special case when this occurs.
   202  		t.Skip("TODO(gvisor.dev/issue/5681): Allow sending to broadcast and multicast addresses with ICMP sockets.")
   203  	}
   204  
   205  	expectNetworkUnreachable := true
   206  	// We don't expect ENETUNREACH if any of the follwing is true:
   207  	// 1. bindTo is specfied.
   208  	if !bindTo.Equal(net.IPv4zero) {
   209  		expectNetworkUnreachable = false
   210  	}
   211  	// 2. We are binding to a device.
   212  	if bindToDevice {
   213  		expectNetworkUnreachable = false
   214  	}
   215  	// 3. sendTo is neither 224.0.0.1 nor 255.255.255.255.
   216  	if !sendTo.Equal(net.IPv4bcast) && !sendTo.Equal(net.IPv4allsys) {
   217  		expectNetworkUnreachable = false
   218  	}
   219  
   220  	expectPacket := true
   221  	// We don't expect an incoming packet if any of the following is true:
   222  	// 1. sendTo is not an ipv4 address.
   223  	if !isV4 {
   224  		expectPacket = false
   225  	}
   226  	// 2. sendTo is the dut itself.
   227  	if sendTo.Equal(dut.Net.RemoteIPv4) {
   228  		expectPacket = false
   229  	}
   230  	// 3. we are expecting ENETUNREACH.
   231  	if expectNetworkUnreachable {
   232  		expectPacket = false
   233  	}
   234  
   235  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   236  
   237  	for name, payload := range map[string][]byte{
   238  		"empty":    nil,
   239  		"small":    []byte("hello world"),
   240  		"random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
   241  	} {
   242  		t.Run(name, func(t *testing.T) {
   243  			icmpLayer := &testbench.ICMPv4{
   244  				Type:    testbench.ICMPv4Type(header.ICMPv4Echo),
   245  				Payload: payload,
   246  			}
   247  			bytes, err := icmpLayer.ToBytes()
   248  			if err != nil {
   249  				t.Fatalf("icmpLayer.ToBytes() = %s", err)
   250  			}
   251  			destSockaddr := unix.SockaddrInet4{}
   252  			copy(destSockaddr.Addr[:], sendTo.To4())
   253  
   254  			// Tell the DUT to send a packet out the ICMP socket.
   255  			ret, err := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
   256  			if expectNetworkUnreachable {
   257  				if !(ret == -1 && err == unix.ENETUNREACH) {
   258  					t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (-1, %s)", ret, err, unix.ENETUNREACH)
   259  				}
   260  			} else {
   261  				if !(int(ret) == len(bytes) && err == unix.Errno(0)) {
   262  					t.Fatalf("got dut.SendToWithErrno = (%d, %s), want (%d, 0)", ret, err, len(bytes))
   263  				}
   264  			}
   265  
   266  			// Verify the test runner received an ICMP packet with the correctly
   267  			// set "ident".
   268  			if env.ident != 0 {
   269  				icmpLayer.Ident = &env.ident
   270  			}
   271  			want := append(env.layers, icmpLayer)
   272  			if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
   273  				t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
   274  			} else if ok && !expectPacket {
   275  				matchedFrame := got[len(got)-1]
   276  				t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
   277  			}
   278  		})
   279  	}
   280  }
   281  
   282  func (test *icmpV4Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   283  	if bindTo.To4() == nil || isBroadcastOrMulticast(dut, bindTo) {
   284  		// ICMPv4 sockets cannot bind to IPv6, broadcast, or multicast
   285  		// addresses.
   286  		return
   287  	}
   288  
   289  	expectPacket := (bindTo.Equal(dut.Net.RemoteIPv4) || bindTo.Equal(net.IPv4zero)) && sendTo.Equal(dut.Net.RemoteIPv4)
   290  
   291  	// TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
   292  	// restricts ICMP sockets to receive only from unicast addresses.
   293  	if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv4zero) && isBroadcastOrMulticast(dut, sendTo) {
   294  		expectPacket = true
   295  	}
   296  
   297  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   298  
   299  	for name, payload := range map[string][]byte{
   300  		"empty":    nil,
   301  		"small":    []byte("hello world"),
   302  		"random1k": testbench.GenerateRandomPayload(t, maxICMPv4PayloadSize),
   303  	} {
   304  		t.Run(name, func(t *testing.T) {
   305  			icmpLayer := &testbench.ICMPv4{
   306  				Type:    testbench.ICMPv4Type(header.ICMPv4EchoReply),
   307  				Payload: payload,
   308  			}
   309  			if env.ident != 0 {
   310  				icmpLayer.Ident = &env.ident
   311  			}
   312  
   313  			// Send an ICMPv4 packet from the test runner to the DUT.
   314  			frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
   315  			env.conn.SendFrame(t, frame)
   316  
   317  			// Verify the behavior of the ICMP socket on the DUT.
   318  			if expectPacket {
   319  				payload, err := icmpLayer.ToBytes()
   320  				if err != nil {
   321  					t.Fatalf("icmpLayer.ToBytes() = %s", err)
   322  				}
   323  
   324  				// Receive one extra byte to assert the length of the
   325  				// packet received in the case where the packet contains
   326  				// more data than expected.
   327  				len := int32(len(payload)) + 1
   328  				got, want := dut.Recv(t, env.socketFD, len, 0), payload
   329  				if diff := cmp.Diff(want, got); diff != "" {
   330  					t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
   331  				}
   332  			} else {
   333  				// Expected receive error, set a short receive timeout.
   334  				dut.SetSockOptTimeval(
   335  					t,
   336  					env.socketFD,
   337  					unix.SOL_SOCKET,
   338  					unix.SO_RCVTIMEO,
   339  					&unix.Timeval{
   340  						Sec:  1,
   341  						Usec: 0,
   342  					},
   343  				)
   344  				ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv4PayloadSize, 0)
   345  				if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
   346  					t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
   347  				}
   348  			}
   349  		})
   350  	}
   351  }
   352  
   353  type icmpV6TestEnv struct {
   354  	socketFD int32
   355  	ident    uint16
   356  	conn     testbench.IPv6Conn
   357  	layers   testbench.Layers
   358  }
   359  
   360  // icmpV6Test and icmpV4Test look substantially similar at first look, but have
   361  // enough subtle differences in setup and test expectations to discourage
   362  // refactoring:
   363  //   - Different IP layers
   364  //   - Different testbench.Connections
   365  //   - Different UNIX domain and proto arguments
   366  //   - Different expectPacket and wantErrno for send and receive
   367  type icmpV6Test struct{}
   368  
   369  func (test *icmpV6Test) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) icmpV6TestEnv {
   370  	t.Helper()
   371  
   372  	// Tell the DUT to create a socket.
   373  	var socketFD int32
   374  	var ident uint16
   375  
   376  	if bindTo != nil {
   377  		socketFD, ident = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6, bindTo)
   378  	} else {
   379  		// An unbound socket will auto-bind to IN6ADDR_ANY_INIT.
   380  		socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_ICMPV6)
   381  	}
   382  	t.Cleanup(func() {
   383  		dut.Close(t, socketFD)
   384  	})
   385  
   386  	if bindToDevice {
   387  		dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
   388  	}
   389  
   390  	// Create a socket on the test runner.
   391  	conn := dut.Net.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
   392  	t.Cleanup(func() {
   393  		conn.Close(t)
   394  	})
   395  
   396  	dstAddr := sendTo.To16()
   397  	return icmpV6TestEnv{
   398  		socketFD: socketFD,
   399  		ident:    ident,
   400  		conn:     conn,
   401  		layers: testbench.Layers{
   402  			expectedEthLayer(t, dut, socketFD, sendTo),
   403  			&testbench.IPv6{
   404  				DstAddr: &dstAddr,
   405  			},
   406  		},
   407  	}
   408  }
   409  
   410  func (test *icmpV6Test) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   411  	if bindTo.To4() != nil || bindTo.IsMulticast() {
   412  		// ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
   413  		return
   414  	}
   415  
   416  	expectPacket := sendTo.Equal(dut.Net.LocalIPv6)
   417  	wantErrno := unix.Errno(0)
   418  
   419  	if sendTo.To4() != nil {
   420  		wantErrno = unix.EINVAL
   421  
   422  		// TODO(gvisor.dev/issue/5966): Remove this if statement once ICMPv6 sockets
   423  		// return EINVAL after calling sendto with an IPv4 address.
   424  		if dut.Uname.IsGvisor() || dut.Uname.IsFuchsia() {
   425  			wantErrno = unix.ENETUNREACH
   426  			if !bindTo.Equal(dut.Net.RemoteIPv6) && (bindToDevice || isInTestSubnetV4(dut, sendTo)) {
   427  				wantErrno = unix.Errno(0)
   428  			}
   429  		}
   430  	}
   431  
   432  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   433  
   434  	for name, payload := range map[string][]byte{
   435  		"empty":    nil,
   436  		"small":    []byte("hello world"),
   437  		"random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
   438  	} {
   439  		t.Run(name, func(t *testing.T) {
   440  			icmpLayer := &testbench.ICMPv6{
   441  				Type:    testbench.ICMPv6Type(header.ICMPv6EchoRequest),
   442  				Payload: payload,
   443  			}
   444  			bytes, err := icmpLayer.ToBytes()
   445  			if err != nil {
   446  				t.Fatalf("icmpLayer.ToBytes() = %s", err)
   447  			}
   448  			destSockaddr := unix.SockaddrInet6{
   449  				ZoneId: dut.Net.RemoteDevID,
   450  			}
   451  			copy(destSockaddr.Addr[:], sendTo.To16())
   452  
   453  			// Tell the DUT to send a packet out the ICMPv6 socket.
   454  			gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, bytes, 0, &destSockaddr)
   455  
   456  			if gotErrno != wantErrno {
   457  				t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
   458  			}
   459  			if wantErrno != 0 {
   460  				return
   461  			}
   462  			if got, want := int(gotRet), len(bytes); got != want {
   463  				t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
   464  			}
   465  
   466  			// Verify the test runner received an ICMPv6 packet with the
   467  			// correctly set "ident".
   468  			if env.ident != 0 {
   469  				icmpLayer.Ident = &env.ident
   470  			}
   471  			want := append(env.layers, icmpLayer)
   472  			if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
   473  				t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
   474  			} else if ok && !expectPacket {
   475  				matchedFrame := got[len(got)-1]
   476  				t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
   477  			}
   478  		})
   479  	}
   480  }
   481  
   482  func (test *icmpV6Test) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   483  	if bindTo.To4() != nil || bindTo.IsMulticast() {
   484  		// ICMPv6 sockets cannot bind to IPv4 or multicast addresses.
   485  		return
   486  	}
   487  
   488  	expectPacket := true
   489  	switch {
   490  	case bindTo.Equal(dut.Net.RemoteIPv6) && sendTo.Equal(dut.Net.RemoteIPv6):
   491  	case bindTo.Equal(net.IPv6zero) && sendTo.Equal(dut.Net.RemoteIPv6):
   492  	case bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv6linklocalallnodes):
   493  	default:
   494  		expectPacket = false
   495  	}
   496  
   497  	// TODO(gvisor.dev/issue/5763): Remove this if statement once gVisor
   498  	// restricts ICMP sockets to receive only from unicast addresses.
   499  	if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && isBroadcastOrMulticast(dut, sendTo) {
   500  		expectPacket = false
   501  	}
   502  
   503  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   504  
   505  	for name, payload := range map[string][]byte{
   506  		"empty":    nil,
   507  		"small":    []byte("hello world"),
   508  		"random1k": testbench.GenerateRandomPayload(t, maxICMPv6PayloadSize),
   509  	} {
   510  		t.Run(name, func(t *testing.T) {
   511  			icmpLayer := &testbench.ICMPv6{
   512  				Type:    testbench.ICMPv6Type(header.ICMPv6EchoReply),
   513  				Payload: payload,
   514  			}
   515  			if env.ident != 0 {
   516  				icmpLayer.Ident = &env.ident
   517  			}
   518  
   519  			// Send an ICMPv6 packet from the test runner to the DUT.
   520  			frame := env.conn.CreateFrame(t, env.layers, icmpLayer)
   521  			env.conn.SendFrame(t, frame)
   522  
   523  			// Verify the behavior of the ICMPv6 socket on the DUT.
   524  			if expectPacket {
   525  				payload, err := icmpLayer.ToBytes()
   526  				if err != nil {
   527  					t.Fatalf("icmpLayer.ToBytes() = %s", err)
   528  				}
   529  
   530  				// Receive one extra byte to assert the length of the
   531  				// packet received in the case where the packet contains
   532  				// more data than expected.
   533  				len := int32(len(payload)) + 1
   534  				got, want := dut.Recv(t, env.socketFD, len, 0), payload
   535  				if diff := cmp.Diff(want, got); diff != "" {
   536  					t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
   537  				}
   538  			} else {
   539  				// Expected receive error, set a short receive timeout.
   540  				dut.SetSockOptTimeval(
   541  					t,
   542  					env.socketFD,
   543  					unix.SOL_SOCKET,
   544  					unix.SO_RCVTIMEO,
   545  					&unix.Timeval{
   546  						Sec:  1,
   547  						Usec: 0,
   548  					},
   549  				)
   550  				ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, maxICMPv6PayloadSize, 0)
   551  				if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
   552  					t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
   553  				}
   554  			}
   555  		})
   556  	}
   557  }
   558  
   559  type udpConn interface {
   560  	SrcPort(*testing.T) uint16
   561  	SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
   562  	ListenForFrame(*testing.T, testbench.Layers, time.Duration) ([]testbench.Layers, bool)
   563  	Close(*testing.T)
   564  }
   565  
   566  type udpTestEnv struct {
   567  	socketFD int32
   568  	conn     udpConn
   569  	layers   testbench.Layers
   570  }
   571  
   572  type udpTest struct{}
   573  
   574  func (test *udpTest) setup(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) udpTestEnv {
   575  	t.Helper()
   576  
   577  	var (
   578  		socketFD                 int32
   579  		outgoingUDP, incomingUDP testbench.UDP
   580  	)
   581  
   582  	// Tell the DUT to create a socket.
   583  	if bindTo != nil {
   584  		var remotePort uint16
   585  		socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, bindTo)
   586  		outgoingUDP.DstPort = &remotePort
   587  		incomingUDP.SrcPort = &remotePort
   588  	} else {
   589  		// An unbound socket will auto-bind to INADDR_ANY.
   590  		socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
   591  	}
   592  	t.Cleanup(func() {
   593  		dut.Close(t, socketFD)
   594  	})
   595  
   596  	if bindToDevice {
   597  		dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
   598  	}
   599  
   600  	// Create a socket on the test runner.
   601  	var conn udpConn
   602  	var ipLayer testbench.Layer
   603  	if addr := sendTo.To4(); addr != nil {
   604  		udpConn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
   605  		conn = &udpConn
   606  		ipLayer = &testbench.IPv4{
   607  			DstAddr: testbench.Address(tcpip.AddrFrom4Slice(addr)),
   608  		}
   609  	} else {
   610  		udpConn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
   611  		conn = &udpConn
   612  		ipLayer = &testbench.IPv6{
   613  			DstAddr: testbench.Address(tcpip.AddrFrom16Slice(sendTo.To16())),
   614  		}
   615  	}
   616  	t.Cleanup(func() {
   617  		conn.Close(t)
   618  	})
   619  
   620  	return udpTestEnv{
   621  		socketFD: socketFD,
   622  		conn:     conn,
   623  		layers: testbench.Layers{
   624  			expectedEthLayer(t, dut, socketFD, sendTo),
   625  			ipLayer,
   626  			&incomingUDP,
   627  		},
   628  	}
   629  }
   630  
   631  func (test *udpTest) Send(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   632  	wantErrno := unix.Errno(0)
   633  
   634  	if sendTo.To4() == nil {
   635  		// If sendTo is an IPv6 address.
   636  		if bindTo.To4() != nil {
   637  			// But bindTo is an IPv4 address, we expect EAFNOSUPPORT.
   638  			wantErrno = unix.EAFNOSUPPORT
   639  
   640  			// TODO(gvisor.dev/issue/5967): Remove this if statement once UDPv4 sockets
   641  			// returns EAFNOSUPPORT after calling sendto with an IPv6 address.
   642  			if dut.Uname.IsGvisor() {
   643  				wantErrno = unix.EINVAL
   644  			}
   645  		}
   646  	} else {
   647  		// If sendTo is an IPv4 address.
   648  		if bindTo.Equal(dut.Net.RemoteIPv6) {
   649  			// if bindTo is dut's IPv6 address, we expect ENETUNREACH.
   650  			wantErrno = unix.ENETUNREACH
   651  		}
   652  
   653  		if !bindToDevice && !bindTo.Equal(dut.Net.RemoteIPv4) && (sendTo.Equal(net.IPv4bcast) || sendTo.Equal(net.IPv4allsys)) {
   654  			// if not binding to a device, bindTo is not dut's IPv4 addression and sendTo is
   655  			// 255.255.255.255 or 224.0.0.1, we expect ENETUNERACH.
   656  			wantErrno = unix.ENETUNREACH
   657  		}
   658  	}
   659  
   660  	expectPacket := true
   661  	// We don't expect an incoming packet if:
   662  	// 1. sendTo is dut itself.
   663  	if isRemoteAddr(dut, sendTo) {
   664  		expectPacket = false
   665  	}
   666  	// 2. we expect an error when sending the packet.
   667  	if wantErrno != unix.Errno(0) {
   668  		expectPacket = false
   669  	}
   670  
   671  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   672  
   673  	for name, payload := range map[string][]byte{
   674  		"empty":    nil,
   675  		"small":    []byte("hello world"),
   676  		"random1k": testbench.GenerateRandomPayload(t, maxUDPPayloadSize(bindTo)),
   677  	} {
   678  		t.Run(name, func(t *testing.T) {
   679  			var destSockaddr unix.Sockaddr
   680  			if sendTo4 := sendTo.To4(); sendTo4 != nil {
   681  				addr := unix.SockaddrInet4{
   682  					Port: int(env.conn.SrcPort(t)),
   683  				}
   684  				copy(addr.Addr[:], sendTo4)
   685  				destSockaddr = &addr
   686  			} else {
   687  				addr := unix.SockaddrInet6{
   688  					Port:   int(env.conn.SrcPort(t)),
   689  					ZoneId: dut.Net.RemoteDevID,
   690  				}
   691  				copy(addr.Addr[:], sendTo.To16())
   692  				destSockaddr = &addr
   693  			}
   694  
   695  			// Tell the DUT to send a packet out the UDP socket.
   696  			gotRet, gotErrno := dut.SendToWithErrno(context.Background(), t, env.socketFD, payload, 0, destSockaddr)
   697  
   698  			if gotErrno != wantErrno {
   699  				t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (_, %s), want = (_, %s)", env.socketFD, sendTo, gotErrno, wantErrno)
   700  			}
   701  			if wantErrno != 0 {
   702  				return
   703  			}
   704  			if got, want := int(gotRet), len(payload); got != want {
   705  				t.Fatalf("got dut.SendToWithErrno(_, _, %d, _, _, %s) = (%d, _), want = (%d, _)", env.socketFD, sendTo, got, want)
   706  			}
   707  
   708  			// Verify the test runner received a UDP packet with the
   709  			// correct payload.
   710  			want := append(env.layers, &testbench.Payload{
   711  				Bytes: payload,
   712  			})
   713  			if got, ok := env.conn.ListenForFrame(t, want, time.Second); !ok && expectPacket {
   714  				t.Fatalf("did not receive expected frame matching %s\nGot frames: %s", want, got)
   715  			} else if ok && !expectPacket {
   716  				matchedFrame := got[len(got)-1]
   717  				t.Fatalf("got unexpected frame matching %s\nGot frame: %s", want, matchedFrame)
   718  			}
   719  		})
   720  	}
   721  }
   722  
   723  func (test *udpTest) Receive(t *testing.T, dut testbench.DUT, bindTo, sendTo net.IP, bindToDevice bool) {
   724  	subnetBroadcast := dut.Net.SubnetBroadcast()
   725  
   726  	expectPacket := true
   727  	switch {
   728  	case bindTo.Equal(sendTo):
   729  	case bindTo.Equal(net.IPv4zero) && sameIPVersion(bindTo, sendTo) && !sendTo.Equal(dut.Net.LocalIPv4):
   730  	case bindTo.Equal(net.IPv6zero) && isBroadcast(dut, sendTo):
   731  	case bindTo.Equal(net.IPv6zero) && isRemoteAddr(dut, sendTo):
   732  	case bindTo.Equal(subnetBroadcast) && sendTo.Equal(subnetBroadcast):
   733  	default:
   734  		expectPacket = false
   735  	}
   736  
   737  	// TODO(gvisor.dev/issue/5956): Remove this if statement once gVisor
   738  	// restricts ICMP sockets to receive only from unicast addresses.
   739  	if (dut.Uname.IsGvisor() || dut.Uname.IsFuchsia()) && bindTo.Equal(net.IPv6zero) && sendTo.Equal(net.IPv4allsys) {
   740  		expectPacket = true
   741  	}
   742  
   743  	env := test.setup(t, dut, bindTo, sendTo, bindToDevice)
   744  	maxPayloadSize := maxUDPPayloadSize(bindTo)
   745  
   746  	for name, payload := range map[string][]byte{
   747  		"empty":    nil,
   748  		"small":    []byte("hello world"),
   749  		"random1k": testbench.GenerateRandomPayload(t, maxPayloadSize),
   750  	} {
   751  		t.Run(name, func(t *testing.T) {
   752  			// Send a UDP packet from the test runner to the DUT.
   753  			env.conn.SendFrame(t, env.layers, &testbench.Payload{Bytes: payload})
   754  
   755  			// Verify the behavior of the ICMP socket on the DUT.
   756  			if expectPacket {
   757  				// Receive one extra byte to assert the length of the
   758  				// packet received in the case where the packet contains
   759  				// more data than expected.
   760  				len := int32(len(payload)) + 1
   761  				got, want := dut.Recv(t, env.socketFD, len, 0), payload
   762  				if diff := cmp.Diff(want, got); diff != "" {
   763  					t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
   764  				}
   765  			} else {
   766  				// Expected receive error, set a short receive timeout.
   767  				dut.SetSockOptTimeval(
   768  					t,
   769  					env.socketFD,
   770  					unix.SOL_SOCKET,
   771  					unix.SO_RCVTIMEO,
   772  					&unix.Timeval{
   773  						Sec:  1,
   774  						Usec: 0,
   775  					},
   776  				)
   777  				ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, env.socketFD, int32(maxPayloadSize), 0)
   778  				if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK {
   779  					t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
   780  				}
   781  			}
   782  		})
   783  	}
   784  }
   785  
   786  func isBroadcast(dut testbench.DUT, ip net.IP) bool {
   787  	return ip.Equal(net.IPv4bcast) || ip.Equal(dut.Net.SubnetBroadcast())
   788  }
   789  
   790  func isBroadcastOrMulticast(dut testbench.DUT, ip net.IP) bool {
   791  	return isBroadcast(dut, ip) || ip.IsMulticast()
   792  }
   793  
   794  func sameIPVersion(a, b net.IP) bool {
   795  	return (a.To4() == nil) == (b.To4() == nil)
   796  }
   797  
   798  func isRemoteAddr(dut testbench.DUT, ip net.IP) bool {
   799  	return ip.Equal(dut.Net.RemoteIPv4) || ip.Equal(dut.Net.RemoteIPv6)
   800  }
   801  
   802  func isInTestSubnetV4(dut testbench.DUT, ip net.IP) bool {
   803  	network := net.IPNet{
   804  		IP:   dut.Net.LocalIPv4,
   805  		Mask: net.CIDRMask(dut.Net.IPv4PrefixLength, net.IPv4len*8),
   806  	}
   807  	return network.Contains(ip)
   808  }