Implement Broadcast support

This change adds support for the SO_BROADCAST socket option in gVisor Netstack.
This support includes getsockopt()/setsockopt() functionality for both UDP and
TCP endpoints (the latter being a NOOP), dispatching broadcast messages up and
down the stack, and route finding/creation for broadcast packets. Finally, a
suite of tests have been implemented, exercising this functionality through the
Linux syscall API.

PiperOrigin-RevId: 234850781
Change-Id: If3e666666917d39f55083741c78314a06defb26c
This commit is contained in:
Amanda Tait
2019-02-20 12:54:13 -08:00
committed by Shentubot
parent 3e3a1ef9d6
commit ea070b9d5f
22 changed files with 698 additions and 11 deletions
+3
View File
@@ -141,6 +141,9 @@ func (c *Client) Request(ctx context.Context, requestedAddr tcpip.Address) (cfg
}, nil); err != nil {
return Config{}, fmt.Errorf("dhcp: connect failed: %v", err)
}
if err := ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
return Config{}, fmt.Errorf("dhcp: setsockopt SO_BROADCAST: %v", err)
}
epin, err := c.stack.NewEndpoint(udp.ProtocolNumber, ipv4.ProtocolNumber, &wq)
if err != nil {
+3
View File
@@ -287,6 +287,9 @@ func TestTwoServers(t *testing.T) {
if err = ep.Bind(tcpip.FullAddress{Port: ServerPort}, nil); err != nil {
t.Fatalf("dhcp: server bind: %v", err)
}
if err = ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
t.Fatalf("dhcp: setsockopt: %v", err)
}
serverCtx, cancel := context.WithCancel(context.Background())
defer cancel()
+3
View File
@@ -123,6 +123,9 @@ func newEPConnServer(ctx context.Context, stack *stack.Stack, addrs []tcpip.Addr
if err := ep.Bind(tcpip.FullAddress{Port: ServerPort}, nil); err != nil {
return nil, fmt.Errorf("dhcp: server bind: %v", err)
}
if err := ep.SetSockOpt(tcpip.BroadcastOption(1)); err != nil {
return nil, fmt.Errorf("dhcp: server setsockopt: %v", err)
}
c := newEPConn(ctx, wq, ep)
return NewServer(ctx, c, addrs, cfg)
}
+21
View File
@@ -582,6 +582,7 @@ func GetSockOpt(t *kernel.Task, s socket.Socket, ep commonEndpoint, family int,
// getSockOptSocket implements GetSockOpt when level is SOL_SOCKET.
func getSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, family int, skType transport.SockType, name, outLen int) (interface{}, *syserr.Error) {
// TODO: Stop rejecting short optLen values in getsockopt.
switch name {
case linux.SO_TYPE:
if outLen < sizeOfInt32 {
@@ -681,6 +682,18 @@ func getSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, family
return int32(v), nil
case linux.SO_BROADCAST:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
}
var v tcpip.BroadcastOption
if err := ep.GetSockOpt(&v); err != nil {
return nil, syserr.TranslateNetstackError(err)
}
return int32(v), nil
case linux.SO_KEEPALIVE:
if outLen < sizeOfInt32 {
return nil, syserr.ErrInvalidArgument
@@ -982,6 +995,14 @@ func setSockOptSocket(t *kernel.Task, s socket.Socket, ep commonEndpoint, name i
v := usermem.ByteOrder.Uint32(optVal)
return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.ReusePortOption(v)))
case linux.SO_BROADCAST:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
}
v := usermem.ByteOrder.Uint32(optVal)
return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.BroadcastOption(v)))
case linux.SO_PASSCRED:
if len(optVal) < sizeOfInt32 {
return syserr.ErrInvalidArgument
+2
View File
@@ -43,6 +43,7 @@ var (
ErrQueueSizeNotSupported = New(tcpip.ErrQueueSizeNotSupported.String(), linux.ENOTTY)
ErrNoSuchFile = New(tcpip.ErrNoSuchFile.String(), linux.ENOENT)
ErrInvalidOptionValue = New(tcpip.ErrInvalidOptionValue.String(), linux.EINVAL)
ErrBroadcastDisabled = New(tcpip.ErrBroadcastDisabled.String(), linux.EACCES)
)
var netstackErrorTranslations = map[*tcpip.Error]*Error{
@@ -80,6 +81,7 @@ var netstackErrorTranslations = map[*tcpip.Error]*Error{
tcpip.ErrNetworkUnreachable: ErrNetworkUnreachable,
tcpip.ErrMessageTooLong: ErrMessageTooLong,
tcpip.ErrNoBufferSpace: ErrNoBufferSpace,
tcpip.ErrBroadcastDisabled: ErrBroadcastDisabled,
}
// TranslateNetstackError converts an error from the tcpip package to a sentry
+15
View File
@@ -399,6 +399,21 @@ func (n *NIC) DeliverNetworkPacket(linkEP LinkEndpoint, remote, _ tcpip.LinkAddr
src, dst := netProto.ParseAddresses(vv.First())
// If the packet is destined to the IPv4 Broadcast address, then make a
// route to each IPv4 network endpoint and let each endpoint handle the
// packet.
if dst == header.IPv4Broadcast {
for _, ref := range n.endpoints {
if ref.protocol == header.IPv4ProtocolNumber && ref.tryIncRef() {
r := makeRoute(protocol, dst, src, linkEP.LinkAddress(), ref)
r.RemoteLinkAddress = remote
ref.ep.HandlePacket(&r, vv)
ref.decRef()
}
}
return
}
if ref := n.getRef(protocol, dst); ref != nil {
r := makeRoute(protocol, dst, src, linkEP.LinkAddress(), ref)
r.RemoteLinkAddress = remote
+59 -11
View File
@@ -132,7 +132,22 @@ func (ep *multiPortEndpoint) selectEndpoint(id TransportEndpointID) TransportEnd
// HandlePacket is called by the stack when new packets arrive to this transport
// endpoint.
func (ep *multiPortEndpoint) HandlePacket(r *Route, id TransportEndpointID, vv buffer.VectorisedView) {
ep.selectEndpoint(id).HandlePacket(r, id, vv)
// If this is a broadcast datagram, deliver the datagram to all endpoints
// managed by ep.
if id.LocalAddress == header.IPv4Broadcast {
for i, endpoint := range ep.endpointsArr {
// HandlePacket modifies vv, so each endpoint needs its own copy.
if i == len(ep.endpointsArr)-1 {
endpoint.HandlePacket(r, id, vv)
break
}
vvCopy := buffer.NewView(vv.Size())
copy(vvCopy, vv.ToView())
endpoint.HandlePacket(r, id, vvCopy.ToVectorisedView())
}
} else {
ep.selectEndpoint(id).HandlePacket(r, id, vv)
}
}
// HandleControlPacket implements stack.TransportEndpoint.HandleControlPacket.
@@ -224,20 +239,47 @@ func (d *transportDemuxer) unregisterEndpoint(netProtos []tcpip.NetworkProtocolN
}
}
// deliverPacket attempts to deliver the given packet. Returns true if it found
// an endpoint, false otherwise.
var loopbackSubnet = func() tcpip.Subnet {
sn, err := tcpip.NewSubnet("\x7f\x00\x00\x00", "\xff\x00\x00\x00")
if err != nil {
panic(err)
}
return sn
}()
// deliverPacket attempts to find one or more matching transport endpoints, and
// then, if matches are found, delivers the packet to them. Returns true if it
// found one or more endpoints, false otherwise.
func (d *transportDemuxer) deliverPacket(r *Route, protocol tcpip.TransportProtocolNumber, vv buffer.VectorisedView, id TransportEndpointID) bool {
eps, ok := d.protocol[protocolIDs{r.NetProto, protocol}]
if !ok {
return false
}
// If a sender bound to the Loopback interface sends a broadcast,
// that broadcast must not be delivered to the sender.
if loopbackSubnet.Contains(r.RemoteAddress) && r.LocalAddress == header.IPv4Broadcast && id.LocalPort == id.RemotePort {
return false
}
// If the packet is a broadcast, then find all matching transport endpoints.
// Otherwise, try to find a single matching transport endpoint.
destEps := make([]TransportEndpoint, 0, 1)
eps.mu.RLock()
ep := d.findEndpointLocked(eps, vv, id)
if protocol == header.UDPProtocolNumber && id.LocalAddress == header.IPv4Broadcast {
for epID, endpoint := range eps.endpoints {
if epID.LocalPort == id.LocalPort {
destEps = append(destEps, endpoint)
}
}
} else if ep := d.findEndpointLocked(eps, vv, id); ep != nil {
destEps = append(destEps, ep)
}
eps.mu.RUnlock()
// Fail if we didn't find one.
if ep == nil {
// Fail if we didn't find at least one matching transport endpoint.
if len(destEps) == 0 {
// UDP packet could not be delivered to an unknown destination port.
if protocol == header.UDPProtocolNumber {
r.Stats().UDP.UnknownPortErrors.Increment()
@@ -246,7 +288,9 @@ func (d *transportDemuxer) deliverPacket(r *Route, protocol tcpip.TransportProto
}
// Deliver the packet.
ep.HandlePacket(r, id, vv)
for _, ep := range destEps {
ep.HandlePacket(r, id, vv)
}
return true
}
@@ -277,7 +321,7 @@ func (d *transportDemuxer) deliverControlPacket(net tcpip.NetworkProtocolNumber,
func (d *transportDemuxer) findEndpointLocked(eps *transportEndpoints, vv buffer.VectorisedView, id TransportEndpointID) TransportEndpoint {
// Try to find a match with the id as provided.
if ep := eps.endpoints[id]; ep != nil {
if ep, ok := eps.endpoints[id]; ok {
return ep
}
@@ -285,7 +329,7 @@ func (d *transportDemuxer) findEndpointLocked(eps *transportEndpoints, vv buffer
nid := id
nid.LocalAddress = ""
if ep := eps.endpoints[nid]; ep != nil {
if ep, ok := eps.endpoints[nid]; ok {
return ep
}
@@ -293,11 +337,15 @@ func (d *transportDemuxer) findEndpointLocked(eps *transportEndpoints, vv buffer
nid.LocalAddress = id.LocalAddress
nid.RemoteAddress = ""
nid.RemotePort = 0
if ep := eps.endpoints[nid]; ep != nil {
if ep, ok := eps.endpoints[nid]; ok {
return ep
}
// Try to find a match with only the local port.
nid.LocalAddress = ""
return eps.endpoints[nid]
if ep, ok := eps.endpoints[nid]; ok {
return ep
}
return nil
}
+11
View File
@@ -100,6 +100,7 @@ var (
ErrNetworkUnreachable = &Error{msg: "network is unreachable"}
ErrMessageTooLong = &Error{msg: "message too long"}
ErrNoBufferSpace = &Error{msg: "no buffer space available"}
ErrBroadcastDisabled = &Error{msg: "broadcast socket option disabled"}
)
// Errors related to Subnet
@@ -502,6 +503,10 @@ type RemoveMembershipOption MembershipOption
// TCP out-of-band data is delivered along with the normal in-band data.
type OutOfBandInlineOption int
// BroadcastOption is used by SetSockOpt/GetSockOpt to specify whether
// datagram sockets are allowed to send packets to a broadcast address.
type BroadcastOption int
// Route is a row in the routing table. It specifies through which NIC (and
// gateway) sets of packets should be routed. A row is considered viable if the
// masked target address matches the destination adddress in the row.
@@ -527,6 +532,12 @@ func (r *Route) Match(addr Address) bool {
return false
}
// Using header.Ipv4Broadcast would introduce an import cycle, so
// we'll use a literal instead.
if addr == "\xff\xff\xff\xff" {
return true
}
for i := 0; i < len(r.Destination); i++ {
if (addr[i] & r.Mask[i]) != r.Destination[i] {
return false
+20
View File
@@ -116,6 +116,9 @@ type endpoint struct {
route stack.Route `state:"manual"`
v6only bool
isConnectNotified bool
// TCP should never broadcast but Linux nevertheless supports enabling/
// disabling SO_BROADCAST, albeit as a NOOP.
broadcast bool
// effectiveNetProtos contains the network protocols actually in use. In
// most cases it will only contain "netProto", but in cases like IPv6
@@ -813,6 +816,12 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
e.notifyProtocolGoroutine(notifyKeepaliveChanged)
return nil
case tcpip.BroadcastOption:
e.mu.Lock()
e.broadcast = v != 0
e.mu.Unlock()
return nil
default:
return nil
}
@@ -971,6 +980,17 @@ func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
*o = 1
return nil
case *tcpip.BroadcastOption:
e.mu.Lock()
v := e.broadcast
e.mu.Unlock()
*o = 0
if v {
*o = 1
}
return nil
default:
return tcpip.ErrUnknownProtocolOption
}
@@ -336,6 +336,7 @@ func loadError(s string) *tcpip.Error {
tcpip.ErrNetworkUnreachable,
tcpip.ErrMessageTooLong,
tcpip.ErrNoBufferSpace,
tcpip.ErrBroadcastDisabled,
}
messageToError = make(map[string]*tcpip.Error)
+23
View File
@@ -82,6 +82,7 @@ type endpoint struct {
multicastAddr tcpip.Address
multicastNICID tcpip.NICID
reusePort bool
broadcast bool
// shutdownFlags represent the current shutdown state of the endpoint.
shutdownFlags tcpip.ShutdownFlags
@@ -347,6 +348,10 @@ func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, <-c
nicid = e.bindNICID
}
if to.Addr == header.IPv4Broadcast && !e.broadcast {
return 0, nil, tcpip.ErrBroadcastDisabled
}
r, _, _, err := e.connectRoute(nicid, *to)
if err != nil {
return 0, nil, err
@@ -502,6 +507,13 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
e.mu.Lock()
e.reusePort = v != 0
e.mu.Unlock()
case tcpip.BroadcastOption:
e.mu.Lock()
e.broadcast = v != 0
e.mu.Unlock()
return nil
}
return nil
}
@@ -581,6 +593,17 @@ func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error {
*o = 0
return nil
case *tcpip.BroadcastOption:
e.mu.RLock()
v := e.broadcast
e.mu.RUnlock()
*o = 0
if v {
*o = 1
}
return nil
default:
return tcpip.ErrUnknownProtocolOption
}
+69
View File
@@ -148,6 +148,7 @@ cc_library(
hdrs = ["ip_socket_test_util.h"],
deps = [
":socket_test_util",
"@com_google_absl//absl/strings",
],
)
@@ -1970,6 +1971,42 @@ cc_library(
alwayslink = 1,
)
cc_library(
name = "socket_ipv4_udp_unbound_external_networking_test_cases",
testonly = 1,
srcs = [
"socket_ipv4_udp_unbound_external_networking.cc",
],
hdrs = [
"socket_ipv4_udp_unbound_external_networking.h",
],
deps = [
":ip_socket_test_util",
":socket_test_util",
"//test/util:test_util",
"@com_google_googletest//:gtest",
],
alwayslink = 1,
)
cc_library(
name = "socket_ipv4_tcp_unbound_external_networking_test_cases",
testonly = 1,
srcs = [
"socket_ipv4_tcp_unbound_external_networking.cc",
],
hdrs = [
"socket_ipv4_tcp_unbound_external_networking.h",
],
deps = [
":ip_socket_test_util",
":socket_test_util",
"//test/util:test_util",
"@com_google_googletest//:gtest",
],
alwayslink = 1,
)
cc_binary(
name = "socket_abstract_test",
testonly = 1,
@@ -2147,6 +2184,38 @@ cc_binary(
],
)
cc_binary(
name = "socket_ipv4_udp_unbound_external_networking_test",
testonly = 1,
srcs = [
"socket_ipv4_udp_unbound_external_networking_test.cc",
],
linkstatic = 1,
deps = [
":ip_socket_test_util",
":socket_ipv4_udp_unbound_external_networking_test_cases",
":socket_test_util",
"//test/util:test_main",
"//test/util:test_util",
],
)
cc_binary(
name = "socket_ipv4_tcp_unbound_external_networking_test",
testonly = 1,
srcs = [
"socket_ipv4_tcp_unbound_external_networking_test.cc",
],
linkstatic = 1,
deps = [
":ip_socket_test_util",
":socket_ipv4_tcp_unbound_external_networking_test_cases",
":socket_test_util",
"//test/util:test_main",
"//test/util:test_util",
],
)
cc_binary(
name = "socket_ip_udp_loopback_non_blocking_test",
testonly = 1,
@@ -13,7 +13,9 @@
// limitations under the License.
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <cstring>
#include "test/syscalls/linux/ip_socket_test_util.h"
@@ -95,5 +97,19 @@ SocketPairKind IPv4UDPUnboundSocketPair(int type) {
/* dual_stack = */ false)};
}
SocketKind IPv4UDPUnboundSocket(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket");
return SocketKind{description, UnboundSocketCreator(
AF_INET, type | SOCK_DGRAM, IPPROTO_UDP)};
}
SocketKind IPv4TCPUnboundSocket(int type) {
std::string description =
absl::StrCat(DescribeSocketType(type), "IPv4 TCP socket");
return SocketKind{description, UnboundSocketCreator(
AF_INET, type | SOCK_STREAM, IPPROTO_TCP)};
}
} // namespace testing
} // namespace gvisor
@@ -58,6 +58,14 @@ SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type);
// SocketPairs created with AF_INET and the given type.
SocketPairKind IPv4UDPUnboundSocketPair(int type);
// IPv4UDPUnboundSocketPair returns a SocketKind that represents
// a SimpleSocket created with AF_INET, SOCK_DGRAM, and the given type.
SocketKind IPv4UDPUnboundSocket(int type);
// IPv4TCPUnboundSocketPair returns a SocketKind that represents
// a SimpleSocket created with AF_INET, SOCK_STREAM and the given type.
SocketKind IPv4TCPUnboundSocket(int type);
} // namespace testing
} // namespace gvisor
@@ -0,0 +1,66 @@
// Copyright 2019 Google LLC
//
// 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.
#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <cstdio>
#include <cstring>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "gtest/gtest.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
// Verifies that a newly instantiated TCP socket does not have the
// broadcast socket option enabled.
TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, TCPBroadcastDefault) {
auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get = -1;
socklen_t get_sz = sizeof(get);
EXPECT_THAT(
getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get, kSockOptOff);
EXPECT_EQ(get_sz, sizeof(get));
}
// Verifies that a newly instantiated TCP socket returns true after enabling
// the broadcast socket option.
TEST_P(IPv4TCPUnboundExternalNetworkingSocketTest, SetTCPBroadcast) {
auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
int get = -1;
socklen_t get_sz = sizeof(get);
EXPECT_THAT(
getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get, kSockOptOn);
EXPECT_EQ(get_sz, sizeof(get));
}
} // namespace testing
} // namespace gvisor
@@ -0,0 +1,30 @@
// Copyright 2019 Google LLC
//
// 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.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
#include "test/syscalls/linux/socket_test_util.h"
namespace gvisor {
namespace testing {
// Test fixture for tests that apply to unbound IPv4 TCP sockets in a sandbox
// with external networking support.
using IPv4TCPUnboundExternalNetworkingSocketTest = SimpleSocketTest;
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_TCP_UNBOUND_EXTERNAL_NETWORKING_H_
@@ -0,0 +1,35 @@
// Copyright 2019 Google LLC
//
// 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.
#include <vector>
#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
std::vector<SocketKind> GetSockets() {
return ApplyVec<SocketKind>(
IPv4TCPUnboundSocket,
AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
}
INSTANTIATE_TEST_CASE_P(IPv4TCPSockets,
IPv4TCPUnboundExternalNetworkingSocketTest,
::testing::ValuesIn(GetSockets()));
} // namespace testing
} // namespace gvisor
@@ -0,0 +1,231 @@
// Copyright 2019 Google LLC
//
// 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.
#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <cstdio>
#include <cstring>
#include "gmock/gmock.h"
#include "gtest/gtest.h"
#include "gtest/gtest.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
// Verifies that a newly instantiated UDP socket does not have the
// broadcast socket option enabled.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) {
auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
int get = -1;
socklen_t get_sz = sizeof(get);
EXPECT_THAT(
getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get, kSockOptOff);
EXPECT_EQ(get_sz, sizeof(get));
}
// Verifies that a newly instantiated UDP socket returns true after enabling
// the broadcast socket option.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, SetUDPBroadcast) {
auto socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
EXPECT_THAT(setsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
int get = -1;
socklen_t get_sz = sizeof(get);
EXPECT_THAT(
getsockopt(socket->get(), SOL_SOCKET, SO_BROADCAST, &get, &get_sz),
SyscallSucceedsWithValue(0));
EXPECT_EQ(get, kSockOptOn);
EXPECT_EQ(get_sz, sizeof(get));
}
// Verifies that a broadcast UDP packet will arrive at all UDP sockets with
// the destination port number.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
UDPBroadcastReceivedOnAllExpectedEndpoints) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto rcvr1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto rcvr2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto norcv = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
// Enable SO_BROADCAST on the sending socket.
ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
// Enable SO_REUSEPORT on the receiving sockets so that they may both be bound
// to the broadcast messages destination port.
ASSERT_THAT(setsockopt(rcvr1->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
ASSERT_THAT(setsockopt(rcvr2->get(), SOL_SOCKET, SO_REUSEPORT, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
sockaddr_in rcv_addr = {};
socklen_t rcv_addr_sz = sizeof(rcv_addr);
rcv_addr.sin_family = AF_INET;
rcv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ASSERT_THAT(bind(rcvr1->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
rcv_addr_sz),
SyscallSucceedsWithValue(0));
// Retrieve port number from first socket so that it can be bound to the
// second socket.
rcv_addr = {};
ASSERT_THAT(
getsockname(rcvr1->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
&rcv_addr_sz),
SyscallSucceedsWithValue(0));
ASSERT_THAT(bind(rcvr2->get(), reinterpret_cast<struct sockaddr*>(&rcv_addr),
rcv_addr_sz),
SyscallSucceedsWithValue(0));
// Bind the non-receiving socket to an ephemeral port.
sockaddr_in norcv_addr = {};
norcv_addr.sin_family = AF_INET;
norcv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ASSERT_THAT(
bind(norcv->get(), reinterpret_cast<struct sockaddr*>(&norcv_addr),
sizeof(norcv_addr)),
SyscallSucceedsWithValue(0));
// Broadcast a test message.
sockaddr_in dst_addr = {};
dst_addr.sin_family = AF_INET;
dst_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
dst_addr.sin_port = rcv_addr.sin_port;
constexpr char kTestMsg[] = "hello, world";
EXPECT_THAT(
sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
reinterpret_cast<struct sockaddr*>(&dst_addr), sizeof(dst_addr)),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
// Verify that the receiving sockets received the test message.
char buf[sizeof(kTestMsg)] = {};
EXPECT_THAT(read(rcvr1->get(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg)));
memset(buf, 0, sizeof(buf));
EXPECT_THAT(read(rcvr2->get(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
EXPECT_EQ(0, memcmp(buf, kTestMsg, sizeof(kTestMsg)));
// Verify that the non-receiving socket did not receive the test message.
memset(buf, 0, sizeof(buf));
EXPECT_THAT(RetryEINTR(recv)(norcv->get(), buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EAGAIN));
}
// Verifies that a UDP broadcast sent via the loopback interface is not received
// by the sender.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
UDPBroadcastViaLoopbackFails) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
// Enable SO_BROADCAST.
ASSERT_THAT(setsockopt(sender->get(), SOL_SOCKET, SO_BROADCAST, &kSockOptOn,
sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
// Bind the sender to the loopback interface.
sockaddr_in src = {};
socklen_t src_sz = sizeof(src);
src.sin_family = AF_INET;
src.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ASSERT_THAT(
bind(sender->get(), reinterpret_cast<struct sockaddr*>(&src), src_sz),
SyscallSucceedsWithValue(0));
ASSERT_THAT(getsockname(sender->get(),
reinterpret_cast<struct sockaddr*>(&src), &src_sz),
SyscallSucceedsWithValue(0));
ASSERT_EQ(src.sin_addr.s_addr, htonl(INADDR_LOOPBACK));
// Send the message.
sockaddr_in dst = {};
dst.sin_family = AF_INET;
dst.sin_addr.s_addr = htonl(INADDR_BROADCAST);
dst.sin_port = src.sin_port;
constexpr char kTestMsg[] = "hello, world";
EXPECT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
reinterpret_cast<struct sockaddr*>(&dst), sizeof(dst)),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
// Verify that the message was not received by the sender (loopback).
char buf[sizeof(kTestMsg)] = {};
EXPECT_THAT(RetryEINTR(recv)(sender->get(), buf, sizeof(buf), MSG_DONTWAIT),
SyscallFailsWithErrno(EAGAIN));
}
// Verifies that a UDP broadcast fails to send on a socket with SO_BROADCAST
// disabled.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendBroadcast) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
// Broadcast a test message without having enabled SO_BROADCAST on the sending
// socket.
sockaddr_in addr = {};
socklen_t addr_sz = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(12345);
addr.sin_addr.s_addr = htonl(INADDR_BROADCAST);
constexpr char kTestMsg[] = "hello, world";
EXPECT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
reinterpret_cast<struct sockaddr*>(&addr), addr_sz),
SyscallFailsWithErrno(EACCES));
}
// Verifies that a UDP unicast on an unbound socket reaches its destination.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendUnicastOnUnbound) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto rcvr = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
// Bind the receiver and retrieve its address and port number.
sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(0);
ASSERT_THAT(bind(rcvr->get(), reinterpret_cast<struct sockaddr*>(&addr),
sizeof(addr)),
SyscallSucceedsWithValue(0));
memset(&addr, 0, sizeof(addr));
socklen_t addr_sz = sizeof(addr);
ASSERT_THAT(getsockname(rcvr->get(),
reinterpret_cast<struct sockaddr*>(&addr), &addr_sz),
SyscallSucceedsWithValue(0));
// Send a test message to the receiver.
constexpr char kTestMsg[] = "hello, world";
ASSERT_THAT(sendto(sender->get(), kTestMsg, sizeof(kTestMsg), 0,
reinterpret_cast<struct sockaddr*>(&addr), addr_sz),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
char buf[sizeof(kTestMsg)] = {};
ASSERT_THAT(read(rcvr->get(), buf, sizeof(buf)),
SyscallSucceedsWithValue(sizeof(kTestMsg)));
}
} // namespace testing
} // namespace gvisor
@@ -0,0 +1,30 @@
// Copyright 2019 Google LLC
//
// 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.
#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
#include "test/syscalls/linux/socket_test_util.h"
namespace gvisor {
namespace testing {
// Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox
// with external networking support.
using IPv4UDPUnboundExternalNetworkingSocketTest = SimpleSocketTest;
} // namespace testing
} // namespace gvisor
#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_
@@ -0,0 +1,35 @@
// Copyright 2019 Google LLC
//
// 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.
#include <vector>
#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/test_util.h"
namespace gvisor {
namespace testing {
std::vector<SocketKind> GetSockets() {
return ApplyVec<SocketKind>(
IPv4UDPUnboundSocket,
AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK}));
}
INSTANTIATE_TEST_CASE_P(IPv4UDPSockets,
IPv4UDPUnboundExternalNetworkingSocketTest,
::testing::ValuesIn(GetSockets()));
} // namespace testing
} // namespace gvisor

Some files were not shown because too many files have changed in this diff Show More