summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls')
-rw-r--r--test/syscalls/linux/packet_socket.cc74
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound.cc80
-rw-r--r--test/syscalls/linux/tcp_socket.cc123
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) {