mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
@@ -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 {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user