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