mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
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:
committed by
gVisor bot
parent
004b4e727b
commit
2dbd37fd74
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user