diff options
22 files changed, 698 insertions, 11 deletions
diff --git a/pkg/dhcp/client.go b/pkg/dhcp/client.go index 3330c4998..6d48eec7e 100644 --- a/pkg/dhcp/client.go +++ b/pkg/dhcp/client.go @@ -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 { diff --git a/pkg/dhcp/dhcp_test.go b/pkg/dhcp/dhcp_test.go index a21dce6bc..026064394 100644 --- a/pkg/dhcp/dhcp_test.go +++ b/pkg/dhcp/dhcp_test.go @@ -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() diff --git a/pkg/dhcp/server.go b/pkg/dhcp/server.go index 3e06ab4c7..c72c3b70d 100644 --- a/pkg/dhcp/server.go +++ b/pkg/dhcp/server.go @@ -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) } diff --git a/pkg/sentry/socket/epsocket/epsocket.go b/pkg/sentry/socket/epsocket/epsocket.go index a97db5348..e24e58aed 100644 --- a/pkg/sentry/socket/epsocket/epsocket.go +++ b/pkg/sentry/socket/epsocket/epsocket.go @@ -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 diff --git a/pkg/syserr/netstack.go b/pkg/syserr/netstack.go index 20e756edb..05ca475d1 100644 --- a/pkg/syserr/netstack.go +++ b/pkg/syserr/netstack.go @@ -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 diff --git a/pkg/tcpip/stack/nic.go b/pkg/tcpip/stack/nic.go index 586ca873e..43d7c2ec4 100644 --- a/pkg/tcpip/stack/nic.go +++ b/pkg/tcpip/stack/nic.go @@ -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 diff --git a/pkg/tcpip/stack/transport_demuxer.go b/pkg/tcpip/stack/transport_demuxer.go index a5ff2159a..c18208dc0 100644 --- a/pkg/tcpip/stack/transport_demuxer.go +++ b/pkg/tcpip/stack/transport_demuxer.go @@ -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 } diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index a6e47397a..89e9d6741 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -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 diff --git a/pkg/tcpip/transport/tcp/endpoint.go b/pkg/tcpip/transport/tcp/endpoint.go index 1ee9f8d25..aa31a78af 100644 --- a/pkg/tcpip/transport/tcp/endpoint.go +++ b/pkg/tcpip/transport/tcp/endpoint.go @@ -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 } diff --git a/pkg/tcpip/transport/tcp/endpoint_state.go b/pkg/tcpip/transport/tcp/endpoint_state.go index 4891c7941..a07cd9011 100644 --- a/pkg/tcpip/transport/tcp/endpoint_state.go +++ b/pkg/tcpip/transport/tcp/endpoint_state.go @@ -336,6 +336,7 @@ func loadError(s string) *tcpip.Error { tcpip.ErrNetworkUnreachable, tcpip.ErrMessageTooLong, tcpip.ErrNoBufferSpace, + tcpip.ErrBroadcastDisabled, } messageToError = make(map[string]*tcpip.Error) diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index 9c3881d63..05d35e526 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -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 } diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 9da5204c1..beece8930 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -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, @@ -2148,6 +2185,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, srcs = [ diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index f8232fc24..4ad787cc0 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -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 diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h index a6721091a..cac790e64 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -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 diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc new file mode 100644 index 000000000..8e1c13ff4 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.cc @@ -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 diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h new file mode 100644 index 000000000..b23de08d1 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking.h @@ -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_ diff --git a/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc new file mode 100644 index 000000000..c6fb42641 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_tcp_unbound_external_networking_test.cc @@ -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 diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc new file mode 100644 index 000000000..7d561b991 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc @@ -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 diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h new file mode 100644 index 000000000..5cf9fa8eb --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h @@ -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_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc new file mode 100644 index 000000000..e07385134 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking_test.cc @@ -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 diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index 8d19f79ac..035087566 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -22,6 +22,7 @@ #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" #include "absl/time/clock.h" +#include "test/util/file_descriptor.h" #include "test/util/posix_error.h" #include "test/util/temp_path.h" #include "test/util/thread_util.h" @@ -463,6 +464,17 @@ SocketPairKind Reversed(SocketPairKind const& base) { }}; } +Creator<FileDescriptor> UnboundSocketCreator(int domain, int type, + int protocol) { + return [=]() -> PosixErrorOr<std::unique_ptr<FileDescriptor>> { + int sock; + RETURN_ERROR_IF_SYSCALL_FAIL(sock = socket(domain, type, protocol)); + MaybeSave(); // Successful socket creation. + + return absl::make_unique<FileDescriptor>(sock); + }; +} + std::vector<SocketPairKind> IncludeReversals(std::vector<SocketPairKind> vec) { return ApplyVecToVec<SocketPairKind>(std::vector<Middleware>{NoOp, Reversed}, vec); diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 906b3e929..dfabdf179 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -278,6 +278,11 @@ Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, int protocol, bool dual_stack); +// UnboundSocketCreator returns a Creator<FileDescriptor> that obtains a file +// descriptor by creating a socket. +Creator<FileDescriptor> UnboundSocketCreator(int domain, int type, + int protocol); + // A SocketPairKind couples a human-readable description of a socket pair with // a function that creates such a socket pair. struct SocketPairKind { |