diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 414f662b9..0f173a10c 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -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 } diff --git a/pkg/tcpip/tests/integration/multicast_broadcast_test.go b/pkg/tcpip/tests/integration/multicast_broadcast_test.go index 0a538b821..52b88873f 100644 --- a/pkg/tcpip/tests/integration/multicast_broadcast_test.go +++ b/pkg/tcpip/tests/integration/multicast_broadcast_test.go @@ -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) + } +} diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index 05773a29c..896eaa516 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -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(&send_addr.addr)->sin_port = + reinterpret_cast(&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.