| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <arpa/inet.h> |
| #include <netinet/icmp6.h> |
| #include <netinet/ip.h> |
| #include <netinet/ip6.h> |
| #include <netinet/udp.h> |
| #include <sys/socket.h> |
| |
| #if defined(__linux__) |
| #include <sys/syscall.h> |
| |
| #include <linux/capability.h> |
| #endif |
| |
| #include <zircon/compiler.h> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/connectivity/network/tests/os.h" |
| #include "src/connectivity/network/tests/socket/util.h" |
| |
| namespace { |
| |
| TEST(RawSocketTest, ProtocolZeroNotSupported) { |
| // This test intentionally does not check if we have access to raw sockets as |
| // this test is independent of raw socket capabilities; a protocol value of 0 |
| // is always not supported for raw IP sockets. |
| ASSERT_EQ(socket(AF_INET, SOCK_RAW, 0), -1); |
| ASSERT_EQ(errno, EPROTONOSUPPORT) << strerror(errno); |
| } |
| |
| TEST(RawSocketTest, UnsupportedOps) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| EXPECT_EQ(listen(fd.get(), 0), -1); |
| EXPECT_EQ(errno, EOPNOTSUPP) << strerror(errno); |
| EXPECT_EQ(accept(fd.get(), nullptr, nullptr), -1); |
| EXPECT_EQ(errno, EOPNOTSUPP) << strerror(errno); |
| } |
| |
| TEST(RawSocketTest, SendToDifferentProtocolV6) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| if (kIsFuchsia) { |
| GTEST_SKIP() << "Does not pass on fuchsia (https://gvisor.dev/issue/6422)"; |
| } |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| const struct sockaddr_in6 send_addr = { |
| .sin6_family = AF_INET6, |
| // If spcified, the port value must not be different from the associated |
| // protocol of the raw IPv6 socket. |
| .sin6_port = IPPROTO_TCP, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| |
| char payload[1] = {}; |
| ASSERT_EQ(sendto(fd.get(), &payload, sizeof(payload), 0, |
| reinterpret_cast<const struct sockaddr*>(&send_addr), sizeof(send_addr)), |
| -1); |
| ASSERT_EQ(errno, EINVAL); |
| |
| EXPECT_EQ(close(fd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(RawSocketTest, SendToDifferentProtocolV4) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| const uint32_t network_endian_loopback_addr = htonl(INADDR_LOOPBACK); |
| |
| fbl::unique_fd udp; |
| ASSERT_TRUE(udp = fbl::unique_fd(socket(AF_INET, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| fbl::unique_fd tcp; |
| ASSERT_TRUE(tcp = fbl::unique_fd(socket(AF_INET, SOCK_RAW, IPPROTO_TCP))) << strerror(errno); |
| |
| const struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr = |
| { |
| .s_addr = network_endian_loopback_addr, |
| }, |
| }; |
| |
| char addrbuf[INET_ADDRSTRLEN]; |
| const char* addrstr = inet_ntop(addr.sin_family, &addr.sin_addr, addrbuf, sizeof(addrbuf)); |
| ASSERT_NE(addrstr, nullptr); |
| |
| auto send = [&addr](const fbl::unique_fd& fd, uint16_t proto, char payload) { |
| struct sockaddr_in send_addr = addr; |
| // The port is ignored when sending through raw IPv4 sockets. |
| send_addr.sin_port = proto; |
| |
| ASSERT_EQ(sendto(fd.get(), &payload, sizeof(payload), 0, |
| reinterpret_cast<struct sockaddr*>(&send_addr), sizeof(send_addr)), |
| ssize_t(sizeof(payload))) |
| << strerror(errno); |
| }; |
| { |
| SCOPED_TRACE("try to send TCP through UDP associatd raw IP socket"); |
| ASSERT_NO_FATAL_FAILURE(send(udp, IPPROTO_TCP /* proto */, IPPROTO_UDP /* payload */)); |
| } |
| { |
| SCOPED_TRACE("try to send UDP through TCP associatd raw IP socket"); |
| ASSERT_NO_FATAL_FAILURE(send(tcp, IPPROTO_UDP /* proto */, IPPROTO_TCP /* payload */)); |
| } |
| |
| // Receive a packet through the raw socket and make sure the transport |
| // protocol matches what the socket is associated with. |
| auto recv = [&network_endian_loopback_addr, &addrstr](const fbl::unique_fd& fd, uint16_t proto, |
| char payload) { |
| char read_raw_ip_buf[sizeof(iphdr) + sizeof(payload)] = {}; |
| struct sockaddr_in peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(fd.get(), &read_raw_ip_buf, sizeof(read_raw_ip_buf), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), &peerlen), |
| ssize_t(sizeof(read_raw_ip_buf))) |
| << strerror(errno); |
| ASSERT_EQ(peerlen, sizeof(peer)); |
| iphdr* ip = reinterpret_cast<iphdr*>(read_raw_ip_buf); |
| EXPECT_EQ(ntohs(ip->tot_len), sizeof(read_raw_ip_buf)); |
| EXPECT_EQ(ntohs(ip->frag_off) & IP_OFFMASK, 0); |
| EXPECT_EQ(ip->protocol, proto); |
| EXPECT_EQ(ip->saddr, network_endian_loopback_addr); |
| EXPECT_EQ(ip->daddr, network_endian_loopback_addr); |
| EXPECT_EQ(read_raw_ip_buf[sizeof(iphdr)], payload); |
| char peerbuf[INET_ADDRSTRLEN]; |
| const char* peerstr = inet_ntop(peer.sin_family, &peer.sin_addr, peerbuf, sizeof(peerbuf)); |
| ASSERT_NE(peerstr, nullptr); |
| EXPECT_STREQ(peerstr, addrstr); |
| }; |
| |
| { |
| SCOPED_TRACE("receive UDP message on UDP associated raw socket"); |
| ASSERT_NO_FATAL_FAILURE(recv(udp, IPPROTO_UDP /* proto */, IPPROTO_UDP /* payload */)); |
| } |
| { |
| SCOPED_TRACE("receive TCP message on TCP associated raw socket"); |
| ASSERT_NO_FATAL_FAILURE(recv(tcp, IPPROTO_TCP /* proto */, IPPROTO_TCP /* payload */)); |
| } |
| |
| EXPECT_EQ(close(tcp.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(udp.release()), 0) << strerror(errno); |
| } |
| |
| TEST(RawSocketTest, SendtoRecvfromV6) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd udpfd; |
| ASSERT_TRUE(udpfd = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno); |
| |
| const uint16_t udpfd_bound_port = 1335; |
| const uint16_t udpfd_peer_port = 1336; |
| const struct sockaddr_in6 addr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| |
| { |
| struct sockaddr_in6 bind_addr = addr; |
| bind_addr.sin6_port = htons(udpfd_bound_port); |
| ASSERT_EQ( |
| bind(udpfd.get(), reinterpret_cast<const struct sockaddr*>(&bind_addr), sizeof(bind_addr)), |
| 0) |
| << strerror(errno); |
| } |
| |
| const char payload[] = {1, 2, 3, 4}; |
| char raw_udp_buf[sizeof(udphdr) + sizeof(payload)] = {}; |
| *(reinterpret_cast<udphdr*>(raw_udp_buf)) = udphdr{ |
| .uh_sport = htons(udpfd_peer_port), |
| .uh_dport = htons(udpfd_bound_port), |
| .uh_ulen = htons(sizeof(raw_udp_buf)), |
| .uh_sum = 0, // Checksumming is not required for packets that are looped |
| // back on Fuchsia. For Linux, we use IPV6_CHECKSUM below. |
| }; |
| memcpy(raw_udp_buf + sizeof(udphdr), payload, sizeof(payload)); |
| |
| fbl::unique_fd rawfd; |
| ASSERT_TRUE(rawfd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| if (!kIsFuchsia) { |
| // Fuchsia does not require checksumming if the packet is looped back but |
| // Linux always does so we ask the netstack to populate the checksum on |
| // Linux. |
| int offset = offsetof(udphdr, uh_sum); |
| ASSERT_EQ(setsockopt(rawfd.get(), IPPROTO_IPV6, IPV6_CHECKSUM, &offset, sizeof(offset)), 0) |
| << strerror(errno); |
| } |
| ASSERT_EQ(sendto(rawfd.get(), raw_udp_buf, sizeof(raw_udp_buf), 0, |
| reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| ssize_t(sizeof(raw_udp_buf))) |
| << strerror(errno); |
| |
| char addrbuf[INET_ADDRSTRLEN]; |
| const char* addrstr = inet_ntop(addr.sin6_family, &addr.sin6_addr, addrbuf, sizeof(addrbuf)); |
| ASSERT_NE(addrstr, nullptr); |
| |
| { |
| char read_payload[sizeof(payload)] = {}; |
| struct sockaddr_in6 peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(udpfd.get(), read_payload, sizeof(read_payload), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), &peerlen), |
| ssize_t(sizeof(payload))) |
| << strerror(errno); |
| ASSERT_EQ(peerlen, sizeof(peer)); |
| for (size_t i = 0; i < sizeof(payload); i++) { |
| EXPECT_EQ(payload[i], read_payload[i]) << "byte mismatch @ idx=" << i; |
| } |
| char peerbuf[INET_ADDRSTRLEN]; |
| const char* peerstr = inet_ntop(peer.sin6_family, &peer.sin6_addr, peerbuf, sizeof(peerbuf)); |
| ASSERT_NE(peerstr, nullptr); |
| EXPECT_STREQ(peerstr, addrstr); |
| EXPECT_EQ(ntohs(peer.sin6_port), udpfd_peer_port); |
| ASSERT_EQ(sendto(udpfd.get(), payload, sizeof(payload), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), peerlen), |
| ssize_t(sizeof(payload))) |
| << strerror(errno); |
| } |
| |
| auto check_packet = [&rawfd, &raw_udp_buf, addrstr]() { |
| char read_raw_udp_buf[sizeof(udphdr) + sizeof(payload)] = {}; |
| struct sockaddr_in6 peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(rawfd.get(), read_raw_udp_buf, sizeof(read_raw_udp_buf), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), &peerlen), |
| ssize_t(sizeof(read_raw_udp_buf))) |
| << strerror(errno); |
| ASSERT_EQ(peerlen, sizeof(peer)); |
| reinterpret_cast<udphdr*>(read_raw_udp_buf)->uh_sum = 0; |
| for (size_t i = 0; i < sizeof(raw_udp_buf); i++) { |
| EXPECT_EQ(raw_udp_buf[i], read_raw_udp_buf[i]) << "byte mismatch @ idx=" << i; |
| } |
| char peerbuf[INET_ADDRSTRLEN]; |
| const char* peerstr = inet_ntop(peer.sin6_family, &peer.sin6_addr, peerbuf, sizeof(peerbuf)); |
| ASSERT_NE(peerstr, nullptr); |
| EXPECT_STREQ(peerstr, addrstr); |
| }; |
| |
| // The packet we originally wrote should be received by the raw socket since |
| // the socket is bound to loopback and the UDP packet is sent to loopback. |
| // |
| // Note that for IPv6, raw sockets never return the IPv6 header, unlike IPv4. |
| { |
| SCOPED_TRACE("check packet sent with raw socket"); |
| ASSERT_NO_FATAL_FAILURE(check_packet()); |
| } |
| |
| // Validate the packet sent with the UDP socket. |
| { |
| // Update expected ports as the packet was sent from the peer. |
| udphdr* udp = reinterpret_cast<udphdr*>(raw_udp_buf); |
| udp->uh_dport = htons(udpfd_peer_port); |
| udp->uh_sport = htons(udpfd_bound_port); |
| SCOPED_TRACE("check packet sent with UDP socket"); |
| ASSERT_NO_FATAL_FAILURE(check_packet()); |
| } |
| |
| EXPECT_EQ(close(rawfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(udpfd.release()), 0) << strerror(errno); |
| } |
| |
| TEST(RawSocketTest, SendtoRecvfrom) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| const uint16_t udpfd_bound_port = 1338; |
| const uint16_t udpfd_peer_port = 1337; |
| const uint32_t network_endian_loopback_addr = htonl(INADDR_LOOPBACK); |
| |
| fbl::unique_fd udpfd; |
| ASSERT_TRUE(udpfd = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno); |
| |
| const struct sockaddr_in addr = { |
| .sin_family = AF_INET, |
| .sin_addr = |
| { |
| .s_addr = network_endian_loopback_addr, |
| }, |
| }; |
| |
| { |
| struct sockaddr_in bind_addr = addr; |
| bind_addr.sin_port = htons(udpfd_bound_port); |
| ASSERT_EQ( |
| bind(udpfd.get(), reinterpret_cast<const struct sockaddr*>(&bind_addr), sizeof(bind_addr)), |
| 0) |
| << strerror(errno); |
| } |
| |
| const char payload[] = {1, 2, 3, 4}; |
| char raw_udp_buf[sizeof(udphdr) + sizeof(payload)] = {}; |
| *(reinterpret_cast<udphdr*>(raw_udp_buf)) = udphdr{ |
| .uh_sport = htons(udpfd_peer_port), |
| .uh_dport = htons(udpfd_bound_port), |
| .uh_ulen = htons(sizeof(raw_udp_buf)), |
| .uh_sum = 0, // Checksum is optional for UDP on IPv4. |
| }; |
| memcpy(raw_udp_buf + sizeof(udphdr), payload, sizeof(payload)); |
| |
| fbl::unique_fd rawfd; |
| ASSERT_TRUE(rawfd = fbl::unique_fd(socket(AF_INET, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| ASSERT_EQ(sendto(rawfd.get(), raw_udp_buf, sizeof(raw_udp_buf), 0, |
| reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| ssize_t(sizeof(raw_udp_buf))) |
| << strerror(errno); |
| |
| char addrbuf[INET_ADDRSTRLEN]; |
| const char* addrstr = inet_ntop(addr.sin_family, &addr.sin_addr, addrbuf, sizeof(addrbuf)); |
| ASSERT_NE(addrstr, nullptr); |
| |
| auto check_payload = [&udpfd, &payload, addrstr, udpfd_peer_port]() { |
| char read_payload[sizeof(payload)] = {}; |
| struct sockaddr_in peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(udpfd.get(), read_payload, sizeof(read_payload), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), &peerlen), |
| ssize_t(sizeof(payload))) |
| << strerror(errno); |
| ASSERT_EQ(peerlen, sizeof(peer)); |
| for (size_t i = 0; i < sizeof(payload); i++) { |
| EXPECT_EQ(payload[i], read_payload[i]) << "byte mismatch @ idx=" << i; |
| } |
| char peerbuf[INET_ADDRSTRLEN]; |
| const char* peerstr = inet_ntop(peer.sin_family, &peer.sin_addr, peerbuf, sizeof(peerbuf)); |
| ASSERT_NE(peerstr, nullptr); |
| EXPECT_STREQ(peerstr, addrstr); |
| EXPECT_EQ(ntohs(peer.sin_port), udpfd_peer_port); |
| }; |
| { |
| SCOPED_TRACE("check UDP payload sent with raw socket"); |
| ASSERT_NO_FATAL_FAILURE(check_payload()); |
| } |
| { |
| int yes = 1; |
| ASSERT_EQ(setsockopt(udpfd.get(), SOL_SOCKET, SO_NO_CHECK, &yes, sizeof(yes)), 0) |
| << strerror(errno); |
| |
| struct sockaddr_in peer = addr; |
| peer.sin_port = htons(udpfd_peer_port); |
| ASSERT_EQ(sendto(udpfd.get(), payload, sizeof(payload), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), sizeof(peer)), |
| ssize_t(sizeof(payload))) |
| << strerror(errno); |
| } |
| |
| const unsigned int EXPECTED_IHL = |
| sizeof(iphdr) / 4; // IHL holds the number of header bytes in 4 byte units. |
| |
| auto check_packet = [EXPECTED_IHL, network_endian_loopback_addr, &rawfd, &raw_udp_buf, |
| addrstr]() { |
| char read_raw_ip_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(payload)] = {}; |
| struct sockaddr_in peer; |
| socklen_t peerlen = sizeof(peer); |
| ASSERT_EQ(recvfrom(rawfd.get(), read_raw_ip_buf, sizeof(read_raw_ip_buf), 0, |
| reinterpret_cast<struct sockaddr*>(&peer), &peerlen), |
| ssize_t(sizeof(read_raw_ip_buf))) |
| << strerror(errno); |
| ASSERT_EQ(peerlen, sizeof(peer)); |
| iphdr* ip = reinterpret_cast<iphdr*>(read_raw_ip_buf); |
| EXPECT_EQ(ip->version, static_cast<unsigned int>(IPVERSION)); |
| EXPECT_EQ(ip->ihl, EXPECTED_IHL); |
| EXPECT_EQ(ntohs(ip->tot_len), sizeof(iphdr) + sizeof(udphdr) + sizeof(payload)); |
| EXPECT_EQ(ntohs(ip->frag_off) & IP_OFFMASK, 0); |
| EXPECT_EQ(ip->protocol, SOL_UDP); |
| EXPECT_EQ(ip->saddr, network_endian_loopback_addr); |
| EXPECT_EQ(ip->daddr, network_endian_loopback_addr); |
| char* read_raw_udp_buf = &read_raw_ip_buf[sizeof(iphdr)]; |
| for (size_t i = 0; i < sizeof(raw_udp_buf); i++) { |
| EXPECT_EQ(raw_udp_buf[i], read_raw_udp_buf[i]) << "byte mismatch @ idx=" << i; |
| } |
| char peerbuf[INET_ADDRSTRLEN]; |
| const char* peerstr = inet_ntop(peer.sin_family, &peer.sin_addr, peerbuf, sizeof(peerbuf)); |
| ASSERT_NE(peerstr, nullptr); |
| EXPECT_STREQ(peerstr, addrstr); |
| }; |
| |
| // The packet we originally wrote should be received by the raw socket since |
| // the socket is bound to loopback and the UDP packet is sent to loopback. |
| // |
| // IPv4 always returns the IPv4 header when reading from a raw socket. |
| { |
| SCOPED_TRACE("check packet sent with raw socket"); |
| ASSERT_NO_FATAL_FAILURE(check_packet()); |
| } |
| |
| // Validate the packet sent with the UDP socket. |
| { |
| // Update expected ports as the packet was sent from the peer. |
| udphdr* udp = reinterpret_cast<udphdr*>(raw_udp_buf); |
| udp->uh_dport = htons(udpfd_peer_port); |
| udp->uh_sport = htons(udpfd_bound_port); |
| SCOPED_TRACE("check packet sent with UDP socket"); |
| ASSERT_NO_FATAL_FAILURE(check_packet()); |
| } |
| |
| // Attempt to write the packet but with the IPv4 header included. |
| { |
| udphdr* udp = reinterpret_cast<udphdr*>(raw_udp_buf); |
| udp->uh_dport = htons(udpfd_bound_port); |
| udp->uh_sport = htons(udpfd_peer_port); |
| char raw_ip_buf[sizeof(iphdr) + sizeof(udphdr) + sizeof(payload)] = {}; |
| iphdr* ip = reinterpret_cast<iphdr*>(raw_ip_buf); |
| ip->version = IPVERSION; |
| ip->ihl = EXPECTED_IHL; |
| ip->ttl = 2; |
| ip->protocol = SOL_UDP; |
| ip->saddr = network_endian_loopback_addr; |
| ip->daddr = network_endian_loopback_addr; |
| memcpy(raw_ip_buf + sizeof(iphdr), raw_udp_buf, sizeof(raw_udp_buf)); |
| int yes = 1; |
| ASSERT_EQ(setsockopt(rawfd.get(), SOL_IP, IP_HDRINCL, (void*)&yes, sizeof(yes)), 0) |
| << strerror(errno); |
| ASSERT_EQ(sendto(rawfd.get(), raw_ip_buf, sizeof(raw_ip_buf), 0, |
| reinterpret_cast<const struct sockaddr*>(&addr), sizeof(addr)), |
| ssize_t(sizeof(raw_ip_buf))) |
| << strerror(errno); |
| } |
| { |
| SCOPED_TRACE("check UDP payload sent with raw socket with IP header included"); |
| ASSERT_NO_FATAL_FAILURE(check_payload()); |
| } |
| |
| EXPECT_EQ(close(rawfd.release()), 0) << strerror(errno); |
| EXPECT_EQ(close(udpfd.release()), 0) << strerror(errno); |
| } |
| |
| // Fixture for tests parameterized by family and protocol. |
| class RawSocketTest : public testing::TestWithParam<std::tuple<int, int>> { |
| protected: |
| // Creates a socket to be used in tests. |
| void SetUp() override { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| const auto& [family, protocol] = GetParam(); |
| |
| ASSERT_TRUE(fd_ = fbl::unique_fd(socket(family, SOCK_RAW, protocol))) << strerror(errno); |
| } |
| |
| const fbl::unique_fd& fd() const { return fd_; } |
| |
| private: |
| fbl::unique_fd fd_; |
| }; |
| |
| TEST_P(RawSocketTest, SockOptSoProtocol) { |
| const auto& [family, protocol] = GetParam(); |
| |
| int opt; |
| socklen_t optlen = sizeof(opt); |
| ASSERT_EQ(getsockopt(fd().get(), SOL_SOCKET, SO_PROTOCOL, &opt, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(opt)); |
| EXPECT_EQ(opt, protocol); |
| } |
| |
| TEST_P(RawSocketTest, SockOptSoType) { |
| int opt; |
| socklen_t optlen = sizeof(opt); |
| ASSERT_EQ(getsockopt(fd().get(), SOL_SOCKET, SO_TYPE, &opt, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(opt)); |
| EXPECT_EQ(opt, SOCK_RAW); |
| } |
| |
| TEST_P(RawSocketTest, SockOptIPHdrIncl) { |
| const auto& [family, protocol] = GetParam(); |
| |
| int opt; |
| socklen_t optlen = sizeof(opt); |
| // TODO(https://fxbug.deb/90810): Don't support IP_HDRINCL on raw IPv6 sockets. |
| if (kIsFuchsia || family == AF_INET) { |
| ASSERT_EQ(getsockopt(fd().get(), SOL_IP, IP_HDRINCL, &opt, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(opt)); |
| EXPECT_EQ(opt, protocol == IPPROTO_RAW); |
| |
| constexpr int yes = 1; |
| ASSERT_EQ(setsockopt(fd().get(), SOL_IP, IP_HDRINCL, &yes, sizeof(yes)), 0) << strerror(errno); |
| ASSERT_EQ(getsockopt(fd().get(), SOL_IP, IP_HDRINCL, &opt, &optlen), 0) << strerror(errno); |
| ASSERT_EQ(optlen, sizeof(opt)); |
| EXPECT_EQ(opt, yes); |
| } else { |
| ASSERT_EQ(getsockopt(fd().get(), SOL_IP, IP_HDRINCL, &opt, &optlen), -1); |
| EXPECT_EQ(errno, ENOPROTOOPT) << strerror(errno); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(AllRawSocketTests, RawSocketTest, |
| testing::Combine(testing::Values(AF_INET, AF_INET6), |
| testing::Values(IPPROTO_TCP, IPPROTO_UDP, IPPROTO_RAW))); |
| |
| TEST(RawSocketICMPv6Test, GetSetFilterSucceeds) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6))) << strerror(errno); |
| |
| icmp6_filter set_filter; |
| ICMP6_FILTER_SETBLOCKALL(&set_filter); |
| ICMP6_FILTER_SETPASS(std::numeric_limits<uint8_t>::min(), &set_filter); |
| ICMP6_FILTER_SETPASS(std::numeric_limits<uint8_t>::max() / 2, &set_filter); |
| ICMP6_FILTER_SETPASS(std::numeric_limits<uint8_t>::max(), &set_filter); |
| ASSERT_EQ(setsockopt(fd.get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter, sizeof(set_filter)), 0) |
| << strerror(errno); |
| |
| { |
| icmp6_filter got_filter; |
| socklen_t got_filter_len = sizeof(got_filter); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter, &got_filter_len), 0) |
| << strerror(errno); |
| EXPECT_EQ(memcmp(&got_filter, &set_filter, sizeof(set_filter)), 0); |
| } |
| |
| // We use a length smaller than a full filter length and expect that only the |
| // bytes up to the provided length are modified. The last element should be |
| // unmodified when getsockopt returns. |
| { |
| icmp6_filter got_filter = {}; |
| constexpr socklen_t kShortFilterLen = sizeof(got_filter) - sizeof(got_filter.icmp6_filt[0]); |
| socklen_t got_filter_len = kShortFilterLen; |
| ASSERT_EQ(getsockopt(fd.get(), SOL_ICMPV6, ICMP6_FILTER, &got_filter, &got_filter_len), 0) |
| << strerror(errno); |
| ASSERT_EQ(got_filter_len, kShortFilterLen); |
| icmp6_filter expected_filter = set_filter; |
| expected_filter.icmp6_filt[std::size(expected_filter.icmp6_filt) - 1] = 0; |
| EXPECT_EQ(memcmp(&got_filter, &expected_filter, sizeof(expected_filter)), 0); |
| } |
| } |
| |
| TEST(RawSocketICMPv6Test, FilterICMPPackets) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6))) << strerror(errno); |
| |
| constexpr sockaddr_in6 kLoopbackAddr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| |
| // Allow Echo Request (128); disallow everything else, e.g. Echo Reply (129). |
| // NB: Netstack3 doesn't allow sending ICMP messages with invalid message |
| // types; so stick to known valid message types. |
| constexpr uint8_t kAllowedType = 128; |
| constexpr uint8_t kDisallowedType = 129; |
| |
| // Pass only the allowed type. |
| { |
| icmp6_filter set_filter; |
| ICMP6_FILTER_SETBLOCKALL(&set_filter); |
| ICMP6_FILTER_SETPASS(kAllowedType, &set_filter); |
| ASSERT_EQ(setsockopt(fd.get(), SOL_ICMPV6, ICMP6_FILTER, &set_filter, sizeof(set_filter)), 0) |
| << strerror(errno); |
| } |
| |
| // Send an allowed ICMP message, and a disallowed ICMP message. |
| std::array<uint8_t, 2> types = {kAllowedType, kDisallowedType}; |
| constexpr uint8_t kUnusedICMPCode = 0; |
| for (uint8_t type : types) { |
| const icmp6_hdr packet = { |
| .icmp6_type = type, |
| .icmp6_code = kUnusedICMPCode, |
| // The stack will calculate the checksum. |
| .icmp6_cksum = 0, |
| }; |
| |
| ssize_t n = sendto(fd.get(), &packet, sizeof(packet), 0, |
| reinterpret_cast<const sockaddr*>(&kLoopbackAddr), sizeof(kLoopbackAddr)); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, static_cast<ssize_t>(sizeof(packet))); |
| } |
| |
| // Make sure only the allowed type was received. |
| { |
| icmp6_hdr got_packet; |
| sockaddr_in6 sender; |
| socklen_t sender_len = sizeof(sender); |
| ssize_t n = recvfrom(fd.get(), &got_packet, sizeof(got_packet), 0 /* flags */, |
| reinterpret_cast<sockaddr*>(&sender), &sender_len); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, static_cast<ssize_t>(sizeof(got_packet))); |
| ASSERT_EQ(sender_len, sizeof(sender)); |
| EXPECT_EQ(memcmp(&sender, &kLoopbackAddr, sizeof(kLoopbackAddr)), 0); |
| EXPECT_EQ(got_packet.icmp6_type, kAllowedType); |
| EXPECT_EQ(got_packet.icmp6_code, kUnusedICMPCode); |
| EXPECT_NE(got_packet.icmp6_cksum, 0); |
| } |
| |
| // Make sure no more packets delivered to the raw socket. |
| { |
| icmp6_hdr got_packet; |
| sockaddr_in6 sender; |
| socklen_t sender_len = sizeof(sender); |
| ASSERT_EQ(recvfrom(fd.get(), &got_packet, sizeof(got_packet), MSG_DONTWAIT, |
| reinterpret_cast<sockaddr*>(&sender), &sender_len), |
| -1); |
| EXPECT_EQ(errno, EAGAIN); |
| } |
| } |
| |
| TEST(RawSocketICMPv6Test, NegativeIPv6ChecksumsFoldToNegativeOne) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| auto check = [&](int v) { |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &v, sizeof(v)), 0) << strerror(errno); |
| |
| int got; |
| socklen_t got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len), 0) << strerror(errno); |
| ASSERT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, -1); |
| }; |
| |
| ASSERT_NO_FATAL_FAILURE(check(-1)); |
| ASSERT_NO_FATAL_FAILURE(check(-2)); |
| ASSERT_NO_FATAL_FAILURE(check(std::numeric_limits<int>::min())); |
| } |
| |
| TEST(RawSocketICMPv6Test, SetIPv6ChecksumErrorForOddValues) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| int intV = 3; |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| |
| intV = 5; |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)), -1); |
| EXPECT_EQ(errno, EINVAL) << strerror(errno); |
| } |
| |
| TEST(RawSocketICMPv6Test, SetIPv6ChecksumSuccessForEvenValues) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd fd; |
| ASSERT_TRUE(fd = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) << strerror(errno); |
| |
| int intV = 2; |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)), 0) |
| << strerror(errno); |
| |
| intV = 4; |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &intV, sizeof(intV)), 0) |
| << strerror(errno); |
| } |
| |
| TEST(RawSocketICMPv6Test, IPv6Checksum_ValidateAndCalculate) { |
| SKIP_IF_CANT_ACCESS_RAW_SOCKETS(); |
| |
| fbl::unique_fd checksum_set; |
| ASSERT_TRUE(checksum_set = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) |
| << strerror(errno); |
| |
| fbl::unique_fd checksum_not_set; |
| ASSERT_TRUE(checksum_not_set = fbl::unique_fd(socket(AF_INET6, SOCK_RAW, IPPROTO_UDP))) |
| << strerror(errno); |
| |
| const sockaddr_in6 addr = { |
| .sin6_family = AF_INET6, |
| .sin6_addr = IN6ADDR_LOOPBACK_INIT, |
| }; |
| |
| auto bind_and_set_checksum = [&](const fbl::unique_fd& fd, int v) { |
| ASSERT_EQ(bind(fd.get(), reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)), 0) |
| << strerror(errno); |
| |
| int got; |
| socklen_t got_len = sizeof(got); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len), 0) << strerror(errno); |
| ASSERT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, -1); |
| |
| ASSERT_EQ(setsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &v, sizeof(v)), 0) << strerror(errno); |
| ASSERT_EQ(getsockopt(fd.get(), SOL_IPV6, IPV6_CHECKSUM, &got, &got_len), 0) << strerror(errno); |
| ASSERT_EQ(got_len, sizeof(got)); |
| EXPECT_EQ(got, v); |
| }; |
| |
| struct udp_packet { |
| udphdr udp; |
| uint32_t value; |
| } __PACKED; |
| |
| ASSERT_NO_FATAL_FAILURE( |
| bind_and_set_checksum(checksum_set, offsetof(udp_packet, udp) + offsetof(udphdr, uh_sum))); |
| ASSERT_NO_FATAL_FAILURE(bind_and_set_checksum(checksum_not_set, -1)); |
| |
| auto send = [&](const fbl::unique_fd& fd, uint32_t v) { |
| const udp_packet packet = { |
| .value = v, |
| }; |
| |
| ssize_t n = sendto(fd.get(), &packet, sizeof(packet), /*flags=*/0, |
| reinterpret_cast<const sockaddr*>(&addr), sizeof(addr)); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, static_cast<ssize_t>(sizeof(packet))); |
| }; |
| |
| auto expect_receive = [&](const fbl::unique_fd& fd, uint32_t v, bool should_check_xsum) { |
| udp_packet packet; |
| sockaddr_in6 sender; |
| socklen_t sender_len = sizeof(sender); |
| ssize_t n = recvfrom(fd.get(), &packet, sizeof(packet), /*flags=*/0, |
| reinterpret_cast<sockaddr*>(&sender), &sender_len); |
| ASSERT_GE(n, 0) << strerror(errno); |
| ASSERT_EQ(n, static_cast<ssize_t>(sizeof(packet))); |
| ASSERT_EQ(sender_len, sizeof(sender)); |
| EXPECT_EQ(memcmp(&sender, &addr, sizeof(addr)), 0); |
| EXPECT_EQ(packet.value, v); |
| if (should_check_xsum) { |
| EXPECT_NE(packet.udp.uh_sum, 0); |
| } else { |
| EXPECT_EQ(packet.udp.uh_sum, 0); |
| } |
| }; |
| |
| uint32_t counter = 1; |
| // Packets sent through checksum_not_set will not have a valid checksum set so |
| // checksum_set should not accept those packets. |
| ASSERT_NO_FATAL_FAILURE(send(checksum_not_set, counter)); |
| ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_not_set, counter, false)); |
| |
| // Packets sent through checksum_set will have a valid checksum so both |
| // sockets should accept them. |
| ASSERT_NO_FATAL_FAILURE(send(checksum_set, ++counter)); |
| ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_set, counter, true)); |
| ASSERT_NO_FATAL_FAILURE(expect_receive(checksum_not_set, counter, true)); |
| } |
| |
| } // namespace |