diff options
Diffstat (limited to 'test/syscalls/linux/socket_netlink_route.cc')
-rw-r--r-- | test/syscalls/linux/socket_netlink_route.cc | 990 |
1 files changed, 0 insertions, 990 deletions
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc deleted file mode 100644 index e5aed1eec..000000000 --- a/test/syscalls/linux/socket_netlink_route.cc +++ /dev/null @@ -1,990 +0,0 @@ -// Copyright 2018 The gVisor Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include <arpa/inet.h> -#include <ifaddrs.h> -#include <linux/if.h> -#include <linux/netlink.h> -#include <linux/rtnetlink.h> -#include <sys/socket.h> -#include <sys/types.h> -#include <unistd.h> - -#include <iostream> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/strings/str_format.h" -#include "absl/types/optional.h" -#include "test/syscalls/linux/socket_netlink_util.h" -#include "test/syscalls/linux/socket_test_util.h" -#include "test/util/capability_util.h" -#include "test/util/cleanup.h" -#include "test/util/file_descriptor.h" -#include "test/util/test_util.h" - -// Tests for NETLINK_ROUTE sockets. - -namespace gvisor { -namespace testing { - -namespace { - -constexpr uint32_t kSeq = 12345; - -using ::testing::AnyOf; -using ::testing::Eq; - -// Parameters for SockOptTest. They are: -// 0: Socket option to query. -// 1: A predicate to run on the returned sockopt value. Should return true if -// the value is considered ok. -// 2: A description of what the sockopt value is expected to be. Should complete -// the sentence "<value> was unexpected, expected <description>" -using SockOptTest = ::testing::TestWithParam< - std::tuple<int, std::function<bool(int)>, std::string>>; - -TEST_P(SockOptTest, GetSockOpt) { - int sockopt = std::get<0>(GetParam()); - auto verifier = std::get<1>(GetParam()); - std::string verifier_description = std::get<2>(GetParam()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)); - - int res; - socklen_t len = sizeof(res); - - EXPECT_THAT(getsockopt(fd.get(), SOL_SOCKET, sockopt, &res, &len), - SyscallSucceeds()); - - EXPECT_EQ(len, sizeof(res)); - EXPECT_TRUE(verifier(res)) << absl::StrFormat( - "getsockopt(%d, SOL_SOCKET, %d, &res, &len) => res=%d was unexpected, " - "expected %s", - fd.get(), sockopt, res, verifier_description); -} - -std::function<bool(int)> IsPositive() { - return [](int val) { return val > 0; }; -} - -std::function<bool(int)> IsEqual(int target) { - return [target](int val) { return val == target; }; -} - -INSTANTIATE_TEST_SUITE_P( - NetlinkRouteTest, SockOptTest, - ::testing::Values( - std::make_tuple(SO_SNDBUF, IsPositive(), "positive send buffer size"), - std::make_tuple(SO_RCVBUF, IsPositive(), - "positive receive buffer size"), - std::make_tuple(SO_TYPE, IsEqual(SOCK_RAW), - absl::StrFormat("SOCK_RAW (%d)", SOCK_RAW)), - std::make_tuple(SO_DOMAIN, IsEqual(AF_NETLINK), - absl::StrFormat("AF_NETLINK (%d)", AF_NETLINK)), - std::make_tuple(SO_PROTOCOL, IsEqual(NETLINK_ROUTE), - absl::StrFormat("NETLINK_ROUTE (%d)", NETLINK_ROUTE)), - std::make_tuple(SO_PASSCRED, IsEqual(0), "0"))); - -// Validates the reponses to RTM_GETLINK + NLM_F_DUMP. -void CheckGetLinkResponse(const struct nlmsghdr* hdr, int seq, int port) { - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWLINK), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, seq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - if (hdr->nlmsg_type != RTM_NEWLINK) { - return; - } - - // RTM_NEWLINK contains at least the header and ifinfomsg. - EXPECT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - - // TODO(mpratt): Check ifinfomsg contents and following attrs. -} - -PosixError DumpLinks( - const FileDescriptor& fd, uint32_t seq, - const std::function<void(const struct nlmsghdr* hdr)>& fn) { - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = seq; - req.ifm.ifi_family = AF_UNSPEC; - - return NetlinkRequestResponse(fd, &req, sizeof(req), fn, false); -} - -TEST(NetlinkRouteTest, GetLinkDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - // Loopback is common among all tests, check that it's found. - bool loopbackFound = false; - ASSERT_NO_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) { - CheckGetLinkResponse(hdr, kSeq, port); - if (hdr->nlmsg_type != RTM_NEWLINK) { - return; - } - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - std::cout << "Found interface idx=" << msg->ifi_index - << ", type=" << std::hex << msg->ifi_type; - if (msg->ifi_type == ARPHRD_LOOPBACK) { - loopbackFound = true; - EXPECT_NE(msg->ifi_flags & IFF_LOOPBACK, 0); - } - })); - EXPECT_TRUE(loopbackFound); -} - -struct Link { - int index; - std::string name; -}; - -PosixErrorOr<absl::optional<Link>> FindLoopbackLink() { - ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); - - absl::optional<Link> link; - RETURN_IF_ERRNO(DumpLinks(fd, kSeq, [&](const struct nlmsghdr* hdr) { - if (hdr->nlmsg_type != RTM_NEWLINK || - hdr->nlmsg_len < NLMSG_SPACE(sizeof(struct ifinfomsg))) { - return; - } - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - if (msg->ifi_type == ARPHRD_LOOPBACK) { - const auto* rta = FindRtAttr(hdr, msg, IFLA_IFNAME); - if (rta == nullptr) { - // Ignore links that do not have a name. - return; - } - - link = Link(); - link->index = msg->ifi_index; - link->name = std::string(reinterpret_cast<const char*>(RTA_DATA(rta))); - } - })); - return link; -} - -// CheckLinkMsg checks a netlink message against an expected link. -void CheckLinkMsg(const struct nlmsghdr* hdr, const Link& link) { - ASSERT_THAT(hdr->nlmsg_type, Eq(RTM_NEWLINK)); - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct ifinfomsg))); - const struct ifinfomsg* msg = - reinterpret_cast<const struct ifinfomsg*>(NLMSG_DATA(hdr)); - EXPECT_EQ(msg->ifi_index, link.index); - - const struct rtattr* rta = FindRtAttr(hdr, msg, IFLA_IFNAME); - EXPECT_NE(nullptr, rta) << "IFLA_IFNAME not found in message."; - if (rta != nullptr) { - std::string name(reinterpret_cast<const char*>(RTA_DATA(rta))); - EXPECT_EQ(name, link.name); - } -} - -TEST(NetlinkRouteTest, GetLinkByIndex) { - absl::optional<Link> loopback_link = - ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink()); - ASSERT_TRUE(loopback_link.has_value()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.ifm.ifi_index = loopback_link->index; - - bool found = false; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckLinkMsg(hdr, *loopback_link); - found = true; - }, - false)); - EXPECT_TRUE(found) << "Netlink response does not contain any links."; -} - -TEST(NetlinkRouteTest, GetLinkByName) { - absl::optional<Link> loopback_link = - ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink()); - ASSERT_TRUE(loopback_link.has_value()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - struct rtattr rtattr; - char ifname[IFNAMSIZ]; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.rtattr.rta_type = IFLA_IFNAME; - req.rtattr.rta_len = RTA_LENGTH(loopback_link->name.size() + 1); - strncpy(req.ifname, loopback_link->name.c_str(), sizeof(req.ifname)); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); - - bool found = false; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckLinkMsg(hdr, *loopback_link); - found = true; - }, - false)); - EXPECT_TRUE(found) << "Netlink response does not contain any links."; -} - -TEST(NetlinkRouteTest, GetLinkByIndexNotFound) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.ifm.ifi_index = 1234590; - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(ENODEV, ::testing::_)); -} - -TEST(NetlinkRouteTest, GetLinkByNameNotFound) { - const std::string name = "nodevice?!"; - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - struct rtattr rtattr; - char ifname[IFNAMSIZ]; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - req.rtattr.rta_type = IFLA_IFNAME; - req.rtattr.rta_len = RTA_LENGTH(name.size() + 1); - strncpy(req.ifname, name.c_str(), sizeof(req.ifname)); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifm)) + NLMSG_ALIGN(req.rtattr.rta_len); - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(ENODEV, ::testing::_)); -} - -TEST(NetlinkRouteTest, MsgHdrMsgUnsuppType) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - // If type & 0x3 is equal to 0x2, this means a get request - // which doesn't require CAP_SYS_ADMIN. - req.hdr.nlmsg_type = ((__RTM_MAX + 1024) & (~0x3)) | 0x2; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - EXPECT_THAT(NetlinkRequestAckOrError(fd, kSeq, &req, sizeof(req)), - PosixErrorIs(EOPNOTSUPP, ::testing::_)); -} - -TEST(NetlinkRouteTest, MsgHdrMsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // Small enough to ensure that the response doesn't fit. - constexpr size_t kBufferSize = 10; - std::vector<char> buf(kBufferSize); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), - SyscallSucceedsWithValue(kBufferSize)); - EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); -} - -TEST(NetlinkRouteTest, MsgTruncMsgHdrMsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETLINK; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.ifm.ifi_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - // No destination required; it defaults to pid 0, the kernel. - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // Small enough to ensure that the response doesn't fit. - constexpr size_t kBufferSize = 10; - std::vector<char> buf(kBufferSize); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - - int res = 0; - ASSERT_THAT(res = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - EXPECT_GT(res, kBufferSize); - EXPECT_EQ((msg.msg_flags & MSG_TRUNC), MSG_TRUNC); -} - -TEST(NetlinkRouteTest, ControlMessageIgnored) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr control_hdr; - struct nlmsghdr message_hdr; - struct ifinfomsg ifm; - }; - - struct request req = {}; - - // This control message is ignored. We still receive a response for the - // following RTM_GETLINK. - req.control_hdr.nlmsg_len = sizeof(req.control_hdr); - req.control_hdr.nlmsg_type = NLMSG_DONE; - req.control_hdr.nlmsg_seq = kSeq; - - req.message_hdr.nlmsg_len = sizeof(req.message_hdr) + sizeof(req.ifm); - req.message_hdr.nlmsg_type = RTM_GETLINK; - req.message_hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.message_hdr.nlmsg_seq = kSeq; - - req.ifm.ifi_family = AF_UNSPEC; - - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - CheckGetLinkResponse(hdr, kSeq, port); - }, - false)); -} - -TEST(NetlinkRouteTest, GetAddrDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWADDR), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - if (hdr->nlmsg_type != RTM_NEWADDR) { - return; - } - - // RTM_NEWADDR contains at least the header and ifaddrmsg. - EXPECT_GE(hdr->nlmsg_len, sizeof(*hdr) + sizeof(struct ifaddrmsg)); - - // TODO(mpratt): Check ifaddrmsg contents and following attrs. - }, - false)); -} - -TEST(NetlinkRouteTest, LookupAll) { - struct ifaddrs* if_addr_list = nullptr; - auto cleanup = Cleanup([&if_addr_list]() { freeifaddrs(if_addr_list); }); - - // Not a syscall but we can use the syscall matcher as glibc sets errno. - ASSERT_THAT(getifaddrs(&if_addr_list), SyscallSucceeds()); - - int count = 0; - for (struct ifaddrs* i = if_addr_list; i; i = i->ifa_next) { - if (!i->ifa_addr || (i->ifa_addr->sa_family != AF_INET && - i->ifa_addr->sa_family != AF_INET6)) { - continue; - } - count++; - } - ASSERT_GT(count, 0); -} - -TEST(NetlinkRouteTest, AddAddr) { - SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); - - absl::optional<Link> loopback_link = - ASSERT_NO_ERRNO_AND_VALUE(FindLoopbackLink()); - ASSERT_TRUE(loopback_link.has_value()); - - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct ifaddrmsg ifa; - struct rtattr rtattr; - struct in_addr addr; - char pad[NLMSG_ALIGNTO + RTA_ALIGNTO]; - }; - - struct request req = {}; - req.hdr.nlmsg_type = RTM_NEWADDR; - req.hdr.nlmsg_seq = kSeq; - req.ifa.ifa_family = AF_INET; - req.ifa.ifa_prefixlen = 24; - req.ifa.ifa_flags = 0; - req.ifa.ifa_scope = 0; - req.ifa.ifa_index = loopback_link->index; - req.rtattr.rta_type = IFA_LOCAL; - req.rtattr.rta_len = RTA_LENGTH(sizeof(req.addr)); - inet_pton(AF_INET, "10.0.0.1", &req.addr); - req.hdr.nlmsg_len = - NLMSG_LENGTH(sizeof(req.ifa)) + NLMSG_ALIGN(req.rtattr.rta_len); - - // Create should succeed, as no such address in kernel. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK; - EXPECT_NO_ERRNO( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len)); - - // Replace an existing address should succeed. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; - req.hdr.nlmsg_seq++; - EXPECT_NO_ERRNO( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len)); - - // Create exclusive should fail, as we created the address above. - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK; - req.hdr.nlmsg_seq++; - EXPECT_THAT( - NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len), - PosixErrorIs(EEXIST, ::testing::_)); -} - -// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request. -TEST(NetlinkRouteTest, GetRouteDump) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct request { - struct nlmsghdr hdr; - struct rtmsg rtm; - }; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETROUTE; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rtm.rtm_family = AF_UNSPEC; - - bool routeFound = false; - bool dstFound = true; - ASSERT_NO_ERRNO(NetlinkRequestResponse( - fd, &req, sizeof(req), - [&](const struct nlmsghdr* hdr) { - // Validate the reponse to RTM_GETROUTE + NLM_F_DUMP. - EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWROUTE), Eq(NLMSG_DONE))); - - EXPECT_TRUE((hdr->nlmsg_flags & NLM_F_MULTI) == NLM_F_MULTI) - << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - // The test should not proceed if it's not a RTM_NEWROUTE message. - if (hdr->nlmsg_type != RTM_NEWROUTE) { - return; - } - - // RTM_NEWROUTE contains at least the header and rtmsg. - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg))); - const struct rtmsg* msg = - reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr)); - // NOTE: rtmsg fields are char fields. - std::cout << "Found route table=" << static_cast<int>(msg->rtm_table) - << ", protocol=" << static_cast<int>(msg->rtm_protocol) - << ", scope=" << static_cast<int>(msg->rtm_scope) - << ", type=" << static_cast<int>(msg->rtm_type); - - int len = RTM_PAYLOAD(hdr); - bool rtDstFound = false; - for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len); - attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_DST) { - char address[INET_ADDRSTRLEN] = {}; - inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address)); - std::cout << ", dst=" << address; - rtDstFound = true; - } - } - - std::cout << std::endl; - - if (msg->rtm_table == RT_TABLE_MAIN) { - routeFound = true; - dstFound = rtDstFound && dstFound; - } - }, - false)); - // At least one route found in main route table. - EXPECT_TRUE(routeFound); - // Found RTA_DST for each route in main table. - EXPECT_TRUE(dstFound); -} - -// GetRouteRequest tests a RTM_GETROUTE request with RTM_F_LOOKUP_TABLE flag. -TEST(NetlinkRouteTest, GetRouteRequest) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - uint32_t port = ASSERT_NO_ERRNO_AND_VALUE(NetlinkPortID(fd.get())); - - struct __attribute__((__packed__)) request { - struct nlmsghdr hdr; - struct rtmsg rtm; - struct nlattr nla; - struct in_addr sin_addr; - }; - - constexpr uint32_t kSeq = 12345; - - struct request req = {}; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETROUTE; - req.hdr.nlmsg_flags = NLM_F_REQUEST; - req.hdr.nlmsg_seq = kSeq; - - req.rtm.rtm_family = AF_INET; - req.rtm.rtm_dst_len = 32; - req.rtm.rtm_src_len = 0; - req.rtm.rtm_tos = 0; - req.rtm.rtm_table = RT_TABLE_UNSPEC; - req.rtm.rtm_protocol = RTPROT_UNSPEC; - req.rtm.rtm_scope = RT_SCOPE_UNIVERSE; - req.rtm.rtm_type = RTN_UNSPEC; - req.rtm.rtm_flags = RTM_F_LOOKUP_TABLE; - - req.nla.nla_len = 8; - req.nla.nla_type = RTA_DST; - inet_aton("127.0.0.2", &req.sin_addr); - - bool rtDstFound = false; - ASSERT_NO_ERRNO(NetlinkRequestResponseSingle( - fd, &req, sizeof(req), [&](const struct nlmsghdr* hdr) { - // Validate the reponse to RTM_GETROUTE request with RTM_F_LOOKUP_TABLE - // flag. - EXPECT_THAT(hdr->nlmsg_type, RTM_NEWROUTE); - - EXPECT_TRUE(hdr->nlmsg_flags == 0) << std::hex << hdr->nlmsg_flags; - - EXPECT_EQ(hdr->nlmsg_seq, kSeq); - EXPECT_EQ(hdr->nlmsg_pid, port); - - // RTM_NEWROUTE contains at least the header and rtmsg. - ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct rtmsg))); - const struct rtmsg* msg = - reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(hdr)); - - // NOTE: rtmsg fields are char fields. - std::cout << "Found route table=" << static_cast<int>(msg->rtm_table) - << ", protocol=" << static_cast<int>(msg->rtm_protocol) - << ", scope=" << static_cast<int>(msg->rtm_scope) - << ", type=" << static_cast<int>(msg->rtm_type); - - EXPECT_EQ(msg->rtm_family, AF_INET); - EXPECT_EQ(msg->rtm_dst_len, 32); - EXPECT_TRUE((msg->rtm_flags & RTM_F_CLONED) == RTM_F_CLONED) - << std::hex << msg->rtm_flags; - - int len = RTM_PAYLOAD(hdr); - std::cout << ", len=" << len; - for (struct rtattr* attr = RTM_RTA(msg); RTA_OK(attr, len); - attr = RTA_NEXT(attr, len)) { - if (attr->rta_type == RTA_DST) { - char address[INET_ADDRSTRLEN] = {}; - inet_ntop(AF_INET, RTA_DATA(attr), address, sizeof(address)); - std::cout << ", dst=" << address; - rtDstFound = true; - } else if (attr->rta_type == RTA_OIF) { - const char* oif = reinterpret_cast<const char*>(RTA_DATA(attr)); - std::cout << ", oif=" << oif; - } - } - - std::cout << std::endl; - })); - // Found RTA_DST for RTM_F_LOOKUP_TABLE. - EXPECT_TRUE(rtDstFound); -} - -// RecvmsgTrunc tests the recvmsg MSG_TRUNC flag with zero length output -// buffer. MSG_TRUNC with a zero length buffer should consume subsequent -// messages off the socket. -TEST(NetlinkRouteTest, RecvmsgTrunc) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - int trunclen, trunclen2; - - // Note: This test assumes at least two messages are returned by the - // RTM_GETADDR request. That means at least one RTM_NEWLINK message and one - // NLMSG_DONE message. We cannot read all the messages without blocking - // because we would need to read the message into a buffer and check the - // nlmsg_type for NLMSG_DONE. However, the test depends on reading into a - // zero-length buffer. - - // First, call recvmsg with MSG_TRUNC. This will read the full message from - // the socket and return it's full length. Subsequent calls to recvmsg will - // read the next messages from the socket. - ASSERT_THAT(trunclen = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - - // Message should always be truncated. However, While the destination iov is - // zero length, MSG_TRUNC returns the size of the next message so it should - // not be zero. - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(trunclen, 0); - // Returned length is at least the header and ifaddrmsg. - EXPECT_GE(trunclen, sizeof(struct nlmsghdr) + sizeof(struct ifaddrmsg)); - - // Reset the msg_flags to make sure that the recvmsg call is setting them - // properly. - msg.msg_flags = 0; - - // Make a second recvvmsg call to get the next message. - ASSERT_THAT(trunclen2 = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_TRUNC), - SyscallSucceeds()); - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(trunclen2, 0); - - // Assert that the received messages are not the same. - // - // We are calling recvmsg with a zero length buffer so we have no way to - // inspect the messages to make sure they are not equal in value. The best - // we can do is to compare their lengths. - ASSERT_NE(trunclen, trunclen2); -} - -// RecvmsgTruncPeek tests recvmsg with the combination of the MSG_TRUNC and -// MSG_PEEK flags and a zero length output buffer. This is normally used to -// read the full length of the next message on the socket without consuming -// it, so a properly sized buffer can be allocated to store the message. This -// test tests that scenario. -TEST(NetlinkRouteTest, RecvmsgTruncPeek) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - int type = -1; - do { - int peeklen; - int len; - - iov.iov_base = NULL; - iov.iov_len = 0; - - // Call recvmsg with MSG_PEEK and MSG_TRUNC. This will peek at the message - // and return it's full length. - // See: MSG_TRUNC http://man7.org/linux/man-pages/man2/recv.2.html - ASSERT_THAT( - peeklen = RetryEINTR(recvmsg)(fd.get(), &msg, MSG_PEEK | MSG_TRUNC), - SyscallSucceeds()); - - // Message should always be truncated. - ASSERT_EQ(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - ASSERT_NE(peeklen, 0); - - // Reset the message flags for the next call. - msg.msg_flags = 0; - - // Make the actual call to recvmsg to get the actual data. We will use - // the length returned from the peek call for the allocated buffer size.. - std::vector<char> buf(peeklen); - iov.iov_base = buf.data(); - iov.iov_len = buf.size(); - ASSERT_THAT(len = RetryEINTR(recvmsg)(fd.get(), &msg, 0), - SyscallSucceeds()); - - // Message should not be truncated since we allocated the correct buffer - // size. - EXPECT_NE(msg.msg_flags & MSG_TRUNC, MSG_TRUNC); - - // MSG_PEEK should have left data on the socket and the subsequent call - // with should have retrieved the same data. Both calls should have - // returned the message's full length so they should be equal. - ASSERT_NE(len, 0); - ASSERT_EQ(peeklen, len); - - for (struct nlmsghdr* hdr = reinterpret_cast<struct nlmsghdr*>(buf.data()); - NLMSG_OK(hdr, len); hdr = NLMSG_NEXT(hdr, len)) { - type = hdr->nlmsg_type; - } - } while (type != NLMSG_DONE && type != NLMSG_ERROR); -} - -// No SCM_CREDENTIALS are received without SO_PASSCRED set. -TEST(NetlinkRouteTest, NoPasscredNoCreds) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOff, - sizeof(kSockOptOff)), - SyscallSucceeds()); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - char control[CMSG_SPACE(sizeof(struct ucred))] = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - // Note: This test assumes at least one message is returned by the - // RTM_GETADDR request. - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - // No control messages. - EXPECT_EQ(CMSG_FIRSTHDR(&msg), nullptr); -} - -// SCM_CREDENTIALS are received with SO_PASSCRED set. -TEST(NetlinkRouteTest, PasscredCreds) { - FileDescriptor fd = - ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE)); - - ASSERT_THAT(setsockopt(fd.get(), SOL_SOCKET, SO_PASSCRED, &kSockOptOn, - sizeof(kSockOptOn)), - SyscallSucceeds()); - - struct request { - struct nlmsghdr hdr; - struct rtgenmsg rgm; - }; - - struct request req; - req.hdr.nlmsg_len = sizeof(req); - req.hdr.nlmsg_type = RTM_GETADDR; - req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; - req.hdr.nlmsg_seq = kSeq; - req.rgm.rtgen_family = AF_UNSPEC; - - struct iovec iov = {}; - iov.iov_base = &req; - iov.iov_len = sizeof(req); - - struct msghdr msg = {}; - msg.msg_iov = &iov; - msg.msg_iovlen = 1; - - ASSERT_THAT(RetryEINTR(sendmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - iov.iov_base = NULL; - iov.iov_len = 0; - - char control[CMSG_SPACE(sizeof(struct ucred))] = {}; - msg.msg_control = control; - msg.msg_controllen = sizeof(control); - - // Note: This test assumes at least one message is returned by the - // RTM_GETADDR request. - ASSERT_THAT(RetryEINTR(recvmsg)(fd.get(), &msg, 0), SyscallSucceeds()); - - struct ucred creds; - struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); - ASSERT_NE(cmsg, nullptr); - ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(creds))); - ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET); - ASSERT_EQ(cmsg->cmsg_type, SCM_CREDENTIALS); - - memcpy(&creds, CMSG_DATA(cmsg), sizeof(creds)); - - // The peer is the kernel, which is "PID" 0. - EXPECT_EQ(creds.pid, 0); - // The kernel identifies as root. Also allow nobody in case this test is - // running in a userns without root mapped. - EXPECT_THAT(creds.uid, AnyOf(Eq(0), Eq(65534))); - EXPECT_THAT(creds.gid, AnyOf(Eq(0), Eq(65534))); -} - -} // namespace - -} // namespace testing -} // namespace gvisor |