From 6012fe9b5965a2f285f2f99312e6cb7ae84b5fa8 Mon Sep 17 00:00:00 2001 From: Marina Ciocea Date: Thu, 28 Jan 2021 06:23:04 -0800 Subject: Respect SO_BINDTODEVICE in unconnected UDP writes Previously, sending on an unconnected UDP socket would ignore the SO_BINDTODEVICE option. Send on the configured interface when an UDP socket is bound to an interface through setsockop SO_BINDTODEVICE. Add packetimpact tests exercising UDP reads and writes with every combination of bound/unbound, broadcast/multicast/unicast destination, and bound/not-bound to device. PiperOrigin-RevId: 354299670 --- pkg/tcpip/tcpip.go | 2 +- pkg/tcpip/transport/udp/endpoint.go | 3 + test/packetimpact/runner/defs.bzl | 3 - test/packetimpact/testbench/connections.go | 40 +++ test/packetimpact/testbench/dut.go | 2 +- test/packetimpact/tests/BUILD | 14 +- .../tests/udp_recv_mcast_bcast_test.go | 115 ------- .../packetimpact/tests/udp_send_recv_dgram_test.go | 338 +++++++++++++++++---- 8 files changed, 329 insertions(+), 188 deletions(-) delete mode 100644 test/packetimpact/tests/udp_recv_mcast_bcast_test.go diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index e70ae69ef..c023152f1 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -662,7 +662,7 @@ type Endpoint interface { // connected returns nil. Calling connect again results in ErrAlreadyConnected. // Anything else -- the attempt to connect failed. // - // If address.Addr is empty, this means that Enpoint has to be + // If address.Addr is empty, this means that Endpoint has to be // disconnected if this is supported, otherwise // ErrAddressFamilyNotSupported must be returned. Connect(address FullAddress) *Error diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index 4988ba29b..d2f7b6e85 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -480,6 +480,9 @@ func (e *endpoint) write(p tcpip.Payloader, opts tcpip.WriteOptions) (int64, *tc // Reject destination address if it goes through a different // NIC than the endpoint was bound to. nicID := to.NIC + if nicID == 0 { + nicID = tcpip.NICID(e.ops.GetBindToDevice()) + } if e.BindNICID != 0 { if nicID != 0 && nicID != e.BindNICID { return 0, tcpip.ErrNoRoute diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 2b9bfac76..a7c46781f 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -174,9 +174,6 @@ ALL_TESTS = [ PacketimpactTestInfo( name = "udp_discard_mcast_source_addr", ), - PacketimpactTestInfo( - name = "udp_recv_mcast_bcast", - ), PacketimpactTestInfo( name = "udp_any_addr_recv_unicast", ), diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index 576577310..1453ac232 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -1008,6 +1008,13 @@ func (conn *UDPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 { return sa } +// SrcPort returns the source port of this connection. +func (conn *UDPIPv4) SrcPort(t *testing.T) uint16 { + t.Helper() + + return *conn.udpState(t).out.SrcPort +} + // Send sends a packet with reasonable defaults, potentially overriding the UDP // layer and adding additionLayers. func (conn *UDPIPv4) Send(t *testing.T, udp UDP, additionalLayers ...Layer) { @@ -1024,6 +1031,11 @@ func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ... (*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...) } +// SendFrame sends a frame on the wire and updates the state of all layers. +func (conn *UDPIPv4) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) { + (*Connection)(conn).send(t, overrideLayers, additionalLayers...) +} + // Expect expects a frame with the UDP layer matching the provided UDP within // the timeout specified. If it doesn't arrive in time, an error is returned. func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) { @@ -1053,6 +1065,14 @@ func (conn *UDPIPv4) ExpectData(t *testing.T, udp UDP, payload Payload, timeout return (*Connection)(conn).ExpectFrame(t, expected, timeout) } +// ExpectFrame expects a frame that matches the provided Layers within the +// timeout specified. If it doesn't arrive in time, an error is returned. +func (conn *UDPIPv4) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) { + t.Helper() + + return (*Connection)(conn).ExpectFrame(t, frame, timeout) +} + // Close frees associated resources held by the UDPIPv4 connection. func (conn *UDPIPv4) Close(t *testing.T) { t.Helper() @@ -1136,6 +1156,13 @@ func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *unix.SockaddrInet6 return sa } +// SrcPort returns the source port of this connection. +func (conn *UDPIPv6) SrcPort(t *testing.T) uint16 { + t.Helper() + + return *conn.udpState(t).out.SrcPort +} + // Send sends a packet with reasonable defaults, potentially overriding the UDP // layer and adding additionLayers. func (conn *UDPIPv6) Send(t *testing.T, udp UDP, additionalLayers ...Layer) { @@ -1152,6 +1179,11 @@ func (conn *UDPIPv6) SendIPv6(t *testing.T, ip IPv6, udp UDP, additionalLayers . (*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...) } +// SendFrame sends a frame on the wire and updates the state of all layers. +func (conn *UDPIPv6) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) { + (*Connection)(conn).send(t, overrideLayers, additionalLayers...) +} + // Expect expects a frame with the UDP layer matching the provided UDP within // the timeout specified. If it doesn't arrive in time, an error is returned. func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) { @@ -1181,6 +1213,14 @@ func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout return (*Connection)(conn).ExpectFrame(t, expected, timeout) } +// ExpectFrame expects a frame that matches the provided Layers within the +// timeout specified. If it doesn't arrive in time, an error is returned. +func (conn *UDPIPv6) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) { + t.Helper() + + return (*Connection)(conn).ExpectFrame(t, frame, timeout) +} + // Close frees associated resources held by the UDPIPv6 connection. func (conn *UDPIPv6) Close(t *testing.T) { t.Helper() diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index 66a0255b8..d40890e12 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -544,7 +544,7 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, } resp, err := dut.posixServer.SendTo(ctx, &req) if err != nil { - t.Fatalf("faled to call SendTo: %s", err) + t.Fatalf("failed to call SendTo: %s", err) } return resp.GetRet(), syscall.Errno(resp.GetErrno_()) } diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 42aad541f..baa3ae5e9 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -37,18 +37,6 @@ packetimpact_testbench( ], ) -packetimpact_testbench( - name = "udp_recv_mcast_bcast", - srcs = ["udp_recv_mcast_bcast_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_any_addr_recv_unicast", srcs = ["udp_any_addr_recv_unicast_test.go"], @@ -340,6 +328,8 @@ packetimpact_testbench( name = "udp_send_recv_dgram", srcs = ["udp_send_recv_dgram_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", diff --git a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go deleted file mode 100644 index b29c07825..000000000 --- a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright 2020 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package udp_recv_mcast_bcast_test - -import ( - "context" - "flag" - "fmt" - "net" - "syscall" - "testing" - - "github.com/google/go-cmp/cmp" - "golang.org/x/sys/unix" - "gvisor.dev/gvisor/pkg/tcpip" - "gvisor.dev/gvisor/test/packetimpact/testbench" -) - -func init() { - testbench.Initialize(flag.CommandLine) -} - -func TestUDPRecvMcastBcast(t *testing.T) { - dut := testbench.NewDUT(t) - subnetBcastAddr := broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)) - for _, v := range []struct { - bound, to net.IP - }{ - {bound: net.IPv4zero, to: subnetBcastAddr}, - {bound: net.IPv4zero, to: net.IPv4bcast}, - {bound: net.IPv4zero, to: net.IPv4allsys}, - - {bound: subnetBcastAddr, to: subnetBcastAddr}, - - // FIXME(gvisor.dev/issue/4896): Previously by the time subnetBcastAddr is - // created, IPv4PrefixLength is still 0 because genPseudoFlags is not called - // yet, it was only called in NewDUT, so the test didn't do what the author - // original intended to and becomes failing because we process all flags at - // the very beginning. - // - // {bound: subnetBcastAddr, to: net.IPv4bcast}, - - {bound: net.IPv4bcast, to: net.IPv4bcast}, - {bound: net.IPv4allsys, to: net.IPv4allsys}, - } { - t.Run(fmt.Sprintf("bound=%s,to=%s", v.bound, v.to), func(t *testing.T) { - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, v.bound) - defer dut.Close(t, boundFD) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) - conn.SendIP( - t, - testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(v.to.To4()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: payload}, - ) - got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload - if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) - } - }) - } -} - -func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) { - dut := testbench.NewDUT(t) - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4) - dut.SetSockOptTimeval(t, boundFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 1, Usec: 0}) - defer dut.Close(t, boundFD) - conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - defer conn.Close(t) - - for _, to := range []net.IP{ - broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)), - net.IPv4(255, 255, 255, 255), - net.IPv4(224, 0, 0, 1), - } { - t.Run(fmt.Sprint("to=%s", to), func(t *testing.T) { - payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */) - conn.SendIP( - t, - testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(to.To4()))}, - testbench.UDP{}, - &testbench.Payload{Bytes: payload}, - ) - ret, payload, errno := dut.RecvWithErrno(context.Background(), t, boundFD, 100, 0) - if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK { - t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) - } - }) - } -} - -func broadcastAddr(ip net.IP, mask net.IPMask) net.IP { - result := make(net.IP, net.IPv4len) - ip4 := ip.To4() - for i := range ip4 { - result[i] = ip4[i] | ^mask[i] - } - return result -} diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go index 7ee2c8014..6e45cb143 100644 --- a/test/packetimpact/tests/udp_send_recv_dgram_test.go +++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go @@ -15,13 +15,18 @@ package udp_send_recv_dgram_test import ( + "context" "flag" + "fmt" "net" + "syscall" "testing" "time" "github.com/google/go-cmp/cmp" "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/header" "gvisor.dev/gvisor/test/packetimpact/testbench" ) @@ -30,74 +35,295 @@ func init() { } type udpConn interface { - Send(*testing.T, testbench.UDP, ...testbench.Layer) - ExpectData(*testing.T, testbench.UDP, testbench.Payload, time.Duration) (testbench.Layers, error) - Drain(*testing.T) + SrcPort(*testing.T) uint16 + SendFrame(*testing.T, testbench.Layers, ...testbench.Layer) + ExpectFrame(*testing.T, testbench.Layers, time.Duration) (testbench.Layers, error) Close(*testing.T) } +type testCase struct { + bindTo, sendTo net.IP + sendToBroadcast, bindToDevice, expectData bool +} + func TestUDP(t *testing.T) { dut := testbench.NewDUT(t) + subnetBcast := func() net.IP { + subnet := (&tcpip.AddressWithPrefix{ + Address: tcpip.Address(dut.Net.RemoteIPv4.To4()), + PrefixLen: dut.Net.IPv4PrefixLength, + }).Subnet() + return net.IP(subnet.Broadcast()) + }() - for _, isIPv4 := range []bool{true, false} { - ipVersionName := "IPv6" - if isIPv4 { - ipVersionName = "IPv4" - } - t.Run(ipVersionName, func(t *testing.T) { - var addr net.IP - if isIPv4 { - addr = dut.Net.RemoteIPv4 - } else { - addr = dut.Net.RemoteIPv6 + t.Run("Send", func(t *testing.T) { + var testCases []testCase + // Test every valid 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, + subnetBcast, + dut.Net.RemoteIPv4, + dut.Net.RemoteIPv6, + } { + for _, sendTo := range []net.IP{ + net.IPv4bcast, + net.IPv4allsys, + subnetBcast, + dut.Net.LocalIPv4, + dut.Net.LocalIPv6, + } { + // Cannot send to an IPv4 address from a socket bound to IPv6 (except for IPv4-mapped IPv6), + // and viceversa. + if bindTo != nil && ((bindTo.To4() == nil) != (sendTo.To4() == nil)) { + continue + } + for _, bindToDevice := range []bool{true, false} { + expectData := true + switch { + case bindTo.Equal(dut.Net.RemoteIPv4): + // If we're explicitly bound to an interface's unicast address, + // packets are always sent on that interface. + case bindToDevice: + // If we're explicitly bound to an interface, packets are always + // sent on that interface. + case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast(): + // If we're not sending to limited broadcast or multicast, the route table + // will be consulted and packets will be sent on the correct interface. + default: + expectData = false + } + testCases = append( + testCases, + testCase{ + bindTo: bindTo, + sendTo: sendTo, + sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast), + bindToDevice: bindToDevice, + expectData: expectData, + }, + ) + } } - boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, addr) - defer dut.Close(t, boundFD) - - var conn udpConn - var localAddr unix.Sockaddr - if isIPv4 { - v4Conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - localAddr = v4Conn.LocalAddr(t) - conn = &v4Conn - } else { - v6Conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort}) - localAddr = v6Conn.LocalAddr(t, dut.Net.RemoteDevID) - conn = &v6Conn - } - defer conn.Close(t) - - testCases := []struct { - name string - payload []byte - }{ - {"emptypayload", nil}, - {"small payload", []byte("hello world")}, - {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)}, - // Even though UDP allows larger dgrams we don't test it here as - // they need to be fragmented and written out as individual - // frames. + } + for _, tc := range testCases { + boundTestCaseName := "unbound" + if tc.bindTo != nil { + boundTestCaseName = fmt.Sprintf("bindTo=%s", tc.bindTo) } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - t.Run("Send", func(t *testing.T) { - conn.Send(t, testbench.UDP{}, &testbench.Payload{Bytes: tc.payload}) - got, want := dut.Recv(t, boundFD, int32(len(tc.payload)+1), 0), tc.payload - if diff := cmp.Diff(want, got); diff != "" { - t.Fatalf("received payload does not match sent payload, diff (-want, +got):\n%s", diff) + t.Run(fmt.Sprintf("%s/sendTo=%s/bindToDevice=%t/expectData=%t", boundTestCaseName, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) { + runTestCase( + t, + dut, + tc, + func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) { + var destSockaddr unix.Sockaddr + if sendTo4 := tc.sendTo.To4(); sendTo4 != nil { + addr := unix.SockaddrInet4{ + Port: int(conn.SrcPort(t)), + } + copy(addr.Addr[:], sendTo4) + destSockaddr = &addr + } else { + addr := unix.SockaddrInet6{ + Port: int(conn.SrcPort(t)), + ZoneId: dut.Net.RemoteDevID, + } + copy(addr.Addr[:], tc.sendTo.To16()) + destSockaddr = &addr } - }) - t.Run("Recv", func(t *testing.T) { - conn.Drain(t) - if got, want := int(dut.SendTo(t, boundFD, tc.payload, 0, localAddr)), len(tc.payload); got != want { - t.Fatalf("short write got: %d, want: %d", got, want) + if got, want := dut.SendTo(t, socketFD, payload, 0, destSockaddr), len(payload); int(got) != want { + t.Fatalf("got dut.SendTo = %d, want %d", got, want) } - if _, err := conn.ExpectData(t, testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, time.Second); err != nil { + layers = append(layers, &testbench.Payload{ + Bytes: payload, + }) + _, err := conn.ExpectFrame(t, layers, time.Second) + + if !tc.expectData && err == nil { + t.Fatal("received unexpected packet, socket is not bound to device") + } + if err != nil && tc.expectData { t.Fatal(err) } - }) - }) + }, + ) + }) + } + }) + t.Run("Recv", func(t *testing.T) { + // Test every valid combination of broadcast/multicast/unicast + // bound/destination address, and bound/not-bound to device. + var testCases []testCase + for _, addr := range []net.IP{ + net.IPv4bcast, + net.IPv4allsys, + dut.Net.RemoteIPv4, + dut.Net.RemoteIPv6, + } { + for _, bindToDevice := range []bool{true, false} { + testCases = append( + testCases, + testCase{ + bindTo: addr, + sendTo: addr, + sendToBroadcast: addr.Equal(subnetBcast) || addr.Equal(net.IPv4bcast), + bindToDevice: bindToDevice, + expectData: true, + }, + ) } - }) + } + for _, bindTo := range []net.IP{ + net.IPv4zero, + subnetBcast, + dut.Net.RemoteIPv4, + } { + for _, sendTo := range []net.IP{ + subnetBcast, + net.IPv4bcast, + net.IPv4allsys, + } { + // TODO(gvisor.dev/issue/4896): Add bindTo=subnetBcast/sendTo=IPv4bcast + // and bindTo=subnetBcast/sendTo=IPv4allsys test cases. + if bindTo.Equal(subnetBcast) && (sendTo.Equal(net.IPv4bcast) || sendTo.IsMulticast()) { + continue + } + // Expect that a socket bound to a unicast address does not receive + // packets sent to an address other than the bound unicast address. + // + // Note: we cannot use net.IP.IsGlobalUnicast to test this condition + // because IsGlobalUnicast does not check whether the address is the + // subnet broadcast, and returns true in that case. + expectData := !bindTo.Equal(dut.Net.RemoteIPv4) || sendTo.Equal(dut.Net.RemoteIPv4) + for _, bindToDevice := range []bool{true, false} { + testCases = append( + testCases, + testCase{ + bindTo: bindTo, + sendTo: sendTo, + sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast), + bindToDevice: bindToDevice, + expectData: expectData, + }, + ) + } + } + } + for _, tc := range testCases { + t.Run(fmt.Sprintf("bindTo=%s/sendTo=%s/bindToDevice=%t/expectData=%t", tc.bindTo, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) { + runTestCase( + t, + dut, + tc, + func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) { + conn.SendFrame(t, layers, &testbench.Payload{Bytes: payload}) + + if tc.expectData { + got, want := dut.Recv(t, socketFD, int32(len(payload)+1), 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, + socketFD, + unix.SOL_SOCKET, + unix.SO_RCVTIMEO, + &unix.Timeval{ + Sec: 1, + Usec: 0, + }, + ) + ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, socketFD, 100, 0) + if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK { + t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) + } + } + }, + ) + }) + } + }) +} + +func runTestCase( + t *testing.T, + dut testbench.DUT, + tc testCase, + runTc func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers), +) { + var ( + socketFD int32 + outgoingUDP, incomingUDP testbench.UDP + ) + if tc.bindTo != nil { + var remotePort uint16 + socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, tc.bindTo) + outgoingUDP.DstPort = &remotePort + incomingUDP.SrcPort = &remotePort + } else { + // An unbound socket will auto-bind to INNADDR_ANY and a random + // port on sendto. + socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP) + } + defer dut.Close(t, socketFD) + if tc.bindToDevice { + dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName)) + } + + var ethernetLayer testbench.Ether + if tc.sendToBroadcast { + 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. + ethernetBroadcastAddress := header.EthernetBroadcastAddress + ethernetLayer.DstAddr = ðernetBroadcastAddress + } else if tc.sendTo.IsMulticast() { + ethernetMulticastAddress := header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(tc.sendTo.To4())) + ethernetLayer.DstAddr = ðernetMulticastAddress + } + expectedLayers := testbench.Layers{ðernetLayer} + + var conn udpConn + if sendTo4 := tc.sendTo.To4(); sendTo4 != nil { + v4Conn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP) + conn = &v4Conn + expectedLayers = append( + expectedLayers, + &testbench.IPv4{ + DstAddr: testbench.Address(tcpip.Address(sendTo4)), + }, + ) + } else { + v6Conn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP) + conn = &v6Conn + expectedLayers = append( + expectedLayers, + &testbench.IPv6{ + DstAddr: testbench.Address(tcpip.Address(tc.sendTo)), + }, + ) + } + defer conn.Close(t) + + expectedLayers = append(expectedLayers, &incomingUDP) + for _, v := range []struct { + name string + payload []byte + }{ + {"emptypayload", nil}, + {"small payload", []byte("hello world")}, + {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)}, + // Even though UDP allows larger dgrams we don't test it here as + // they need to be fragmented and written out as individual + // frames. + } { + runTc(t, dut, conn, socketFD, tc, v.payload, expectedLayers) } } -- cgit v1.2.3