summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMarina Ciocea <marinaciocea@google.com>2021-01-28 06:23:04 -0800
committergVisor bot <gvisor-bot@google.com>2021-01-28 06:24:46 -0800
commit6012fe9b5965a2f285f2f99312e6cb7ae84b5fa8 (patch)
treec2a29a720d2e557a3ed2712fc71380ba9f39509a
parentb85b23e50d1c264ff4821e182ad89a8ea3d0e0c5 (diff)
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
-rw-r--r--pkg/tcpip/tcpip.go2
-rw-r--r--pkg/tcpip/transport/udp/endpoint.go3
-rw-r--r--test/packetimpact/runner/defs.bzl3
-rw-r--r--test/packetimpact/testbench/connections.go40
-rw-r--r--test/packetimpact/testbench/dut.go2
-rw-r--r--test/packetimpact/tests/BUILD14
-rw-r--r--test/packetimpact/tests/udp_recv_mcast_bcast_test.go115
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go338
8 files changed, 329 insertions, 188 deletions
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
@@ -175,9 +175,6 @@ ALL_TESTS = [
name = "udp_discard_mcast_source_addr",
),
PacketimpactTestInfo(
- name = "udp_recv_mcast_bcast",
- ),
- PacketimpactTestInfo(
name = "udp_any_addr_recv_unicast",
),
PacketimpactTestInfo(
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
@@ -38,18 +38,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"],
deps = [
@@ -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 = &ethernetBroadcastAddress
+ } else if tc.sendTo.IsMulticast() {
+ ethernetMulticastAddress := header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(tc.sendTo.To4()))
+ ethernetLayer.DstAddr = &ethernetMulticastAddress
+ }
+ expectedLayers := testbench.Layers{&ethernetLayer}
+
+ 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)
}
}