diff options
author | Ian Gudger <igudger@google.com> | 2019-02-07 23:14:06 -0800 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-02-07 23:15:23 -0800 |
commit | 80f901b16b8bb8fe397cc44578035173f5155b24 (patch) | |
tree | 91707e2f2b424f71f7bac661c05a830b56244255 | |
parent | fda4d1f4f11201c34bd15d41ba4c94279d135d95 (diff) |
Plumb IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP to netstack.
Also includes a few fixes for IPv4 multicast support. IPv6 support is coming in
a followup CL.
PiperOrigin-RevId: 233008638
Change-Id: If7dae6222fef43fda48033f0292af77832d95e82
-rw-r--r-- | pkg/abi/linux/socket.go | 21 | ||||
-rw-r--r-- | pkg/sentry/socket/epsocket/epsocket.go | 46 | ||||
-rw-r--r-- | pkg/tcpip/stack/stack.go | 7 | ||||
-rw-r--r-- | pkg/tcpip/transport/udp/endpoint.go | 16 | ||||
-rw-r--r-- | pkg/tcpip/transport/udp/endpoint_state.go | 6 | ||||
-rw-r--r-- | test/syscalls/BUILD | 2 | ||||
-rw-r--r-- | test/syscalls/linux/BUILD | 34 | ||||
-rw-r--r-- | test/syscalls/linux/ip_socket_test_util.cc | 33 | ||||
-rw-r--r-- | test/syscalls/linux/ip_socket_test_util.h | 7 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ip_udp_generic.cc | 14 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_udp_unbound.cc | 424 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_udp_unbound.h | 29 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc | 35 | ||||
-rw-r--r-- | test/syscalls/linux/socket_test_util.cc | 38 | ||||
-rw-r--r-- | test/syscalls/linux/socket_test_util.h | 5 |
15 files changed, 694 insertions, 23 deletions
diff --git a/pkg/abi/linux/socket.go b/pkg/abi/linux/socket.go index a5f78506a..906776525 100644 --- a/pkg/abi/linux/socket.go +++ b/pkg/abi/linux/socket.go @@ -204,15 +204,30 @@ const ( // uapi/linux/socket.h. const SockAddrMax = 128 -// SockAddrInt is struct sockaddr_in, from uapi/linux/in.h. +// InetAddr is struct in_addr, from uapi/linux/in.h. +type InetAddr [4]byte + +// SockAddrInet is struct sockaddr_in, from uapi/linux/in.h. type SockAddrInet struct { Family uint16 Port uint16 - Addr [4]byte + Addr InetAddr Zero [8]uint8 // pad to sizeof(struct sockaddr). } -// SockAddrInt6 is struct sockaddr_in6, from uapi/linux/in6.h. +// InetMulticastRequest is struct ip_mreq, from uapi/linux/in.h. +type InetMulticastRequest struct { + MulticastAddr InetAddr + InterfaceAddr InetAddr +} + +// InetMulticastRequestWithNIC is struct ip_mreqn, from uapi/linux/in.h. +type InetMulticastRequestWithNIC struct { + InetMulticastRequest + InterfaceIndex int32 +} + +// SockAddrInet6 is struct sockaddr_in6, from uapi/linux/in6.h. type SockAddrInet6 struct { Family uint16 Port uint16 diff --git a/pkg/sentry/socket/epsocket/epsocket.go b/pkg/sentry/socket/epsocket/epsocket.go index ca865b111..16720456a 100644 --- a/pkg/sentry/socket/epsocket/epsocket.go +++ b/pkg/sentry/socket/epsocket/epsocket.go @@ -1078,6 +1078,25 @@ func setSockOptIPv6(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) return syserr.TranslateNetstackError(ep.SetSockOpt(struct{}{})) } +var ( + inetMulticastRequestSize = int(binary.Size(linux.InetMulticastRequest{})) + inetMulticastRequestWithNICSize = int(binary.Size(linux.InetMulticastRequestWithNIC{})) +) + +func copyInMulticastRequest(optVal []byte) (linux.InetMulticastRequestWithNIC, *syserr.Error) { + if len(optVal) < inetMulticastRequestSize { + return linux.InetMulticastRequestWithNIC{}, syserr.ErrInvalidArgument + } + + var req linux.InetMulticastRequestWithNIC + if len(optVal) >= inetMulticastRequestWithNICSize { + binary.Unmarshal(optVal[:inetMulticastRequestWithNICSize], usermem.ByteOrder, &req) + } else { + binary.Unmarshal(optVal[:inetMulticastRequestSize], usermem.ByteOrder, &req.InetMulticastRequest) + } + return req, nil +} + // setSockOptIP implements SetSockOpt when level is SOL_IP. func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *syserr.Error { switch name { @@ -1096,7 +1115,31 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s } return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.MulticastTTLOption(v))) - case linux.IP_ADD_MEMBERSHIP, linux.MCAST_JOIN_GROUP, linux.IP_MULTICAST_IF: + case linux.IP_ADD_MEMBERSHIP: + req, err := copyInMulticastRequest(optVal) + if err != nil { + return err + } + + return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.AddMembershipOption{ + NIC: tcpip.NICID(req.InterfaceIndex), + InterfaceAddr: tcpip.Address(req.InterfaceAddr[:]), + MulticastAddr: tcpip.Address(req.MulticastAddr[:]), + })) + + case linux.IP_DROP_MEMBERSHIP: + req, err := copyInMulticastRequest(optVal) + if err != nil { + return err + } + + return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.RemoveMembershipOption{ + NIC: tcpip.NICID(req.InterfaceIndex), + InterfaceAddr: tcpip.Address(req.InterfaceAddr[:]), + MulticastAddr: tcpip.Address(req.MulticastAddr[:]), + })) + + case linux.MCAST_JOIN_GROUP, linux.IP_MULTICAST_IF: // FIXME: Disallow IP-level multicast group options by // default. These will need to be supported by appropriately plumbing // the level through to the network stack (if at all). However, we @@ -1108,7 +1151,6 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s linux.IP_BIND_ADDRESS_NO_PORT, linux.IP_BLOCK_SOURCE, linux.IP_CHECKSUM, - linux.IP_DROP_MEMBERSHIP, linux.IP_DROP_SOURCE_MEMBERSHIP, linux.IP_FREEBIND, linux.IP_HDRINCL, diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 7aa9dbd46..854ebe1bb 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -742,6 +742,9 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n return Route{}, tcpip.ErrNoRoute } + // TODO: Route multicast packets with no specified local + // address or NIC. + for i := range s.routeTable { if (id != 0 && id != s.routeTable[i].NIC) || (len(remoteAddr) != 0 && !s.routeTable[i].Match(remoteAddr)) { continue @@ -768,6 +771,10 @@ func (s *Stack) FindRoute(id tcpip.NICID, localAddr, remoteAddr tcpip.Address, n return r, nil } + if isMulticast { + return Route{}, tcpip.ErrNetworkUnreachable + } + return Route{}, tcpip.ErrNoRoute } diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index b2a27a7cb..d46bf0ade 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -99,6 +99,7 @@ type endpoint struct { effectiveNetProtos []tcpip.NetworkProtocolNumber } +// +stateify savable type multicastMembership struct { nicID tcpip.NICID multicastAddr tcpip.Address @@ -412,6 +413,8 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error { nicID = e.stack.CheckLocalAddress(nicID, e.netProto, v.InterfaceAddr) } if nicID == 0 { + // TODO: Allow adding memberships without + // specifing an interface. return tcpip.ErrNoRoute } @@ -766,9 +769,11 @@ func (e *endpoint) bindLocked(addr tcpip.FullAddress, commit func() *tcpip.Error } } + nicid := addr.NIC if len(addr.Addr) != 0 { // A local address was specified, verify that it's valid. - if e.stack.CheckLocalAddress(addr.NIC, netProto, addr.Addr) == 0 { + nicid = e.stack.CheckLocalAddress(addr.NIC, netProto, addr.Addr) + if nicid == 0 { return tcpip.ErrBadLocalAddress } } @@ -777,21 +782,21 @@ func (e *endpoint) bindLocked(addr tcpip.FullAddress, commit func() *tcpip.Error LocalPort: addr.Port, LocalAddress: addr.Addr, } - id, err = e.registerWithStack(addr.NIC, netProtos, id) + id, err = e.registerWithStack(nicid, netProtos, id) if err != nil { return err } if commit != nil { if err := commit(); err != nil { // Unregister, the commit failed. - e.stack.UnregisterTransportEndpoint(addr.NIC, netProtos, ProtocolNumber, id, e) + e.stack.UnregisterTransportEndpoint(nicid, netProtos, ProtocolNumber, id, e) e.stack.ReleasePort(netProtos, ProtocolNumber, id.LocalAddress, id.LocalPort) return err } } e.id = id - e.regNICID = addr.NIC + e.regNICID = nicid e.effectiveNetProtos = netProtos // Mark endpoint as bound. @@ -815,7 +820,8 @@ func (e *endpoint) Bind(addr tcpip.FullAddress, commit func() *tcpip.Error) *tcp return err } - e.bindNICID = addr.NIC + // Save the effective NICID generated by bindLocked. + e.bindNICID = e.regNICID return nil } diff --git a/pkg/tcpip/transport/udp/endpoint_state.go b/pkg/tcpip/transport/udp/endpoint_state.go index db1e281ad..4d8210294 100644 --- a/pkg/tcpip/transport/udp/endpoint_state.go +++ b/pkg/tcpip/transport/udp/endpoint_state.go @@ -103,4 +103,10 @@ func (e *endpoint) afterLoad() { if err != nil { panic(*err) } + + for _, m := range e.multicastMemberships { + if err := e.stack.JoinGroup(e.netProto, m.nicID, m.multicastAddr); err != nil { + panic(err) + } + } } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 53da121ec..a5abf8013 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -361,6 +361,8 @@ syscall_test( test = "//test/syscalls/linux:socket_ip_udp_loopback_test", ) +syscall_test(test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_test") + syscall_test(test = "//test/syscalls/linux:socket_netdevice_test") syscall_test(test = "//test/syscalls/linux:socket_netlink_route_test") diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 590ee1659..75fa52a57 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -1931,6 +1931,24 @@ cc_library( alwayslink = 1, ) +cc_library( + name = "socket_ipv4_udp_unbound_test_cases", + testonly = 1, + srcs = [ + "socket_ipv4_udp_unbound.cc", + ], + hdrs = [ + "socket_ipv4_udp_unbound.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, @@ -2125,6 +2143,22 @@ cc_binary( ) cc_binary( + name = "socket_ipv4_udp_unbound_loopback_test", + testonly = 1, + srcs = [ + "socket_ipv4_udp_unbound_loopback.cc", + ], + linkstatic = 1, + deps = [ + ":ip_socket_test_util", + ":socket_ipv4_udp_unbound_test_cases", + ":socket_test_util", + "//test/util:test_main", + "//test/util:test_util", + ], +) + +cc_binary( name = "socket_domain_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 1659d3d83..f8232fc24 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -12,11 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <net/if.h> +#include <sys/ioctl.h> +#include <cstring> + #include "test/syscalls/linux/ip_socket_test_util.h" namespace gvisor { namespace testing { +PosixErrorOr<int> InterfaceIndex(std::string name) { + // TODO: Consider using netlink. + ifreq req = {}; + memcpy(req.ifr_name, name.c_str(), name.size()); + ASSIGN_OR_RETURN_ERRNO(auto sock, Socket(AF_INET, SOCK_DGRAM, 0)); + RETURN_ERROR_IF_SYSCALL_FAIL(ioctl(sock.get(), SIOCGIFINDEX, &req)); + return req.ifr_ifindex; +} + namespace { std::string DescribeSocketType(int type) { @@ -28,7 +41,7 @@ std::string DescribeSocketType(int type) { SocketPairKind IPv6TCPAcceptBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "IPv6 TCP socket"); + absl::StrCat(DescribeSocketType(type), "connected IPv6 TCP socket"); return SocketPairKind{ description, TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0, /* dual_stack = */ false)}; @@ -36,7 +49,7 @@ SocketPairKind IPv6TCPAcceptBindSocketPair(int type) { SocketPairKind IPv4TCPAcceptBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "IPv4 TCP socket"); + absl::StrCat(DescribeSocketType(type), "connected IPv4 TCP socket"); return SocketPairKind{ description, TCPAcceptBindSocketPairCreator(AF_INET, type | SOCK_STREAM, 0, /* dual_stack = */ false)}; @@ -44,7 +57,7 @@ SocketPairKind IPv4TCPAcceptBindSocketPair(int type) { SocketPairKind DualStackTCPAcceptBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "dual stack TCP socket"); + absl::StrCat(DescribeSocketType(type), "connected dual stack TCP socket"); return SocketPairKind{ description, TCPAcceptBindSocketPairCreator(AF_INET6, type | SOCK_STREAM, 0, /* dual_stack = */ true)}; @@ -52,7 +65,7 @@ SocketPairKind DualStackTCPAcceptBindSocketPair(int type) { SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "IPv6 UDP socket"); + absl::StrCat(DescribeSocketType(type), "connected IPv6 UDP socket"); return SocketPairKind{description, UDPBidirectionalBindSocketPairCreator( AF_INET6, type | SOCK_DGRAM, 0, /* dual_stack = */ false)}; @@ -60,7 +73,7 @@ SocketPairKind IPv6UDPBidirectionalBindSocketPair(int type) { SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket"); + absl::StrCat(DescribeSocketType(type), "connected IPv4 UDP socket"); return SocketPairKind{description, UDPBidirectionalBindSocketPairCreator( AF_INET, type | SOCK_DGRAM, 0, /* dual_stack = */ false)}; @@ -68,11 +81,19 @@ SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type) { SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type) { std::string description = - absl::StrCat(DescribeSocketType(type), "dual stack UDP socket"); + absl::StrCat(DescribeSocketType(type), "connected dual stack UDP socket"); return SocketPairKind{description, UDPBidirectionalBindSocketPairCreator( AF_INET6, type | SOCK_DGRAM, 0, /* dual_stack = */ true)}; } +SocketPairKind IPv4UDPUnboundSocketPair(int type) { + std::string description = + absl::StrCat(DescribeSocketType(type), "IPv4 UDP socket"); + return SocketPairKind{ + description, UDPUnboundSocketPairCreator(AF_INET, type | SOCK_DGRAM, 0, + /* dual_stack = */ false)}; +} + } // 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 1e1400ecd..a6721091a 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -21,6 +21,9 @@ namespace gvisor { namespace testing { +// InterfaceIndex returns the index of the named interface. +PosixErrorOr<int> InterfaceIndex(std::string name); + // IPv6TCPAcceptBindSocketPair returns a SocketPairKind that represents // SocketPairs created with bind() and accept() syscalls with AF_INET6 and the // given type bound to the IPv6 loopback. @@ -51,6 +54,10 @@ SocketPairKind IPv4UDPBidirectionalBindSocketPair(int type); // AF_INET6 and the given type bound to the IPv4 loopback. SocketPairKind DualStackUDPBidirectionalBindSocketPair(int type); +// IPv4UDPUnboundSocketPair returns a SocketPairKind that represents +// SocketPairs created with AF_INET and the given type. +SocketPairKind IPv4UDPUnboundSocketPair(int type); + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc index 789154fb3..58d1c846d 100644 --- a/test/syscalls/linux/socket_ip_udp_generic.cc +++ b/test/syscalls/linux/socket_ip_udp_generic.cc @@ -117,5 +117,19 @@ TEST_P(UDPSocketPairTest, SetUDPMulticastTTLAboveMax) { SyscallFailsWithErrno(EINVAL)); } +TEST_P(UDPSocketPairTest, SetEmptyIPAddMembership) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + struct ip_mreqn req = {}; + int ret = setsockopt(sockets->first_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &req, + sizeof(req)); + // FIXME: gVisor returns the incorrect errno. + if (IsRunningOnGvisor()) { + EXPECT_THAT(ret, SyscallFails()); + } else { + EXPECT_THAT(ret, SyscallFailsWithErrno(EINVAL)); + } +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc new file mode 100644 index 000000000..1b47139e4 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -0,0 +1,424 @@ +// 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.h" + +#include <arpa/inet.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <cstdio> + +#include "gtest/gtest.h" +#include "gtest/gtest.h" +#include "test/syscalls/linux/ip_socket_test_util.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +// Check that packets are not received without a group memebership. Default send +// interface configured by bind. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNoGroup) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the first FD to the loopback. This is an alternative to + // IP_MULTICAST_IF for setting the default send interface. + sockaddr_in senderAddr = {}; + senderAddr.sin_family = AF_INET; + senderAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT( + bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&senderAddr), + sizeof(senderAddr)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address. If multicast worked like unicast, + // this would ensure that we get the packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Send the multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we did not receive the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), + MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +// Check that not setting a default send interface prevents multicast packets +// from being sent. Group membership interface configured by address. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackAddrNoDefaultSendIf) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the second FD to the v4 any address to ensure that we can receive any + // unicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register to receive multicast packets. + ip_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallFailsWithErrno(ENETUNREACH)); +} + +// Check that not setting a default send interface prevents multicast packets +// from being sent. Group membership interface configured by NIC ID. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNicNoDefaultSendIf) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the second FD to the v4 any address to ensure that we can receive any + // unicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register to receive multicast packets. + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallFailsWithErrno(ENETUNREACH)); +} + +// Check that multicast works when the default send interface is configured by +// bind and the group membership is configured by address. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the first FD to the loopback. This is an alternative to + // IP_MULTICAST_IF for setting the default send interface. + sockaddr_in senderAddr = {}; + senderAddr.sin_family = AF_INET; + senderAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT( + bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&senderAddr), + sizeof(senderAddr)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register to receive multicast packets. + ip_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + ASSERT_THAT( + RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + +// Check that multicast works when the default send interface is confgured by +// bind and the group membership is configured by NIC ID. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNic) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the first FD to the loopback. This is an alternative to + // IP_MULTICAST_IF for setting the default send interface. + sockaddr_in senderAddr = {}; + senderAddr.sin_family = AF_INET; + senderAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT( + bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&senderAddr), + sizeof(senderAddr)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register to receive multicast packets. + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + ASSERT_THAT( + RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + +// Check that dropping a group membership that does not exist fails. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastInvalidDrop) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Unregister from a membership that we didn't have. + ip_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP, + &group, sizeof(group)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +// Check that dropping a group membership prevents multicast packets from being +// delivered. Default send address configured by bind and group membership +// interface configured by address. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastDropAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the first FD to the loopback. This is an alternative to + // IP_MULTICAST_IF for setting the default send interface. + sockaddr_in senderAddr = {}; + senderAddr.sin_family = AF_INET; + senderAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT( + bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&senderAddr), + sizeof(senderAddr)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register and unregister to receive multicast packets. + ip_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we did not receive the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), + MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +// Check that dropping a group membership prevents multicast packets from being +// delivered. Default send address configured by bind and group membership +// interface configured by NIC ID. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastDropNic) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Bind the first FD to the loopback. This is an alternative to + // IP_MULTICAST_IF for setting the default send interface. + sockaddr_in senderAddr = {}; + senderAddr.sin_family = AF_INET; + senderAddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT( + bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&senderAddr), + sizeof(senderAddr)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + sockaddr_in receiverAddr = {}; + receiverAddr.sin_family = AF_INET; + receiverAddr.sin_addr.s_addr = htonl(INADDR_ANY); + EXPECT_THAT( + bind(sockets->second_fd(), reinterpret_cast<sockaddr*>(&receiverAddr), + sizeof(receiverAddr)), + SyscallSucceeds()); + socklen_t receiverAddrLen = sizeof(receiverAddr); + EXPECT_THAT( + getsockname(sockets->second_fd(), + reinterpret_cast<sockaddr*>(&receiverAddr), &receiverAddrLen), + SyscallSucceeds()); + EXPECT_EQ(receiverAddrLen, sizeof(receiverAddr)); + + // Register and unregister to receive multicast packets. + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr("224.0.2.1"); + group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_DROP_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + sockaddr_in sendAddr = {}; + sendAddr.sin_family = AF_INET; + sendAddr.sin_port = receiverAddr.sin_port; + sendAddr.sin_addr.s_addr = inet_addr("224.0.2.1"); + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT(RetryEINTR(sendto)( + sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendAddr), sizeof(sendAddr)), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we did not receive the multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + EXPECT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), + MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.h b/test/syscalls/linux/socket_ipv4_udp_unbound.h new file mode 100644 index 000000000..a780c0144 --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.h @@ -0,0 +1,29 @@ +// 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_H_ +#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_ + +#include "test/syscalls/linux/socket_test_util.h" + +namespace gvisor { +namespace testing { + +// Test fixture for tests that apply to pairs of IPv4 UDP sockets. +using IPv4UDPUnboundSocketPairTest = SocketPairTest; + +} // namespace testing +} // namespace gvisor + +#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_H_ diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc new file mode 100644 index 000000000..b70faa33d --- /dev/null +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback.cc @@ -0,0 +1,35 @@ +// Copyright 2018 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.h" +#include "test/syscalls/linux/socket_test_util.h" +#include "test/util/test_util.h" + +namespace gvisor { +namespace testing { + +std::vector<SocketPairKind> GetSocketPairs() { + return ApplyVec<SocketPairKind>( + IPv4UDPUnboundSocketPair, + AllBitwiseCombinations(List<int>{0, SOCK_NONBLOCK})); +} + +INSTANTIATE_TEST_CASE_P(IPv4UDPSockets, IPv4UDPUnboundSocketPairTest, + ::testing::ValuesIn(GetSocketPairs())); + +} // namespace testing +} // namespace gvisor diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc index 80a59df7e..49b8c583f 100644 --- a/test/syscalls/linux/socket_test_util.cc +++ b/test/syscalls/linux/socket_test_util.cc @@ -389,23 +389,32 @@ Creator<SocketPair> TCPAcceptBindSocketPairCreator(int domain, int type, } template <typename T> +PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateUDPBoundSocketPair( + int sock1, int sock2, int type, bool dual_stack) { + ASSIGN_OR_RETURN_ERRNO(T addr1, BindIP<T>(sock1, dual_stack)); + ASSIGN_OR_RETURN_ERRNO(T addr2, BindIP<T>(sock2, dual_stack)); + + return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2); +} + +template <typename T> PosixErrorOr<std::unique_ptr<AddrFDSocketPair>> CreateUDPBidirectionalBindSocketPair(int sock1, int sock2, int type, bool dual_stack) { - ASSIGN_OR_RETURN_ERRNO(T addr1, BindIP<T>(sock1, dual_stack)); - ASSIGN_OR_RETURN_ERRNO(T addr2, BindIP<T>(sock2, dual_stack)); + ASSIGN_OR_RETURN_ERRNO( + auto socks, CreateUDPBoundSocketPair<T>(sock1, sock2, type, dual_stack)); // Connect sock1 to sock2. - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock1, reinterpret_cast<struct sockaddr*>(&addr2), sizeof(addr2))); + RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->first_fd(), socks->second_addr(), + socks->second_addr_size())); MaybeSave(); // Successful connection. // Connect sock2 to sock1. - RETURN_ERROR_IF_SYSCALL_FAIL(connect( - sock2, reinterpret_cast<struct sockaddr*>(&addr1), sizeof(addr1))); + RETURN_ERROR_IF_SYSCALL_FAIL(connect(socks->second_fd(), socks->first_addr(), + socks->first_addr_size())); MaybeSave(); // Successful connection. - return absl::make_unique<AddrFDSocketPair>(sock1, sock2, addr1, addr2); + return socks; } Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, @@ -429,6 +438,21 @@ Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, }; } +Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, + int protocol, bool dual_stack) { + return [=]() -> PosixErrorOr<std::unique_ptr<FDSocketPair>> { + int sock1; + RETURN_ERROR_IF_SYSCALL_FAIL(sock1 = socket(domain, type, protocol)); + MaybeSave(); // Successful socket creation. + + int sock2; + RETURN_ERROR_IF_SYSCALL_FAIL(sock2 = socket(domain, type, protocol)); + MaybeSave(); // Successful socket creation. + + return absl::make_unique<FDSocketPair>(sock1, sock2); + }; +} + SocketPairKind Reversed(SocketPairKind const& base) { auto const& creator = base.creator; return SocketPairKind{ diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h index 6d84b3fa8..826374dc6 100644 --- a/test/syscalls/linux/socket_test_util.h +++ b/test/syscalls/linux/socket_test_util.h @@ -273,6 +273,11 @@ Creator<SocketPair> UDPBidirectionalBindSocketPairCreator(int domain, int type, int protocol, bool dual_stack); +// UDPUnboundSocketPairCreator returns a Creator<SocketPair> that obtains file +// descriptors by creating UDP sockets. +Creator<SocketPair> UDPUnboundSocketPairCreator(int domain, int type, + int protocol, bool dual_stack); + // A SocketPairKind couples a human-readable description of a socket pair with // a function that creates such a socket pair. struct SocketPairKind { |