Adjust route when looping multicast packets

Multicast packets are special in that their destination address does not
identify a specific interface. When sending out such a packet the multicast
address is the remote address, but for incoming packets it is the local
address. Hence, when looping a multicast packet, the route needs to be
tweaked to reflect this.

PiperOrigin-RevId: 251739298
This commit is contained in:
Chris Kuiper
2019-06-05 16:08:29 -07:00
committed by Shentubot
parent c08fcaa364
commit d18bb4f38a
4 changed files with 145 additions and 2 deletions
+3 -1
View File
@@ -215,7 +215,9 @@ func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prepen
views[0] = hdr.View()
views = append(views, payload.Views()...)
vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views)
e.HandlePacket(r, vv)
loopedR := r.MakeLoopedRoute()
e.HandlePacket(&loopedR, vv)
loopedR.Release()
}
if loop&stack.PacketOut == 0 {
return nil
+3 -1
View File
@@ -108,7 +108,9 @@ func (e *endpoint) WritePacket(r *stack.Route, gso *stack.GSO, hdr buffer.Prepen
views[0] = hdr.View()
views = append(views, payload.Views()...)
vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views)
e.HandlePacket(r, vv)
loopedR := r.MakeLoopedRoute()
e.HandlePacket(&loopedR, vv)
loopedR.Release()
}
if loop&stack.PacketOut == 0 {
return nil
+10
View File
@@ -187,3 +187,13 @@ func (r *Route) Clone() Route {
r.ref.incRef()
return *r
}
// MakeLoopedRoute duplicates the given route and tweaks it in case of multicast.
func (r *Route) MakeLoopedRoute() Route {
l := r.Clone()
if header.IsV4MulticastAddress(r.RemoteAddress) || header.IsV6MulticastAddress(r.RemoteAddress) {
l.RemoteAddress, l.LocalAddress = l.LocalAddress, l.RemoteAddress
l.RemoteLinkAddress = l.LocalLinkAddress
}
return l
}
@@ -559,5 +559,134 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that two sockets can join the same multicast group at the same time,
// and both will receive data on it.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, TestSendMulticastToTwo) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
std::unique_ptr<FileDescriptor> receivers[2] = {
ASSERT_NO_ERRNO_AND_VALUE(NewSocket()),
ASSERT_NO_ERRNO_AND_VALUE(NewSocket())};
ip_mreq group = {};
group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
auto receiver_addr = V4Any();
int bound_port = 0;
for (auto& receiver : receivers) {
ASSERT_THAT(setsockopt(receiver->get(), SOL_SOCKET, SO_REUSEPORT,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceeds());
// Bind the receiver to the v4 any address to ensure that we can receive the
// multicast packet.
ASSERT_THAT(
bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(receiver->get(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
// On the first iteration, save the port we are bound to. On the second
// iteration, verify the port is the same as the one from the first
// iteration. In other words, both sockets listen on the same port.
if (bound_port == 0) {
bound_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
} else {
EXPECT_EQ(bound_port,
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port);
}
// Register to receive multicast packets.
ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP,
&group, sizeof(group)),
SyscallSucceeds());
}
// Send a multicast packet to the group and verify both receivers get it.
auto send_addr = V4Multicast();
reinterpret_cast<sockaddr_in*>(&send_addr.addr)->sin_port = bound_port;
char send_buf[200];
RandomizeBuffer(send_buf, sizeof(send_buf));
ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0,
reinterpret_cast<sockaddr*>(&send_addr.addr),
send_addr.addr_len),
SyscallSucceedsWithValue(sizeof(send_buf)));
for (auto& receiver : receivers) {
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
RetryEINTR(recv)(receiver->get(), recv_buf, sizeof(recv_buf), 0),
SyscallSucceedsWithValue(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
}
// Check that when receiving a looped-back multicast packet, its source address
// is not a multicast address.
TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest,
IpMulticastLoopbackFromAddr) {
auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto receiver_addr = V4Any();
ASSERT_THAT(
bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(receiver->get(),
reinterpret_cast<sockaddr*>(&receiver_addr.addr),
&receiver_addr_len),
SyscallSucceeds());
EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
int receiver_port =
reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port;
ip_mreq group = {};
group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress);
ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
sizeof(group)),
SyscallSucceeds());
// Connect to the multicast address. This binds us to the outgoing interface
// and allows us to get its IP (to be compared against the src-IP on the
// receiver side).
auto sendto_addr = V4Multicast();
reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port;
ASSERT_THAT(RetryEINTR(connect)(
sender->get(), reinterpret_cast<sockaddr*>(&sendto_addr.addr),
sendto_addr.addr_len),
SyscallSucceeds());
TestAddress sender_addr("");
ASSERT_THAT(
getsockname(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
&sender_addr.addr_len),
SyscallSucceeds());
ASSERT_EQ(sizeof(struct sockaddr_in), sender_addr.addr_len);
sockaddr_in* sender_addr_in =
reinterpret_cast<sockaddr_in*>(&sender_addr.addr);
// Send a multicast packet.
char send_buf[4] = {};
ASSERT_THAT(RetryEINTR(send)(sender->get(), send_buf, sizeof(send_buf), 0),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Receive a multicast packet.
char recv_buf[sizeof(send_buf)] = {};
TestAddress src_addr("");
ASSERT_THAT(
RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0,
reinterpret_cast<sockaddr*>(&src_addr.addr),
&src_addr.addr_len),
SyscallSucceedsWithValue(sizeof(recv_buf)));
ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len);
sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr);
// Verify that the received source IP:port matches the sender one.
EXPECT_EQ(sender_addr_in->sin_port, src_addr_in->sin_port);
EXPECT_EQ(sender_addr_in->sin_addr.s_addr, src_addr_in->sin_addr.s_addr);
}
} // namespace testing
} // namespace gvisor