diff options
author | Ian Gudger <igudger@google.com> | 2019-02-15 18:39:10 -0800 |
---|---|---|
committer | Shentubot <shentubot@google.com> | 2019-02-15 18:40:15 -0800 |
commit | c611dbc5a7399922588e3fd99b22bda19f684afe (patch) | |
tree | 2bf1ac2dd626e3996d889135a32c4807345bde16 | |
parent | e2dcce544297b2364ebd4874f6db0390aafa366e (diff) |
Implement IP_MULTICAST_IF.
This allows setting a default send interface for IPv4 multicast. IPv6 support
will come later.
PiperOrigin-RevId: 234251379
Change-Id: I65922341cd8b8880f690fae3eeb7ddfa47c8c173
-rw-r--r-- | pkg/sentry/socket/epsocket/epsocket.go | 66 | ||||
-rw-r--r-- | pkg/tcpip/stack/stack.go | 11 | ||||
-rw-r--r-- | pkg/tcpip/tcpip.go | 7 | ||||
-rw-r--r-- | pkg/tcpip/transport/udp/endpoint.go | 115 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv4_udp_unbound.cc | 261 |
5 files changed, 418 insertions, 42 deletions
diff --git a/pkg/sentry/socket/epsocket/epsocket.go b/pkg/sentry/socket/epsocket/epsocket.go index 3a9d1182f..3392ac645 100644 --- a/pkg/sentry/socket/epsocket/epsocket.go +++ b/pkg/sentry/socket/epsocket/epsocket.go @@ -27,7 +27,6 @@ package epsocket import ( "bytes" "math" - "strings" "sync" "syscall" "time" @@ -191,6 +190,15 @@ func New(t *kernel.Task, family int, skType transport.SockType, queue *waiter.Qu var sockAddrInetSize = int(binary.Size(linux.SockAddrInet{})) var sockAddrInet6Size = int(binary.Size(linux.SockAddrInet6{})) +// bytesToIPAddress converts an IPv4 or IPv6 address from the user to the +// netstack representation taking any addresses into account. +func bytesToIPAddress(addr []byte) tcpip.Address { + if bytes.Equal(addr, make([]byte, 4)) || bytes.Equal(addr, make([]byte, 16)) { + return "" + } + return tcpip.Address(addr) +} + // GetAddress reads an sockaddr struct from the given address and converts it // to the FullAddress format. It supports AF_UNIX, AF_INET and AF_INET6 // addresses. @@ -231,12 +239,9 @@ func GetAddress(sfamily int, addr []byte) (tcpip.FullAddress, *syserr.Error) { binary.Unmarshal(addr[:sockAddrInetSize], usermem.ByteOrder, &a) out := tcpip.FullAddress{ - Addr: tcpip.Address(a.Addr[:]), + Addr: bytesToIPAddress(a.Addr[:]), Port: ntohs(a.Port), } - if out.Addr == "\x00\x00\x00\x00" { - out.Addr = "" - } return out, nil case linux.AF_INET6: @@ -247,15 +252,12 @@ func GetAddress(sfamily int, addr []byte) (tcpip.FullAddress, *syserr.Error) { binary.Unmarshal(addr[:sockAddrInet6Size], usermem.ByteOrder, &a) out := tcpip.FullAddress{ - Addr: tcpip.Address(a.Addr[:]), + Addr: bytesToIPAddress(a.Addr[:]), Port: ntohs(a.Port), } if isLinkLocal(out.Addr) { out.NIC = tcpip.NICID(a.Scope_id) } - if out.Addr == tcpip.Address(strings.Repeat("\x00", 16)) { - out.Addr = "" - } return out, nil default: @@ -864,6 +866,30 @@ func getSockOptIP(t *kernel.Task, ep commonEndpoint, name, outLen int) (interfac return int32(v), nil + case linux.IP_MULTICAST_IF: + if outLen < inetMulticastRequestSize { + return nil, syserr.ErrInvalidArgument + } + + var v tcpip.MulticastInterfaceOption + if err := ep.GetSockOpt(&v); err != nil { + return nil, syserr.TranslateNetstackError(err) + } + + a, _ := ConvertAddress(linux.AF_INET, tcpip.FullAddress{Addr: v.InterfaceAddr}) + + rv := linux.InetMulticastRequestWithNIC{ + linux.InetMulticastRequest{ + InterfaceAddr: a.(linux.SockAddrInet).Addr, + }, + int32(v.NIC), + } + + if outLen >= inetMulticastRequestWithNICSize { + return rv, nil + } + return rv.InetMulticastRequest, nil + default: emitUnimplementedEventIP(t, name) } @@ -1148,7 +1174,9 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s } return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.AddMembershipOption{ - NIC: tcpip.NICID(req.InterfaceIndex), + NIC: tcpip.NICID(req.InterfaceIndex), + // TODO: Change AddMembership to use the standard + // any address representation. InterfaceAddr: tcpip.Address(req.InterfaceAddr[:]), MulticastAddr: tcpip.Address(req.MulticastAddr[:]), })) @@ -1160,19 +1188,29 @@ func setSockOptIP(t *kernel.Task, ep commonEndpoint, name int, optVal []byte) *s } return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.RemoveMembershipOption{ - NIC: tcpip.NICID(req.InterfaceIndex), + NIC: tcpip.NICID(req.InterfaceIndex), + // TODO: Change DropMembership to use the standard + // any address representation. InterfaceAddr: tcpip.Address(req.InterfaceAddr[:]), MulticastAddr: tcpip.Address(req.MulticastAddr[:]), })) case linux.IP_MULTICAST_IF: + req, err := copyInMulticastRequest(optVal) + if err != nil { + return err + } + + return syserr.TranslateNetstackError(ep.SetSockOpt(tcpip.MulticastInterfaceOption{ + NIC: tcpip.NICID(req.InterfaceIndex), + InterfaceAddr: bytesToIPAddress(req.InterfaceAddr[:]), + })) + + case linux.MCAST_JOIN_GROUP: // FIXME: Disallow IP-level multicast group options by // default. These will need to be supported by appropriately plumbing // the level through to the network stack (if at all). However, we // still allow setting TTL, and multicast-enable/disable type options. - fallthrough - case linux.MCAST_JOIN_GROUP: - // FIXME: Implement MCAST_JOIN_GROUP. t.Kernel().EmitUnimplementedEvent(t) return syserr.ErrInvalidArgument diff --git a/pkg/tcpip/stack/stack.go b/pkg/tcpip/stack/stack.go index 854ebe1bb..252c79317 100644 --- a/pkg/tcpip/stack/stack.go +++ b/pkg/tcpip/stack/stack.go @@ -565,6 +565,17 @@ func (s *Stack) EnableNIC(id tcpip.NICID) *tcpip.Error { return nil } +// CheckNIC checks if a NIC is usable. +func (s *Stack) CheckNIC(id tcpip.NICID) bool { + s.mu.RLock() + nic, ok := s.nics[id] + s.mu.RUnlock() + if ok { + return nic.linkEP.IsAttached() + } + return false +} + // NICSubnets returns a map of NICIDs to their associated subnets. func (s *Stack) NICSubnets() map[tcpip.NICID][]tcpip.Subnet { s.mu.RLock() diff --git a/pkg/tcpip/tcpip.go b/pkg/tcpip/tcpip.go index 3cd431d4c..a6e47397a 100644 --- a/pkg/tcpip/tcpip.go +++ b/pkg/tcpip/tcpip.go @@ -473,6 +473,13 @@ type KeepaliveCountOption int // TTL value for multicast messages. The default is 1. type MulticastTTLOption uint8 +// MulticastInterfaceOption is used by SetSockOpt/GetSockOpt to specify a +// default interface for multicast. +type MulticastInterfaceOption struct { + NIC NICID + InterfaceAddr Address +} + // MembershipOption is used by SetSockOpt/GetSockOpt as an argument to // AddMembershipOption and RemoveMembershipOption. type MembershipOption struct { diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index fa8f02e46..9c3881d63 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -69,17 +69,19 @@ type endpoint struct { rcvClosed bool // The following fields are protected by the mu mutex. - mu sync.RWMutex `state:"nosave"` - sndBufSize int - id stack.TransportEndpointID - state endpointState - bindNICID tcpip.NICID - regNICID tcpip.NICID - route stack.Route `state:"manual"` - dstPort uint16 - v6only bool - multicastTTL uint8 - reusePort bool + mu sync.RWMutex `state:"nosave"` + sndBufSize int + id stack.TransportEndpointID + state endpointState + bindNICID tcpip.NICID + regNICID tcpip.NICID + route stack.Route `state:"manual"` + dstPort uint16 + v6only bool + multicastTTL uint8 + multicastAddr tcpip.Address + multicastNICID tcpip.NICID + reusePort bool // shutdownFlags represent the current shutdown state of the endpoint. shutdownFlags tcpip.ShutdownFlags @@ -251,6 +253,33 @@ func (e *endpoint) prepareForWrite(to *tcpip.FullAddress) (retry bool, err *tcpi return true, nil } +// connectRoute establishes a route to the specified interface or the +// configured multicast interface if no interface is specified and the +// specified address is a multicast address. +func (e *endpoint) connectRoute(nicid tcpip.NICID, addr tcpip.FullAddress) (stack.Route, tcpip.NICID, tcpip.NetworkProtocolNumber, *tcpip.Error) { + netProto, err := e.checkV4Mapped(&addr, false) + if err != nil { + return stack.Route{}, 0, 0, err + } + + localAddr := e.id.LocalAddress + if header.IsV4MulticastAddress(addr.Addr) || header.IsV6MulticastAddress(addr.Addr) { + if nicid == 0 { + nicid = e.multicastNICID + } + if localAddr == "" { + localAddr = e.multicastAddr + } + } + + // Find a route to the desired destination. + r, err := e.stack.FindRoute(nicid, localAddr, addr.Addr, netProto) + if err != nil { + return stack.Route{}, 0, 0, err + } + return r, nicid, netProto, nil +} + // Write writes data to the endpoint's peer. This method does not block // if the data cannot be written. func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, <-chan struct{}, *tcpip.Error) { @@ -318,15 +347,7 @@ func (e *endpoint) Write(p tcpip.Payload, opts tcpip.WriteOptions) (uintptr, <-c nicid = e.bindNICID } - toCopy := *to - to = &toCopy - netProto, err := e.checkV4Mapped(to, false) - if err != nil { - return 0, nil, err - } - - // Find the enpoint. - r, err := e.stack.FindRoute(nicid, e.id.LocalAddress, to.Addr, netProto) + r, _, _, err := e.connectRoute(nicid, *to) if err != nil { return 0, nil, err } @@ -394,6 +415,42 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error { e.multicastTTL = uint8(v) e.mu.Unlock() + case tcpip.MulticastInterfaceOption: + e.mu.Lock() + defer e.mu.Unlock() + + fa := tcpip.FullAddress{Addr: v.InterfaceAddr} + netProto, err := e.checkV4Mapped(&fa, false) + if err != nil { + return err + } + nic := v.NIC + addr := fa.Addr + + if nic == 0 && addr == "" { + e.multicastAddr = "" + e.multicastNICID = 0 + break + } + + if nic != 0 { + if !e.stack.CheckNIC(nic) { + return tcpip.ErrBadLocalAddress + } + } else { + nic = e.stack.CheckLocalAddress(0, netProto, addr) + if nic == 0 { + return tcpip.ErrBadLocalAddress + } + } + + if e.bindNICID != 0 && e.bindNICID != nic { + return tcpip.ErrInvalidEndpointState + } + + e.multicastNICID = nic + e.multicastAddr = addr + case tcpip.AddMembershipOption: nicID := v.NIC if v.InterfaceAddr != header.IPv4Any { @@ -445,7 +502,6 @@ func (e *endpoint) SetSockOpt(opt interface{}) *tcpip.Error { e.mu.Lock() e.reusePort = v != 0 e.mu.Unlock() - return nil } return nil } @@ -501,6 +557,15 @@ func (e *endpoint) GetSockOpt(opt interface{}) *tcpip.Error { e.mu.Unlock() return nil + case *tcpip.MulticastInterfaceOption: + e.mu.Lock() + *o = tcpip.MulticastInterfaceOption{ + e.multicastNICID, + e.multicastAddr, + } + e.mu.Unlock() + return nil + case *tcpip.ReusePortOption: e.mu.RLock() v := e.reusePort @@ -610,13 +675,7 @@ func (e *endpoint) Connect(addr tcpip.FullAddress) *tcpip.Error { return tcpip.ErrInvalidEndpointState } - netProto, err := e.checkV4Mapped(&addr, false) - if err != nil { - return err - } - - // Find a route to the desired destination. - r, err := e.stack.FindRoute(nicid, e.id.LocalAddress, addr.Addr, netProto) + r, nicid, netProto, err := e.connectRoute(nicid, addr) if err != nil { return err } diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index 4058324a2..2d702179e 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -278,6 +278,238 @@ TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNic) { EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); } +// Check that multicast works when the default send interface is confgured by +// IP_MULTICAST_IF, the send address is specified in sendto, and the group +// membership is configured by address. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Set the default send interface. + ip_mreq iface = {}; + iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + auto receiver_addr = V4Any(); + EXPECT_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; + EXPECT_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_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->second_fd(), 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)); + EXPECT_THAT( + RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&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( + RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + +// Check that multicast works when the default send interface is confgured by +// IP_MULTICAST_IF, the send address is specified in sendto, and the group +// membership is configured by NIC ID. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNic) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Set the default send interface. + ip_mreqn iface = {}; + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + auto receiver_addr = V4Any(); + EXPECT_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; + EXPECT_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")); + EXPECT_THAT(setsockopt(sockets->second_fd(), 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)); + EXPECT_THAT( + RetryEINTR(sendto)(sockets->first_fd(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&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( + RetryEINTR(recv)(sockets->second_fd(), recv_buf, sizeof(recv_buf), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + +// Check that multicast works when the default send interface is confgured by +// IP_MULTICAST_IF, the send address is specified in connect, and the group +// membership is configured by address. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfAddrConnect) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Set the default send interface. + ip_mreq iface = {}; + iface.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + auto receiver_addr = V4Any(); + EXPECT_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; + EXPECT_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_mreq group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + group.imr_interface.s_addr = htonl(INADDR_LOOPBACK); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + auto connect_addr = V4Multicast(); + reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = + reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; + EXPECT_THAT( + RetryEINTR(connect)(sockets->first_fd(), + reinterpret_cast<sockaddr*>(&connect_addr.addr), + connect_addr.addr_len), + SyscallSucceeds()); + + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT( + RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0), + 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), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + +// Check that multicast works when the default send interface is confgured by +// IP_MULTICAST_IF, the send address is specified in connect, and the group +// membership is configured by NIC ID. +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackIfNicConnect) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + // Set the default send interface. + ip_mreqn iface = {}; + iface.imr_ifindex = ASSERT_NO_ERRNO_AND_VALUE(InterfaceIndex("lo")); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()); + + // Bind the second FD to the v4 any address to ensure that we can receive the + // multicast packet. + auto receiver_addr = V4Any(); + EXPECT_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; + EXPECT_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")); + EXPECT_THAT(setsockopt(sockets->second_fd(), IPPROTO_IP, IP_ADD_MEMBERSHIP, + &group, sizeof(group)), + SyscallSucceeds()); + + // Send a multicast packet. + auto connect_addr = V4Multicast(); + reinterpret_cast<sockaddr_in*>(&connect_addr.addr)->sin_port = + reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; + EXPECT_THAT( + RetryEINTR(connect)(sockets->first_fd(), + reinterpret_cast<sockaddr*>(&connect_addr.addr), + connect_addr.addr_len), + SyscallSucceeds()); + + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + EXPECT_THAT( + RetryEINTR(send)(sockets->first_fd(), send_buf, sizeof(send_buf), 0), + 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), 0), + SyscallSucceedsWithValue(sizeof(recv_buf))); + + EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf))); +} + // Check that dropping a group membership that does not exist fails. TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastInvalidDrop) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); @@ -407,5 +639,34 @@ TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastDropNic) { SyscallFailsWithErrno(EAGAIN)); } +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfZero) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + ip_mreqn iface = {}; + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallSucceeds()); +} + +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfInvalidNic) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + ip_mreqn iface = {}; + iface.imr_ifindex = -1; + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastIfInvalidAddr) { + auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + + ip_mreq iface = {}; + iface.imr_interface.s_addr = inet_addr("255.255.255"); + EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_IP, IP_MULTICAST_IF, + &iface, sizeof(iface)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + } // namespace testing } // namespace gvisor |