Support binding to multicast and broadcast addresses

This fixes the issue of not being able to bind to either a multicast or
broadcast address as well as to send and receive data from it. The way to solve
this is to treat these addresses similar to the ANY address and register their
transport endpoint ID with the global stack's demuxer rather than the NIC's.
That way there is no need to require an endpoint with that multicast or
broadcast address. The stack's demuxer is in fact the only correct one to use,
because neither broadcast- nor multicast-bound sockets care which NIC a
packet was received on (for multicast a join is still needed to receive packets
on a NIC).

I also took the liberty of refactoring udp_test.go to consolidate a lot of
duplicate code and make it easier to create repetitive tests that test the same
feature for a variety of packet and socket types. For this purpose I created a
"flowType" that represents two things: 1) the type of packet being sent or
received and 2) the type of socket used for the test. E.g., a "multicastV4in6"
flow represents a V4-mapped multicast packet run through a V6-dual socket.

This allows writing significantly simpler tests. A nice example is testTTL().

PiperOrigin-RevId: 264766909
This commit is contained in:
Chris Kuiper 2019-08-21 22:53:07 -07:00 committed by gVisor bot
parent 5fd63d1c7f
commit 8d9276ed56
4 changed files with 926 additions and 503 deletions

View File

@ -249,6 +249,11 @@ func (e *endpoint) prepareForWrite(to *tcpip.FullAddress) (retry bool, err *tcpi
// specified address is a multicast address.
func (e *endpoint) connectRoute(nicid tcpip.NICID, addr tcpip.FullAddress, netProto tcpip.NetworkProtocolNumber) (stack.Route, tcpip.NICID, *tcpip.Error) {
localAddr := e.id.LocalAddress
if isBroadcastOrMulticast(localAddr) {
// A packet can only originate from a unicast address (i.e., an interface).
localAddr = ""
}
if header.IsV4MulticastAddress(addr.Addr) || header.IsV6MulticastAddress(addr.Addr) {
if nicid == 0 {
nicid = e.multicastNICID
@ -448,7 +453,12 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error {
}
nicID := v.NIC
if v.InterfaceAddr == header.IPv4Any {
// The interface address is considered not-set if it is empty or contains
// all-zeros. The former represent the zero-value in golang, the latter the
// same in a setsockopt(IP_ADD_MEMBERSHIP, &ip_mreqn) syscall.
allZeros := header.IPv4Any
if len(v.InterfaceAddr) == 0 || v.InterfaceAddr == allZeros {
if nicID == 0 {
r, err := e.stack.FindRoute(0, "", v.MulticastAddr, header.IPv4ProtocolNumber, false /* multicastLoop */)
if err == nil {
@ -914,8 +924,8 @@ func (e *endpoint) bindLocked(addr tcpip.FullAddress) *tcpip.Error {
}
nicid := addr.NIC
if len(addr.Addr) != 0 {
// A local address was specified, verify that it's valid.
if len(addr.Addr) != 0 && !isBroadcastOrMulticast(addr.Addr) {
// A local unicast address was specified, verify that it's valid.
nicid = e.stack.CheckLocalAddress(addr.NIC, netProto, addr.Addr)
if nicid == 0 {
return tcpip.ErrBadLocalAddress
@ -1064,3 +1074,7 @@ func (e *endpoint) State() uint32 {
// TODO(b/112063468): Translate internal state to values returned by Linux.
return 0
}
func isBroadcastOrMulticast(a tcpip.Address) bool {
return a == header.IPv4Broadcast || header.IsV4MulticastAddress(a) || header.IsV6MulticastAddress(a)
}

View File

@ -97,7 +97,8 @@ func (e *endpoint) Resume(s *stack.Stack) {
if err != nil {
panic(err)
}
} else if len(e.id.LocalAddress) != 0 { // stateBound
} else if len(e.id.LocalAddress) != 0 && !isBroadcastOrMulticast(e.id.LocalAddress) { // stateBound
// A local unicast address is specified, verify that it's valid.
if e.stack.CheckLocalAddress(e.regNICID, netProto, e.id.LocalAddress) == 0 {
panic(tcpip.ErrBadLocalAddress)
}

File diff suppressed because it is too large Load Diff

View File

@ -30,6 +30,7 @@ namespace gvisor {
namespace testing {
constexpr char kMulticastAddress[] = "224.0.2.1";
constexpr char kBroadcastAddress[] = "255.255.255.255";
TestAddress V4Multicast() {
TestAddress t("V4Multicast");
@ -40,6 +41,15 @@ TestAddress V4Multicast() {
return t;
}
TestAddress V4Broadcast() {
TestAddress t("V4Broadcast");
t.addr.ss_family = AF_INET;
t.addr_len = sizeof(sockaddr_in);
reinterpret_cast<sockaddr_in*>(&t.addr)->sin_addr.s_addr =
inet_addr(kBroadcastAddress);
return t;
}
// Check that packets are not received without a group membership. Default send
// interface configured by bind.
TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNoGroup) {
@ -1426,5 +1436,249 @@ TEST_P(IPv4UDPUnboundSocketPairTest,
}
}
// Check that a receiving socket can bind to the multicast address before
// joining the group and receive data once the group has been joined.
TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenJoinThenReceive) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Bind second socket (receiver) to the multicast address.
auto receiver_addr = V4Multicast();
ASSERT_THAT(bind(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
// Update receiver_addr with the correct port number.
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// Register to receive multicast packets.
ip_mreqn group = {};
group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
group.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo"));
ASSERT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
&group, sizeof(group)),
SyscallSucceeds());
// Send a multicast packet on the first socket out the loopback interface.
ip_mreq iface = {};
iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
&iface, sizeof(iface)),
SyscallSucceeds());
auto sendto_addr = V4Multicast();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(
RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
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),
MSG_DONTWAIT),
SyscallSucceedsWithValue(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that a receiving socket can bind to the multicast address and won't
// receive multicast data if it hasn't joined the group.
TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenNoJoinThenNoReceive) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Bind second socket (receiver) to the multicast address.
auto receiver_addr = V4Multicast();
ASSERT_THAT(bind(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
// Update receiver_addr with the correct port number.
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// Send a multicast packet on the first socket out the loopback interface.
ip_mreq iface = {};
iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK);
ASSERT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF,
&iface, sizeof(iface)),
SyscallSucceeds());
auto sendto_addr = V4Multicast();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(
RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Check that we don't receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
MSG_DONTWAIT),
SyscallFailsWithErrno(EAGAIN));
}
// Check that a socket can bind to a multicast address and still send out
// packets.
TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToMcastThenSend) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Bind second socket (receiver) to the ANY address.
auto receiver_addr = V4Any();
ASSERT_THAT(bind(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// Bind the first socket (sender) to the multicast address.
auto sender_addr = V4Multicast();
ASSERT_THAT(
bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
sender_addr.addr_len),
SyscallSucceeds());
socklen_t sender_addr_len = sender_addr.addr_len;
ASSERT_THAT(getsockname(sockets->first_fd(),
reinterpret_cast<sockaddr*>(&sender_addr.addr),
&sender_addr_len),
SyscallSucceeds());
EXPECT_EQ(sender_addr_len, sender_addr.addr_len);
// Send a packet on the first socket to the loopback address.
auto sendto_addr = V4Loopback();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(
RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
MSG_DONTWAIT),
SyscallSucceedsWithValue(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that a receiving socket can bind to the broadcast address and receive
// broadcast packets.
TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToBcastThenReceive) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Bind second socket (receiver) to the broadcast address.
auto receiver_addr = V4Broadcast();
ASSERT_THAT(bind(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// Send a broadcast packet on the first socket out the loopback interface.
EXPECT_THAT(setsockopt(sockets->first_fd(), SOL_SOCKET, SO_BROADCAST,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceedsWithValue(0));
// Note: Binding to the loopback interface makes the broadcast go out of it.
auto sender_bind_addr = V4Loopback();
ASSERT_THAT(bind(sockets->first_fd(),
reinterpret_cast<sockaddr*>(&sender_bind_addr.addr),
sender_bind_addr.addr_len),
SyscallSucceeds());
auto sendto_addr = V4Broadcast();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(
RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
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),
MSG_DONTWAIT),
SyscallSucceedsWithValue(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that a socket can bind to the broadcast address and still send out
// packets.
TEST_P(IPv4UDPUnboundSocketPairTest, TestBindToBcastThenSend) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
// Bind second socket (receiver) to the ANY address.
auto receiver_addr = V4Any();
ASSERT_THAT(bind(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(sockets->second_fd(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// Bind the first socket (sender) to the broadcast address.
auto sender_addr = V4Broadcast();
ASSERT_THAT(
bind(sockets->first_fd(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
sender_addr.addr_len),
SyscallSucceeds());
socklen_t sender_addr_len = sender_addr.addr_len;
ASSERT_THAT(getsockname(sockets->first_fd(),
reinterpret_cast<sockaddr*>(&sender_addr.addr),
&sender_addr_len),
SyscallSucceeds());
EXPECT_EQ(sender_addr_len, sender_addr.addr_len);
// Send a packet on the first socket to the loopback address.
auto sendto_addr = V4Loopback();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(
RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf),
MSG_DONTWAIT),
SyscallSucceedsWithValue(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
} // namespace testing
} // namespace gvisor