mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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
This commit is contained in:
committed by
gVisor bot
parent
b85b23e50d
commit
6012fe9b59
+1
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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_())
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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("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,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
t.Run(ipVersionName, func(t *testing.T) {
|
||||
var addr net.IP
|
||||
if isIPv4 {
|
||||
addr = dut.Net.RemoteIPv4
|
||||
} else {
|
||||
addr = dut.Net.RemoteIPv6
|
||||
for _, tc := range testCases {
|
||||
boundTestCaseName := "unbound"
|
||||
if tc.bindTo != nil {
|
||||
boundTestCaseName = fmt.Sprintf("bindTo=%s", tc.bindTo)
|
||||
}
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user