From 3458b185148afbabd2e49e3c5be576ee7f5355d5 Mon Sep 17 00:00:00 2001 From: Jeff Martin Date: Tue, 8 Aug 2023 06:41:00 -0700 Subject: [PATCH] Add test coverage for RTM_GETRULE, RTM_ADDRULE, and RTM_DELRULE PiperOrigin-RevId: 554806910 --- test/syscalls/linux/socket_netlink_route.cc | 119 +++++++++++++++++- .../linux/socket_netlink_route_util.cc | 81 ++++++++++++ .../linux/socket_netlink_route_util.h | 11 ++ 3 files changed, 209 insertions(+), 2 deletions(-) diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc index 2239e714b..f959b07c8 100644 --- a/test/syscalls/linux/socket_netlink_route.cc +++ b/test/syscalls/linux/socket_netlink_route.cc @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -23,6 +24,7 @@ #include #include +#include #include #include #include @@ -831,6 +833,10 @@ TEST(NetlinkRouteTest, GetRouteRequest) { // NetlinkRouteTest with a single parameter that must be AF_INET or AF_INET6. using NetlinkRouteIpInvariantTest = ::testing::TestWithParam; +INSTANTIATE_TEST_SUITE_P(NetlinkRouteIpv4AndIpv6Tests, + NetlinkRouteIpInvariantTest, + ::testing::Values(AF_INET, AF_INET6)); + TEST_P(NetlinkRouteIpInvariantTest, AddAndRemoveRoute) { // Gvisor does not support `RTM_NEWROUTE` or `RTM_DELROUTE`. SKIP_IF(IsRunningOnGvisor()); @@ -882,8 +888,117 @@ TEST_P(NetlinkRouteIpInvariantTest, AddAndRemoveRoute) { PosixErrorIs(ESRCH, _)); } -INSTANTIATE_TEST_SUITE_P(AddAndRemoveRoute, NetlinkRouteIpInvariantTest, - ::testing::Values(AF_INET, AF_INET6)); +// GetRuleDump tests a RTM_GETRULE + NLM_F_DUMP request. +TEST(NetlinkRouteTest, GetRuleDump) { + // Gvisor does not support `RTM_GETRULE` + SKIP_IF(IsRunningOnGvisor()); + + 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_GETRULE; + req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP; + req.hdr.nlmsg_seq = kSeq; + req.rtm.rtm_family = AF_UNSPEC; + + bool ruleFound = false; + ASSERT_NO_ERRNO(NetlinkRequestResponse( + fd, &req, sizeof(req), + [&](const struct nlmsghdr* hdr) { + // Validate the response to RTM_GETRULE + NLM_F_DUMP. + EXPECT_THAT(hdr->nlmsg_type, AnyOf(Eq(RTM_NEWRULE), 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 the multipart message is done. + if (hdr->nlmsg_type == NLMSG_DONE) { + return; + } + + // RTM_NEWRULE contains at least the header and rule. + ASSERT_GE(hdr->nlmsg_len, NLMSG_SPACE(sizeof(struct fib_rule_hdr))); + const struct fib_rule_hdr* rule = + reinterpret_cast(NLMSG_DATA(hdr)); + std::cout << std::dec << "Found rule" + << ": family=" << static_cast(rule->family) + << ", table=" << static_cast(rule->table) + << ", action=" << static_cast(rule->action) << std::endl; + // All rules should have a non-zero action. + EXPECT_NE(rule->action, 0); + ruleFound = true; + }, + false)); + // At least one rule found. + EXPECT_TRUE(ruleFound); +} + +TEST_P(NetlinkRouteIpInvariantTest, AddAndRemoveRule) { + // Gvisor does not support `RTM_NEWRULE` or `RTM_DELRULE`. + SKIP_IF(IsRunningOnGvisor()); + // CAP_NET_ADMIN is required to modify the rule table. + SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN))); + + // Based on the test parameter, build an IPv4 or IPv6 destination subnet. + int family = GetParam(); + struct in_addr dst_v4; + struct in6_addr dst_v6; + void* dst = nullptr; + int dst_len; + int prefixlen; + switch (family) { + case AF_INET: + ASSERT_EQ(inet_pton(family, "192.0.2.0", &dst_v4), 1); + prefixlen = 24; + dst = &dst_v4; + dst_len = sizeof(dst_v4); + break; + case AF_INET6: + ASSERT_EQ(inet_pton(family, "2001:db8::", &dst_v6), 1); + prefixlen = 64; + dst = &dst_v6; + dst_len = sizeof(dst_v6); + break; + default: + FAIL() << "address family must be AF_INET or AF_INET6"; + } + + // Unique values for table and priority fields to ensure the new rule does not + // collide with any of the default rules installed by Linux. + const int kTable = 99; + const int kPriority = 12345; + + Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink()); + + // Create should succeed, as no such rule in the kernel. + ASSERT_NO_ERRNO(AddExclusiveLookupInTableRule(family, kTable, kPriority, + prefixlen, dst, dst_len)); + + // Second create should fail, as we already created the rule above. + EXPECT_THAT(AddExclusiveLookupInTableRule(family, kTable, kPriority, + prefixlen, dst, dst_len), + PosixErrorIs(EEXIST, _)); + + // First delete should succeed, as rule exists. + EXPECT_NO_ERRNO( + DelLookupInTableRule(family, kTable, kPriority, prefixlen, dst, dst_len)); + + // Second delete should fail, as rule no longer exists. + EXPECT_THAT( + DelLookupInTableRule(family, kTable, kPriority, prefixlen, dst, dst_len), + PosixErrorIs(ENOENT, _)); +} // RecvmsgTrunc tests the recvmsg MSG_TRUNC flag with zero length output // buffer. MSG_TRUNC with a zero length buffer should consume subsequent diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc index 11f0c27c2..6c1dc7cbc 100644 --- a/test/syscalls/linux/socket_netlink_route_util.cc +++ b/test/syscalls/linux/socket_netlink_route_util.cc @@ -14,6 +14,7 @@ #include "test/syscalls/linux/socket_netlink_route_util.h" +#include #include #include #include @@ -91,6 +92,31 @@ PosixError PopulateRouteNlmsghdr(NetlinkModification modification, return PosixError(EINVAL); } +// Populates |hdr| with appropriate values for the modification type. +PosixError PopulateRuleNlmsghdr(NetlinkModification modification, + struct nlmsghdr* hdr) { + switch (modification) { + case NetlinkModification::kAdd: + hdr->nlmsg_type = RTM_NEWRULE; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + return NoError(); + case NetlinkModification::kAddExclusive: + hdr->nlmsg_type = RTM_NEWRULE; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK; + return NoError(); + case NetlinkModification::kReplace: + hdr->nlmsg_type = RTM_NEWRULE; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK; + return NoError(); + case NetlinkModification::kDelete: + hdr->nlmsg_type = RTM_DELRULE; + hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK; + return NoError(); + } + + return PosixError(EINVAL); +} + // Adds or removes the specified address from the specified interface. PosixError LinkModifyLocalAddr(int index, int family, int prefixlen, const void* addr, int addrlen, @@ -173,6 +199,48 @@ PosixError ModifyUnicastRoute(int interface, int family, int prefixlen, return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); } +// Adds or removes the specified route. +PosixError ModifyLookupInTableRule(int family, int table, int priority, + int prefixlen, const void* dst, int dstlen, + NetlinkModification modification) { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, NetlinkBoundSocket(NETLINK_ROUTE)); + + struct request { + struct nlmsghdr hdr; + struct fib_rule_hdr rule; + char attrbuf[512]; + }; + + struct request req = {}; + PosixError err = PopulateRuleNlmsghdr(modification, &req.hdr); + if (!err.ok()) { + return err; + } + req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(req.rule)); + req.hdr.nlmsg_seq = kSeq; + req.rule.family = family; + req.rule.table = table; + req.rule.action = FR_ACT_TO_TBL; + req.rule.dst_len = prefixlen; + + struct rtattr* fra_priority = reinterpret_cast( + reinterpret_cast(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); + fra_priority->rta_type = FRA_PRIORITY; + fra_priority->rta_len = RTA_LENGTH(sizeof(priority)); + req.hdr.nlmsg_len = + NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(sizeof(priority)); + memcpy(RTA_DATA(fra_priority), &priority, sizeof(priority)); + + struct rtattr* fra_dst = reinterpret_cast( + reinterpret_cast(&req) + NLMSG_ALIGN(req.hdr.nlmsg_len)); + fra_dst->rta_type = FRA_DST; + fra_dst->rta_len = RTA_LENGTH(dstlen); + req.hdr.nlmsg_len = NLMSG_ALIGN(req.hdr.nlmsg_len) + RTA_LENGTH(dstlen); + memcpy(RTA_DATA(fra_dst), dst, dstlen); + + return NetlinkRequestAckOrError(fd, kSeq, &req, req.hdr.nlmsg_len); +} + } // namespace PosixError DumpLinks( @@ -312,5 +380,18 @@ PosixError DelUnicastRoute(int interface, int family, int prefixlen, NetlinkModification::kDelete); } +PosixError AddExclusiveLookupInTableRule(int family, int table, int priority, + int prefixlen, const void* dst, + int dstlen) { + return ModifyLookupInTableRule(family, table, priority, prefixlen, dst, + dstlen, NetlinkModification::kAddExclusive); +} + +PosixError DelLookupInTableRule(int family, int table, int priority, + int prefixlen, const void* dst, int dstlen) { + return ModifyLookupInTableRule(family, table, priority, prefixlen, dst, + dstlen, NetlinkModification::kDelete); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h index 500da85bf..78ba6484b 100644 --- a/test/syscalls/linux/socket_netlink_route_util.h +++ b/test/syscalls/linux/socket_netlink_route_util.h @@ -70,6 +70,17 @@ PosixError AddUnicastRoute(int interface, int family, int prefixlen, PosixError DelUnicastRoute(int interface, int family, int prefixlen, const void* dst, int dstlen); +// AddExclusiveLookupInTableRule adds a PBR rule that performs a route lookup +// against the given table, for all packets destined to the given subnet. +PosixError AddExclusiveLookupInTableRule(int family, int table, int priority, + int prefixlen, const void* dst, + int dstlen); + +// DelLookupInTableRule deletes a PBR rule that performs a route lookup against +// given table, for all packets destined to the given subnet. +PosixError DelLookupInTableRule(int family, int table, int priority, + int prefixlen, const void* dst, int dstlen); + } // namespace testing } // namespace gvisor