diff options
Diffstat (limited to 'test/syscalls')
-rw-r--r-- | test/syscalls/linux/packet_socket.cc | 74 | ||||
-rw-r--r-- | test/syscalls/linux/socket_ipv6_udp_unbound.cc | 80 | ||||
-rw-r--r-- | test/syscalls/linux/tcp_socket.cc | 123 |
3 files changed, 273 insertions, 4 deletions
diff --git a/test/syscalls/linux/packet_socket.cc b/test/syscalls/linux/packet_socket.cc index 43828a52e..c706848fc 100644 --- a/test/syscalls/linux/packet_socket.cc +++ b/test/syscalls/linux/packet_socket.cc @@ -77,6 +77,68 @@ class PacketSocketTest : public ::testing::TestWithParam<int> { FileDescriptor socket_; }; +TEST_P(PacketSocketTest, GetSockName) { + { + // First check the local address of an unbound packet socket. + sockaddr_ll addr; + socklen_t addrlen = sizeof(addr); + ASSERT_THAT(getsockname(socket_.get(), reinterpret_cast<sockaddr*>(&addr), + &addrlen), + SyscallSucceeds()); + // sockaddr_ll ends with an 8 byte physical address field, but only the + // bytes that are used in the sockaddr_ll.sll_addr field are included in the + // address length. Seems Linux used to return the size of sockaddr_ll, but + // https://github.com/torvalds/linux/commit/0fb375fb9b93b7d822debc6a734052337ccfdb1f + // changed things to only return `sizeof(sockaddr_ll) + sll.sll_addr`. + ASSERT_THAT(addrlen, AnyOf(Eq(sizeof(addr)), + Eq(sizeof(addr) - sizeof(addr.sll_addr)))); + EXPECT_EQ(addr.sll_family, AF_PACKET); + EXPECT_EQ(addr.sll_ifindex, 0); + if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { + // TODO(https://gvisor.dev/issue/6530): Do not assume all interfaces have + // an ethernet address. + EXPECT_EQ(addr.sll_halen, ETH_ALEN); + } else { + EXPECT_EQ(addr.sll_halen, 0); + } + EXPECT_EQ(ntohs(addr.sll_protocol), 0); + EXPECT_EQ(addr.sll_hatype, 0); + } + // Next we bind the socket to loopback before checking the local address. + const sockaddr_ll bind_addr = { + .sll_family = AF_PACKET, + .sll_protocol = htons(ETH_P_IP), + .sll_ifindex = ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()), + }; + ASSERT_THAT(bind(socket_.get(), reinterpret_cast<const sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallSucceeds()); + { + sockaddr_ll addr; + socklen_t addrlen = sizeof(addr); + ASSERT_THAT(getsockname(socket_.get(), reinterpret_cast<sockaddr*>(&addr), + &addrlen), + SyscallSucceeds()); + ASSERT_THAT(addrlen, + AnyOf(Eq(sizeof(addr)), + Eq(sizeof(addr) - sizeof(addr.sll_addr) + ETH_ALEN))); + EXPECT_EQ(addr.sll_family, AF_PACKET); + EXPECT_EQ(addr.sll_ifindex, bind_addr.sll_ifindex); + EXPECT_EQ(addr.sll_halen, ETH_ALEN); + // Bound to loopback which has the all zeroes address. + for (int i = 0; i < addr.sll_halen; ++i) { + EXPECT_EQ(addr.sll_addr[i], 0) << "byte mismatch @ idx = " << i; + } + EXPECT_EQ(ntohs(addr.sll_protocol), htons(addr.sll_protocol)); + if (IsRunningOnGvisor() && !IsRunningWithHostinet()) { + // TODO(https://gvisor.dev/issue/6621): Support populating sll_hatype. + EXPECT_EQ(addr.sll_hatype, 0); + } else { + EXPECT_EQ(addr.sll_hatype, ARPHRD_LOOPBACK); + } + } +} + TEST_P(PacketSocketTest, RebindProtocol) { const bool kEthHdrIncluded = GetParam() == SOCK_RAW; @@ -162,10 +224,9 @@ TEST_P(PacketSocketTest, RebindProtocol) { // addresses only use 6 bytes. Linux used to return sizeof(sockaddr_ll)-2 // here, but returns sizeof(sockaddr_ll) since // https://github.com/torvalds/linux/commit/b2cf86e1563e33a14a1c69b3e508d15dc12f804c. - ASSERT_THAT(src_len, ::testing::AnyOf( - ::testing::Eq(sizeof(src)), - ::testing::Eq(sizeof(src) - sizeof(src.sll_addr) + - ETH_ALEN))); + ASSERT_THAT(src_len, + AnyOf(Eq(sizeof(src)), + Eq(sizeof(src) - sizeof(src.sll_addr) + ETH_ALEN))); EXPECT_EQ(src.sll_family, AF_PACKET); EXPECT_EQ(src.sll_ifindex, loopback_index); EXPECT_EQ(src.sll_halen, ETH_ALEN); @@ -214,6 +275,11 @@ TEST_P(PacketSocketTest, RebindProtocol) { ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(ETH_P_IP)); ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); ASSERT_NO_FATAL_FAILURE(test_recv(counter)); + + // A zero valued protocol number should not change the bound network protocol. + ASSERT_NO_FATAL_FAILURE(bind_to_network_protocol(0)); + ASSERT_NO_FATAL_FAILURE(send_udp_message(++counter)); + ASSERT_NO_FATAL_FAILURE(test_recv(counter)); } INSTANTIATE_TEST_SUITE_P(AllPacketSocketTests, PacketSocketTest, diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound.cc b/test/syscalls/linux/socket_ipv6_udp_unbound.cc index 612fd531c..c431bf771 100644 --- a/test/syscalls/linux/socket_ipv6_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv6_udp_unbound.cc @@ -39,6 +39,86 @@ namespace gvisor { namespace testing { +using ::testing::IsNull; +using ::testing::NotNull; + +TEST_P(IPv6UDPUnboundSocketTest, IPv6PacketInfo) { + auto sender_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_socket = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + + auto sender_addr = V6Loopback(); + ASSERT_THAT(bind(sender_socket->get(), AsSockAddr(&sender_addr.addr), + sender_addr.addr_len), + SyscallSucceeds()); + + auto receiver_addr = V6Loopback(); + ASSERT_THAT(bind(receiver_socket->get(), AsSockAddr(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver_socket->get(), + AsSockAddr(&receiver_addr.addr), &receiver_addr_len), + SyscallSucceeds()); + ASSERT_EQ(receiver_addr_len, receiver_addr.addr_len); + + // Make sure we get IPv6 packet information as control messages. + constexpr int one = 1; + ASSERT_THAT(setsockopt(receiver_socket->get(), IPPROTO_IPV6, IPV6_RECVPKTINFO, + &one, sizeof(one)), + SyscallSucceeds()); + + // Send a packet - we don't care about the packet itself, only the returned + // IPV6_PKTINFO control message. + char send_buf[200]; + RandomizeBuffer(send_buf, sizeof(send_buf)); + ASSERT_THAT(RetryEINTR(sendto)( + sender_socket->get(), send_buf, sizeof(send_buf), 0, + AsSockAddr(&receiver_addr.addr), receiver_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Check that we received the packet with the packet information control + // message. + char recv_buf[sizeof(send_buf) + 1]; + in6_pktinfo received_pktinfo; + char recv_cmsg_buf[CMSG_SPACE(sizeof(received_pktinfo))]; + iovec recv_iov = { + .iov_base = recv_buf, + .iov_len = sizeof(recv_buf), + }; + msghdr recv_msg = { + .msg_iov = &recv_iov, + .msg_iovlen = 1, + .msg_control = recv_cmsg_buf, + .msg_controllen = sizeof(recv_cmsg_buf), + }; + ASSERT_THAT( + RetryEINTR(recvmsg)(receiver_socket->get(), &recv_msg, 0 /* flags */), + SyscallSucceedsWithValue(sizeof(send_buf))); + EXPECT_EQ(memcmp(send_buf, recv_buf, sizeof(send_buf)), 0); + + cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msg); + ASSERT_THAT(cmsg, NotNull()); + EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in6_pktinfo))); + EXPECT_EQ(cmsg->cmsg_level, IPPROTO_IPV6); + EXPECT_EQ(cmsg->cmsg_type, IPV6_PKTINFO); + // As per cmsg(3) (https://www.man7.org/linux/man-pages/man3/cmsg.3.html), + // + // CMSG_DATA() returns a pointer to the data portion of a cmsghdr. The + // pointer returned cannot be assumed to be suitably aligned for accessing + // arbitrary payload data types. Applications should not cast it to a + // pointer type matching the payload, but should instead use memcpy(3) to + // copy data to or from a suitably declared object. + memcpy(&received_pktinfo, CMSG_DATA(cmsg), sizeof(received_pktinfo)); + EXPECT_EQ( + memcmp(&received_pktinfo.ipi6_addr, + &(reinterpret_cast<sockaddr_in6*>(&sender_addr.addr)->sin6_addr), + sizeof(received_pktinfo.ipi6_addr)), + 0); + EXPECT_EQ(received_pktinfo.ipi6_ifindex, + ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex())); + EXPECT_THAT(CMSG_NXTHDR(&recv_msg, cmsg), IsNull()); +} + // Test that socket will receive IP_RECVORIGDSTADDR control message. TEST_P(IPv6UDPUnboundSocketTest, SetAndReceiveIPReceiveOrigDstAddr) { auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc index 3fbbf1423..bbcb7e4fd 100644 --- a/test/syscalls/linux/tcp_socket.cc +++ b/test/syscalls/linux/tcp_socket.cc @@ -2067,6 +2067,69 @@ TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) { EXPECT_EQ(got, 0); } +void ShutdownConnectingSocket(int domain, int shutdown_mode) { + FileDescriptor bound_s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(domain, SOCK_STREAM, IPPROTO_TCP)); + + sockaddr_storage bound_addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(domain)); + socklen_t bound_addrlen = sizeof(bound_addr); + + ASSERT_THAT(bind(bound_s.get(), AsSockAddr(&bound_addr), bound_addrlen), + SyscallSucceeds()); + + // Start listening. Use a zero backlog to only allow one connection in the + // accept queue. + ASSERT_THAT(listen(bound_s.get(), 0), SyscallSucceeds()); + + // Get the addresses the socket is bound to because the port is chosen by the + // stack. + ASSERT_THAT( + getsockname(bound_s.get(), AsSockAddr(&bound_addr), &bound_addrlen), + SyscallSucceeds()); + + // Establish a connection. But do not accept it. That way, subsequent + // connections will not get a SYN-ACK because the queue is full. + FileDescriptor connected_s = + ASSERT_NO_ERRNO_AND_VALUE(Socket(domain, SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(connect(connected_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallSucceeds()); + + FileDescriptor connecting_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(domain, SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); + ASSERT_THAT(connect(connecting_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(EINPROGRESS)); + + // Now the test: when a connecting socket is shutdown, the socket should enter + // an error state. + EXPECT_THAT(shutdown(connecting_s.get(), shutdown_mode), SyscallSucceeds()); + + // We don't need to specify any events to get POLLHUP or POLLERR because these + // are always tracked. + struct pollfd poll_fd = { + .fd = connecting_s.get(), + }; + + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 0), SyscallSucceedsWithValue(1)); + EXPECT_EQ(poll_fd.revents, POLLHUP | POLLERR); +} + +TEST_P(SimpleTcpSocketTest, ShutdownReadConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_RD); +} + +TEST_P(SimpleTcpSocketTest, ShutdownWriteConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_WR); +} + +TEST_P(SimpleTcpSocketTest, ShutdownReadWriteConnectingSocket) { + ShutdownConnectingSocket(GetParam(), SHUT_RDWR); +} + // Tests that connecting to an unspecified address results in ECONNREFUSED. TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { sockaddr_storage addr; @@ -2088,6 +2151,66 @@ TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) { } } +TEST_P(SimpleTcpSocketTest, OnlyAcknowledgeBacklogConnections) { + // At some point, there was a bug in gVisor where a connection could be + // SYN-ACK'd by the server even if the accept queue was already full. This was + // possible because once the listener would process an ACK, it would move the + // new connection in the accept queue asynchronously. It created an + // opportunity where the listener could process another SYN before completing + // the delivery that would have filled the accept queue. + // + // This test checks that there is no such race. + + std::array<std::optional<ScopedThread>, 100> threads; + for (auto& thread : threads) { + thread.emplace([]() { + FileDescriptor bound_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + + sockaddr_storage bound_addr = + ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam())); + socklen_t bound_addrlen = sizeof(bound_addr); + + ASSERT_THAT(bind(bound_s.get(), AsSockAddr(&bound_addr), bound_addrlen), + SyscallSucceeds()); + + // Start listening. Use a zero backlog to only allow one connection in the + // accept queue. + ASSERT_THAT(listen(bound_s.get(), 0), SyscallSucceeds()); + + // Get the addresses the socket is bound to because the port is chosen by + // the stack. + ASSERT_THAT( + getsockname(bound_s.get(), AsSockAddr(&bound_addr), &bound_addrlen), + SyscallSucceeds()); + + // Establish a connection, but do not accept it. + FileDescriptor connected_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP)); + ASSERT_THAT(connect(connected_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallSucceeds()); + + // Immediately attempt to establish another connection. Use non blocking + // socket because this is expected to timeout. + FileDescriptor connecting_s = ASSERT_NO_ERRNO_AND_VALUE( + Socket(GetParam(), SOCK_STREAM | SOCK_NONBLOCK, IPPROTO_TCP)); + ASSERT_THAT(connect(connecting_s.get(), + reinterpret_cast<const struct sockaddr*>(&bound_addr), + bound_addrlen), + SyscallFailsWithErrno(EINPROGRESS)); + + struct pollfd poll_fd = { + .fd = connecting_s.get(), + .events = POLLOUT, + }; + EXPECT_THAT(RetryEINTR(poll)(&poll_fd, 1, 10), + SyscallSucceedsWithValue(0)); + }); + } +} + // Tests that send will return EWOULDBLOCK initially with large buffer and will // succeed after the send buffer size is increased. TEST_P(TcpSocketTest, SendUnblocksOnSendBufferIncrease) { |