From 52a51a8e20b3e5c28eb1e66bd57203216cf644c5 Mon Sep 17 00:00:00 2001 From: Kevin Krakauer Date: Tue, 2 Apr 2019 11:12:29 -0700 Subject: Add a raw socket transport endpoint and use it for raw ICMP sockets. Having raw socket code together will make it easier to add support for other raw network protocols. Currently, only ICMP uses the raw endpoint. However, adding support for other protocols such as UDP shouldn't be much more difficult than adding a few switch cases. PiperOrigin-RevId: 241564875 Change-Id: I77e03adafe4ce0fd29ba2d5dfdc547d2ae8f25bf --- test/syscalls/linux/raw_socket_ipv4.cc | 421 ++++++++++++++++++++++++++------- 1 file changed, 334 insertions(+), 87 deletions(-) (limited to 'test/syscalls/linux') 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 #include #include +#include #include #include +#include +#include #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(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(buf + sizeof(struct iphdr)); + struct icmphdr* icmp = + reinterpret_cast(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(&icmp.un.echo.sequence); + icmp.un.echo.sequence = *static_cast(&icmp.un.echo.sequence); ASSERT_THAT(RetryEINTR(sendto)(ping_sock.get(), &icmp, sizeof(icmp), 0, - (struct sockaddr *)&addr_, sizeof(addr_)), + reinterpret_cast(&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(buf); + struct icmphdr* icmp = reinterpret_cast(buf); return icmp->type == type; }; auto match_type_raw = [=](char buf[kBufSize]) { - struct icmphdr *icmp = - reinterpret_cast(buf + sizeof(struct iphdr)); + struct icmphdr* icmp = + reinterpret_cast(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(&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(&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(&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(&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(&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(&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(&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(&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(&addr_), sizeof(addr_)), + SyscallSucceeds()); + ASSERT_THAT( + connect(s_, reinterpret_cast(&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(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(const_cast(&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))); -- cgit v1.2.3