diff options
Diffstat (limited to 'test/syscalls')
-rw-r--r-- | test/syscalls/linux/raw_socket_ipv4.cc | 421 |
1 files changed, 334 insertions, 87 deletions
diff --git a/test/syscalls/linux/raw_socket_ipv4.cc b/test/syscalls/linux/raw_socket_ipv4.cc index 19cded07f..b13806dcb 100644 --- a/test/syscalls/linux/raw_socket_ipv4.cc +++ b/test/syscalls/linux/raw_socket_ipv4.cc @@ -16,8 +16,11 @@ #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/ip_icmp.h> +#include <sys/poll.h> #include <sys/socket.h> #include <sys/types.h> +#include <unistd.h> +#include <algorithm> #include "gtest/gtest.h" #include "test/syscalls/linux/socket_test_util.h" @@ -39,22 +42,26 @@ class RawSocketTest : public ::testing::Test { // Closes the socket created by SetUp(). void TearDown() override; - // The socket used for both reading and writing. - int s_; + // Checks that both an ICMP echo request and reply are received. Calls should + // be wrapped in ASSERT_NO_FATAL_FAILURE. + void ExpectICMPSuccess(const struct icmphdr& icmp); - // The loopback address. - struct sockaddr_in addr_; + void SendEmptyICMP(const struct icmphdr& icmp); + + void SendEmptyICMPTo(int sock, struct sockaddr_in* addr, + const struct icmphdr& icmp); - void SendEmptyICMP(struct icmphdr *icmp); + void ReceiveICMP(char* recv_buf, size_t recv_buf_len, size_t expected_size, + struct sockaddr_in* src); - void SendEmptyICMPTo(int sock, struct sockaddr_in *addr, - struct icmphdr *icmp); + void ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len, + size_t expected_size, struct sockaddr_in* src, int sock); - void ReceiveICMP(char *recv_buf, size_t recv_buf_len, size_t expected_size, - struct sockaddr_in *src); + // The socket used for both reading and writing. + int s_; - void ReceiveICMPFrom(char *recv_buf, size_t recv_buf_len, - size_t expected_size, struct sockaddr_in *src, int sock); + // The loopback address. + struct sockaddr_in addr_; }; void RawSocketTest::SetUp() { @@ -100,49 +107,9 @@ TEST_F(RawSocketTest, SendAndReceive) { icmp.checksum = 2011; icmp.un.echo.sequence = 2012; icmp.un.echo.id = 2014; - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(&icmp)); - - // We're going to receive both the echo request and reply, but the order is - // indeterminate. - char recv_buf[512]; - struct sockaddr_in src; - bool received_request = false; - bool received_reply = false; - - for (int i = 0; i < 2; i++) { - // Receive the packet. - ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf, ABSL_ARRAYSIZE(recv_buf), - sizeof(struct icmphdr), &src)); - EXPECT_EQ(memcmp(&src, &addr_, sizeof(sockaddr_in)), 0); - struct icmphdr *recvd_icmp = - reinterpret_cast<struct icmphdr *>(recv_buf + sizeof(struct iphdr)); - switch (recvd_icmp->type) { - case ICMP_ECHO: - EXPECT_FALSE(received_request); - received_request = true; - // The packet should be identical to what we sent. - EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)), - 0); - break; - - case ICMP_ECHOREPLY: - EXPECT_FALSE(received_reply); - received_reply = true; - // Most fields should be the same. - EXPECT_EQ(recvd_icmp->code, icmp.code); - EXPECT_EQ(recvd_icmp->un.echo.sequence, icmp.un.echo.sequence); - EXPECT_EQ(recvd_icmp->un.echo.id, icmp.un.echo.id); - // A couple are different. - EXPECT_EQ(recvd_icmp->type, ICMP_ECHOREPLY); - // The checksum is computed in such a way that it is guaranteed to have - // changed. - EXPECT_NE(recvd_icmp->checksum, icmp.checksum); - break; - } - } + ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); - ASSERT_TRUE(received_request); - ASSERT_TRUE(received_reply); + ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); } // We should be able to create multiple raw sockets for the same protocol and @@ -162,7 +129,7 @@ TEST_F(RawSocketTest, MultipleSocketReceive) { icmp.checksum = 2014; icmp.un.echo.sequence = 2016; icmp.un.echo.id = 2018; - ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(&icmp)); + ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); // Both sockets will receive the echo request and reply in indeterminate // order, so we'll need to read 2 packets from each. @@ -191,12 +158,14 @@ TEST_F(RawSocketTest, MultipleSocketReceive) { int types[] = {ICMP_ECHO, ICMP_ECHOREPLY}; for (int type : types) { auto match_type = [=](char buf[kBufSize]) { - struct icmphdr *icmp = - reinterpret_cast<struct icmphdr *>(buf + sizeof(struct iphdr)); + struct icmphdr* icmp = + reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr)); return icmp->type == type; }; - char *icmp1 = *std::find_if(recv_buf1.begin(), recv_buf1.end(), match_type); - char *icmp2 = *std::find_if(recv_buf2.begin(), recv_buf2.end(), match_type); + const char* icmp1 = + *std::find_if(recv_buf1.begin(), recv_buf1.end(), match_type); + const char* icmp2 = + *std::find_if(recv_buf2.begin(), recv_buf2.end(), match_type); ASSERT_NE(icmp1, *recv_buf1.end()); ASSERT_NE(icmp2, *recv_buf2.end()); EXPECT_EQ(memcmp(icmp1 + sizeof(struct iphdr), icmp2 + sizeof(struct iphdr), @@ -217,10 +186,10 @@ TEST_F(RawSocketTest, RawAndPingSockets) { struct icmphdr icmp; icmp.type = ICMP_ECHO; icmp.code = 0; - icmp.un.echo.sequence = - *static_cast<unsigned short *>(&icmp.un.echo.sequence); + icmp.un.echo.sequence = *static_cast<unsigned short*>(&icmp.un.echo.sequence); ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, sizeof(icmp), 0, - (struct sockaddr *)&addr_, sizeof(addr_)), + reinterpret_cast<struct sockaddr*>(&addr_), + sizeof(addr_)), SyscallSucceedsWithValue(sizeof(icmp))); // Both sockets will receive the echo request and reply in indeterminate @@ -247,12 +216,12 @@ TEST_F(RawSocketTest, RawAndPingSockets) { int types[] = {ICMP_ECHO, ICMP_ECHOREPLY}; for (int type : types) { auto match_type_ping = [=](char buf[kBufSize]) { - struct icmphdr *icmp = reinterpret_cast<struct icmphdr *>(buf); + struct icmphdr* icmp = reinterpret_cast<struct icmphdr*>(buf); return icmp->type == type; }; auto match_type_raw = [=](char buf[kBufSize]) { - struct icmphdr *icmp = - reinterpret_cast<struct icmphdr *>(buf + sizeof(struct iphdr)); + struct icmphdr* icmp = + reinterpret_cast<struct icmphdr*>(buf + sizeof(struct iphdr)); return icmp->type == type; }; @@ -266,39 +235,317 @@ TEST_F(RawSocketTest, RawAndPingSockets) { } } -void RawSocketTest::SendEmptyICMP(struct icmphdr *icmp) { +// Test that shutting down an unconnected socket fails. +TEST_F(RawSocketTest, FailShutdownWithoutConnect) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallFailsWithErrno(ENOTCONN)); + ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallFailsWithErrno(ENOTCONN)); +} + +// Test that writing to a shutdown write socket fails. +TEST_F(RawSocketTest, FailWritingToShutdown) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + ASSERT_THAT(shutdown(s_, SHUT_WR), SyscallSucceeds()); + + char c; + ASSERT_THAT(RetryEINTR(write)(s_, &c, sizeof(c)), + SyscallFailsWithErrno(EPIPE)); +} + +// Test that reading from a shutdown read socket gets nothing. +TEST_F(RawSocketTest, FailReadingFromShutdown) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + ASSERT_THAT(shutdown(s_, SHUT_RD), SyscallSucceeds()); + + char c; + ASSERT_THAT(read(s_, &c, sizeof(c)), SyscallSucceedsWithValue(0)); +} + +// Test that listen() fails. +TEST_F(RawSocketTest, FailListen) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT(listen(s_, 1), SyscallFailsWithErrno(ENOTSUP)); +} + +// Test that accept() fails. +TEST_F(RawSocketTest, FailAccept) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + struct sockaddr saddr; + socklen_t addrlen; + ASSERT_THAT(accept(s_, &saddr, &addrlen), SyscallFailsWithErrno(ENOTSUP)); +} + +// Test that getpeername() returns nothing before connect(). +TEST_F(RawSocketTest, FailGetPeerNameBeforeConnect) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + struct sockaddr saddr; + socklen_t addrlen; + ASSERT_THAT(getpeername(s_, &saddr, &addrlen), + SyscallFailsWithErrno(ENOTCONN)); +} + +// Test that getpeername() returns something after connect(). +TEST_F(RawSocketTest, GetPeerName) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + struct sockaddr saddr; + socklen_t addrlen; + ASSERT_THAT(getpeername(s_, &saddr, &addrlen), SyscallSucceeds()); + ASSERT_GT(addrlen, 0); +} + +// Test that the socket is writable immediately. +TEST_F(RawSocketTest, PollWritableImmediately) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + struct pollfd pfd = {}; + pfd.fd = s_; + pfd.events = POLLOUT; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1)); +} + +// Test that the socket isn't readable before receiving anything. +TEST_F(RawSocketTest, PollNotReadableInitially) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + // Try to receive data with MSG_DONTWAIT, which returns immediately if there's + // nothing to be read. + char buf[117]; + ASSERT_THAT(RetryEINTR(recv)(s_, buf, sizeof(buf), MSG_DONTWAIT), + SyscallFailsWithErrno(EAGAIN)); +} + +// Test that the socket becomes readable once something is written to it. +TEST_F(RawSocketTest, PollTriggeredOnWrite) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + // Write something so that there's data to be read. + struct icmphdr icmp = {}; + ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); + + struct pollfd pfd = {}; + pfd.fd = s_; + pfd.events = POLLIN; + ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, 10000), SyscallSucceedsWithValue(1)); +} + +// Test that we can connect() to a valid IP (loopback). +TEST_F(RawSocketTest, ConnectToLoopback) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); +} + +// Test that connect() sends packets to the right place. +TEST_F(RawSocketTest, SendAndReceiveViaConnect) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + + // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, + // and ID. None of that should matter for raw sockets - the kernel should + // still give us the packet. + struct icmphdr icmp; + icmp.type = ICMP_ECHO; + icmp.code = 0; + icmp.checksum = 2001; + icmp.un.echo.sequence = 2003; + icmp.un.echo.id = 2004; + ASSERT_THAT(send(s_, &icmp, sizeof(icmp), 0), + SyscallSucceedsWithValue(sizeof(icmp))); + + ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); +} + +// Test that calling send() without connect() fails. +TEST_F(RawSocketTest, SendWithoutConnectFails) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, + // and ID. None of that should matter for raw sockets - the kernel should + // still give us the packet. + struct icmphdr icmp; + icmp.type = ICMP_ECHO; + icmp.code = 0; + icmp.checksum = 2015; + icmp.un.echo.sequence = 2017; + icmp.un.echo.id = 2019; + ASSERT_THAT(send(s_, &icmp, sizeof(icmp), 0), + SyscallFailsWithErrno(ENOTCONN)); +} + +// Bind to localhost. +TEST_F(RawSocketTest, BindToLocalhost) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); +} + +// Bind to a different address. +TEST_F(RawSocketTest, BindToInvalid) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + struct sockaddr_in bind_addr = {}; + bind_addr.sin_family = AF_INET; + bind_addr.sin_addr = {1}; // 1.0.0.0 - An address that we can't bind to. + ASSERT_THAT(bind(s_, reinterpret_cast<struct sockaddr*>(&bind_addr), + sizeof(bind_addr)), + SyscallFailsWithErrno(EADDRNOTAVAIL)); +} + +// Bind to localhost, then send and receive packets. +TEST_F(RawSocketTest, BindSendAndReceive) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + + // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, + // and ID. None of that should matter for raw sockets - the kernel should + // still give us the packet. + struct icmphdr icmp; + icmp.type = ICMP_ECHO; + icmp.code = 0; + icmp.checksum = 2001; + icmp.un.echo.sequence = 2004; + icmp.un.echo.id = 2007; + ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); + + ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); +} + +// Bind and connect to localhost and send/receive packets. +TEST_F(RawSocketTest, BindConnectSendAndReceive) { + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW))); + + ASSERT_THAT( + bind(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + ASSERT_THAT( + connect(s_, reinterpret_cast<struct sockaddr*>(&addr_), sizeof(addr_)), + SyscallSucceeds()); + + // Prepare and send an ICMP packet. Use arbitrary junk for checksum, sequence, + // and ID. None of that should matter for raw sockets - the kernel should + // still give us the packet. + struct icmphdr icmp; + icmp.type = ICMP_ECHO; + icmp.code = 0; + icmp.checksum = 2009; + icmp.un.echo.sequence = 2010; + icmp.un.echo.id = 7; + ASSERT_NO_FATAL_FAILURE(SendEmptyICMP(icmp)); + + ASSERT_NO_FATAL_FAILURE(ExpectICMPSuccess(icmp)); +} + +void RawSocketTest::ExpectICMPSuccess(const struct icmphdr& icmp) { + // We're going to receive both the echo request and reply, but the order is + // indeterminate. + char recv_buf[512]; + struct sockaddr_in src; + bool received_request = false; + bool received_reply = false; + + for (int i = 0; i < 2; i++) { + // Receive the packet. + ASSERT_NO_FATAL_FAILURE(ReceiveICMP(recv_buf, ABSL_ARRAYSIZE(recv_buf), + sizeof(struct icmphdr), &src)); + EXPECT_EQ(memcmp(&src, &addr_, sizeof(sockaddr_in)), 0); + struct icmphdr* recvd_icmp = + reinterpret_cast<struct icmphdr*>(recv_buf + sizeof(struct iphdr)); + switch (recvd_icmp->type) { + case ICMP_ECHO: + EXPECT_FALSE(received_request); + received_request = true; + // The packet should be identical to what we sent. + EXPECT_EQ(memcmp(recv_buf + sizeof(struct iphdr), &icmp, sizeof(icmp)), + 0); + break; + + case ICMP_ECHOREPLY: + EXPECT_FALSE(received_reply); + received_reply = true; + // Most fields should be the same. + EXPECT_EQ(recvd_icmp->code, icmp.code); + EXPECT_EQ(recvd_icmp->un.echo.sequence, icmp.un.echo.sequence); + EXPECT_EQ(recvd_icmp->un.echo.id, icmp.un.echo.id); + // A couple are different. + EXPECT_EQ(recvd_icmp->type, ICMP_ECHOREPLY); + // The checksum is computed in such a way that it is guaranteed to have + // changed. + EXPECT_NE(recvd_icmp->checksum, icmp.checksum); + break; + } + } + + ASSERT_TRUE(received_request); + ASSERT_TRUE(received_reply); +} + +void RawSocketTest::SendEmptyICMP(const struct icmphdr& icmp) { ASSERT_NO_FATAL_FAILURE(SendEmptyICMPTo(s_, &addr_, icmp)); } -void RawSocketTest::SendEmptyICMPTo(int sock, struct sockaddr_in *addr, - struct icmphdr *icmp) { - struct iovec iov = {.iov_base = icmp, .iov_len = sizeof(*icmp)}; - struct msghdr msg { - .msg_name = addr, .msg_namelen = sizeof(*addr), .msg_iov = &iov, - .msg_iovlen = 1, .msg_control = NULL, .msg_controllen = 0, .msg_flags = 0, - }; - ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(sizeof(*icmp))); +void RawSocketTest::SendEmptyICMPTo(int sock, struct sockaddr_in* addr, + const struct icmphdr& icmp) { + // It's safe to use const_cast here because sendmsg won't modify the iovec. + struct iovec iov = {}; + iov.iov_base = static_cast<void*>(const_cast<struct icmphdr*>(&icmp)); + iov.iov_len = sizeof(icmp); + struct msghdr msg = {}; + msg.msg_name = addr; + msg.msg_namelen = sizeof(*addr); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; + ASSERT_THAT(sendmsg(sock, &msg, 0), SyscallSucceedsWithValue(sizeof(icmp))); } -void RawSocketTest::ReceiveICMP(char *recv_buf, size_t recv_buf_len, - size_t expected_size, struct sockaddr_in *src) { +void RawSocketTest::ReceiveICMP(char* recv_buf, size_t recv_buf_len, + size_t expected_size, struct sockaddr_in* src) { ASSERT_NO_FATAL_FAILURE( ReceiveICMPFrom(recv_buf, recv_buf_len, expected_size, src, s_)); } -void RawSocketTest::ReceiveICMPFrom(char *recv_buf, size_t recv_buf_len, +void RawSocketTest::ReceiveICMPFrom(char* recv_buf, size_t recv_buf_len, size_t expected_size, - struct sockaddr_in *src, int sock) { - struct iovec iov = {.iov_base = recv_buf, .iov_len = recv_buf_len}; - struct msghdr msg = { - .msg_name = src, - .msg_namelen = sizeof(*src), - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = NULL, - .msg_controllen = 0, - .msg_flags = 0, - }; + struct sockaddr_in* src, int sock) { + struct iovec iov = {}; + iov.iov_base = recv_buf; + iov.iov_len = recv_buf_len; + struct msghdr msg = {}; + msg.msg_name = src; + msg.msg_namelen = sizeof(*src); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = NULL; + msg.msg_controllen = 0; + msg.msg_flags = 0; // We should receive the ICMP packet plus 20 bytes of IP header. ASSERT_THAT(recvmsg(sock, &msg, 0), SyscallSucceedsWithValue(expected_size + sizeof(struct iphdr))); |