mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Add test coverage for RTM_GETRULE, RTM_ADDRULE, and RTM_DELRULE
PiperOrigin-RevId: 554806910
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <ifaddrs.h>
|
||||
#include <linux/fib_rules.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
@@ -23,6 +24,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@@ -831,6 +833,10 @@ TEST(NetlinkRouteTest, GetRouteRequest) {
|
||||
// NetlinkRouteTest with a single parameter that must be AF_INET or AF_INET6.
|
||||
using NetlinkRouteIpInvariantTest = ::testing::TestWithParam<int>;
|
||||
|
||||
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<const struct fib_rule_hdr*>(NLMSG_DATA(hdr));
|
||||
std::cout << std::dec << "Found rule"
|
||||
<< ": family=" << static_cast<int>(rule->family)
|
||||
<< ", table=" << static_cast<int>(rule->table)
|
||||
<< ", action=" << static_cast<int>(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
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "test/syscalls/linux/socket_netlink_route_util.h"
|
||||
|
||||
#include <linux/fib_rules.h>
|
||||
#include <linux/if.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
@@ -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<struct rtattr*>(
|
||||
reinterpret_cast<int8_t*>(&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<struct rtattr*>(
|
||||
reinterpret_cast<int8_t*>(&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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user