Fix IP_ADD_MEMBERSHIP address checking to match linux.

When handling a call to setsockopt for IP_ADD_MEMBERSHIP with an ip_mreqn
struct with a non-zero interface IP address and interface index, Linux
checks the interface index first and, if it matches a device, ignores the
address.

Linux: https://github.com/torvalds/linux/blob/15205c2829ca2cbb5ece5ceaafe1171a8470e62b/net/ipv4/igmp.c#L1829-L1837

Fixes #7880

PiperOrigin-RevId: 467787302
This commit is contained in:
Lucas Manning
2022-08-15 16:22:01 -07:00
committed by gVisor bot
parent 004b4e727b
commit 2dbd37fd74
3 changed files with 112 additions and 3 deletions
+7 -3
View File
@@ -1437,17 +1437,21 @@ func (s *Stack) CheckLocalAddress(nicID tcpip.NICID, protocol tcpip.NetworkProto
s.mu.RLock()
defer s.mu.RUnlock()
// If a NIC is specified, we try to find the address there only.
// If a NIC is specified, use its NIC id.
if nicID != 0 {
nic, ok := s.nics[nicID]
if !ok {
return 0
}
// In IPv4, linux only checks the interface. If it matches, then it does
// not bother with the address.
// https://github.com/torvalds/linux/blob/15205c2829ca2cbb5ece5ceaafe1171a8470e62b/net/ipv4/igmp.c#L1829-L1837
if protocol == header.IPv4ProtocolNumber {
return nic.id
}
if nic.CheckLocalAddress(protocol, addr) {
return nic.id
}
return 0
}
@@ -728,3 +728,52 @@ func TestUDPAddRemoveMembershipSocketOption(t *testing.T) {
})
}
}
func TestAddMembershipInterfacePrecedence(t *testing.T) {
const nicID = 1
multicastAddr := tcpip.Address("\xe0\x01\x02\x03")
proto := header.IPv4ProtocolNumber
// This address is nonsensical. If the precedence is correct, this should not
// matter, because ADD_IP_MEMBERSHIP should consider the interface index
// and use that before checking the address.
localAddr := tcpip.AddressWithPrefix{
Address: testutil.MustParse4("8.0.8.0"),
PrefixLen: 24,
}
s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{ipv4.NewProtocol, ipv6.NewProtocol},
TransportProtocols: []stack.TransportProtocolFactory{udp.NewProtocol},
})
e := channel.New(0, defaultMTU, "")
defer e.Close()
if err := s.CreateNIC(nicID, e); err != nil {
t.Fatalf("CreateNIC(%d, _): %s", nicID, err)
}
protoAddr := tcpip.ProtocolAddress{Protocol: proto, AddressWithPrefix: localAddr}
if err := s.AddProtocolAddress(nicID, protoAddr, stack.AddressProperties{}); err != nil {
t.Fatalf("AddProtocolAddress(%d, %+v, {}): %s", nicID, protoAddr, err)
}
var wq waiter.Queue
ep, err := s.NewEndpoint(udp.ProtocolNumber, proto, &wq)
if err != nil {
t.Fatalf("NewEndpoint(%d, %d, _): %s", udp.ProtocolNumber, proto, err)
}
defer ep.Close()
bindAddr := tcpip.FullAddress{Port: utils.LocalPort}
if err := ep.Bind(bindAddr); err != nil {
t.Fatalf("ep.Bind(%#v): %s", bindAddr, err)
}
memOpt := tcpip.MembershipOption{MulticastAddr: multicastAddr}
memOpt.NIC = nicID
memOpt.InterfaceAddr = localAddr.Address
// Add membership should succeed when the interface index is specified,
// even if a bad interface address is specified.
addOpt := tcpip.AddMembershipOption(memOpt)
if err := ep.SetSockOpt(&addOpt); err != nil {
t.Fatalf("ep.SetSockOpt(&%#v): %s", addOpt, err)
}
}
@@ -257,6 +257,62 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) {
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that multicast works when an interface identifier and address are
// provided for multicast registration. The interface should take priority.
TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfaceIndexAndAddr) {
auto socket1 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
auto socket2 = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
// Bind the first FD to the loopback. This is an alternative to
// IP_MULTICAST_IF for setting the default send interface.
auto sender_addr = V4Loopback();
ASSERT_THAT(
bind(socket1->get(), AsSockAddr(&sender_addr.addr), sender_addr.addr_len),
SyscallSucceeds());
// Bind the second FD to the v4 any address to ensure that we can receive the
// multicast packet.
auto receiver_addr = V4Any();
ASSERT_THAT(bind(socket2->get(), AsSockAddr(&receiver_addr.addr),
receiver_addr.addr_len),
SyscallSucceeds());
socklen_t receiver_addr_len = receiver_addr.addr_len;
ASSERT_THAT(getsockname(socket2->get(), AsSockAddr(&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(GetLoopbackIndex());
// Intentionally use an address that isn't assigned to the loopback device.
// The index should take precedence.
group.imr_address.s_addr = htonl(0x08080808);
ASSERT_THAT(setsockopt(socket2->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group,
sizeof(group)),
SyscallSucceeds());
// Send a multicast packet.
auto send_addr = V4Multicast();
reinterpret_cast<sockaddr_in*>(&send_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)(socket1->get(), send_buf, sizeof(send_buf), 0,
AsSockAddr(&send_addr.addr), send_addr.addr_len),
SyscallSucceedsWithValue(sizeof(send_buf)));
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
// Check that multicast works when the default send interface is configured by
// IP_MULTICAST_IF, the send address is specified in sendto, and the group
// membership is configured by address.