blob: 25ad89bbcd447b2a5f70538172619823e786eab9 [file] [log] [blame]
// Copyright 2020 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/routing_service.h"
#include <sys/socket.h>
#include <algorithm>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <utility>
#include <base/files/scoped_file.h>
#include <base/strings/stringprintf.h>
#include <base/types/cxx23_to_underlying.h>
#include <gtest/gtest.h>
#include "patchpanel/mock_lifeline_fd_service.h"
#include "patchpanel/mock_system.h"
#include "patchpanel/system.h"
using testing::Return;
using testing::WithArgs;
namespace patchpanel {
namespace {
std::string hex(uint32_t val) {
return base::StringPrintf("0x%08x", val);
}
struct sockopt_data {
int sockfd;
int level;
int optname;
char optval[256];
socklen_t optlen;
};
void SetOptval(sockopt_data* sockopt, uint32_t optval) {
sockopt->optlen = sizeof(optval);
memcpy(sockopt->optval, &optval, sizeof(optval));
}
uint32_t GetOptval(const sockopt_data& sockopt) {
uint32_t optval;
memcpy(&optval, sockopt.optval, sizeof(optval));
return optval;
}
Fwmark fwmark(uint32_t fwmark) {
return {.fwmark = fwmark};
}
base::ScopedFD MakeTestSocket() {
return base::ScopedFD(socket(AF_INET, SOCK_DGRAM, 0));
}
class TestableRoutingService : public RoutingService {
public:
TestableRoutingService(System* system, LifelineFDService* lifeline_fd_svc)
: RoutingService(system, lifeline_fd_svc) {}
~TestableRoutingService() = default;
int GetSockopt(int sockfd,
int level,
int optname,
void* optval,
socklen_t* optlen) override {
sockopt.sockfd = sockfd;
sockopt.level = level;
sockopt.optname = optname;
memcpy(optval, sockopt.optval,
std::min(*optlen, (socklen_t)sizeof(sockopt.optval)));
*optlen = sockopt.optlen;
return getsockopt_ret;
}
int SetSockopt(int sockfd,
int level,
int optname,
const void* optval,
socklen_t optlen) override {
sockopt.sockfd = sockfd;
sockopt.level = level;
sockopt.optname = optname;
sockopt.optlen = optlen;
memcpy(sockopt.optval, optval,
std::min(optlen, (socklen_t)sizeof(sockopt.optval)));
return setsockopt_ret;
}
// Variables used to mock and track interactions with getsockopt and
// setsockopt.
int getsockopt_ret;
int setsockopt_ret;
sockopt_data sockopt;
};
class RoutingServiceTest : public testing::Test {
public:
RoutingServiceTest() = default;
protected:
void SetUp() override {
ON_CALL(lifeline_fd_svc_, AddLifelineFD)
.WillByDefault(
WithArgs<1>([](base::OnceClosure cb) {
return base::ScopedClosureRunner(base::DoNothing());
}));
}
MockSystem system_;
MockLifelineFDService lifeline_fd_svc_;
};
} // namespace
TEST_F(RoutingServiceTest, FwmarkSize) {
EXPECT_EQ(sizeof(uint32_t), sizeof(Fwmark));
}
TEST_F(RoutingServiceTest, FwmarkOperators) {
EXPECT_EQ(fwmark(0x00000000), fwmark(0x00000000) | fwmark(0x00000000));
EXPECT_EQ(fwmark(0x00000000), fwmark(0x00000000) & fwmark(0x00000000));
EXPECT_EQ(fwmark(0x00110034), fwmark(0x00110034) | fwmark(0x00000000));
EXPECT_EQ(fwmark(0x00000000), fwmark(0x00110034) & fwmark(0x00000000));
EXPECT_EQ(fwmark(0x1234abcd), fwmark(0x12340000) | fwmark(0x0000abcd));
EXPECT_EQ(fwmark(0x00000000), fwmark(0x12340000) & fwmark(0x0000abcd));
EXPECT_EQ(fwmark(0x00120000), fwmark(0x00120000) & fwmark(0x00120000));
EXPECT_EQ(fwmark(0x12fffbcd), fwmark(0x1234abcd) | fwmark(0x00fff000));
EXPECT_EQ(fwmark(0x0034a000), fwmark(0x1234abcd) & fwmark(0x00fff000));
EXPECT_EQ(fwmark(0x0000ffff), ~fwmark(0xffff0000));
EXPECT_EQ(fwmark(0x12345678), ~~fwmark(0x12345678));
EXPECT_EQ(fwmark(0x55443322), ~fwmark(0xaabbccdd));
}
TEST_F(RoutingServiceTest, FwmarkAndMaskConstants) {
EXPECT_EQ("0x00003f00", kFwmarkAllSourcesMask.ToString());
EXPECT_EQ("0xffff0000", kFwmarkRoutingMask.ToString());
EXPECT_EQ("0x00000001", kFwmarkLegacySNAT.ToString());
EXPECT_EQ("0x0000c000", kFwmarkVpnMask.ToString());
EXPECT_EQ("0x00008000", kFwmarkRouteOnVpn.ToString());
EXPECT_EQ("0x00004000", kFwmarkBypassVpn.ToString());
EXPECT_EQ("0x00002000", kFwmarkForwardedSourcesMask.ToString());
EXPECT_EQ("0x000000e0", kFwmarkQoSCategoryMask.ToString());
EXPECT_EQ(0x00003f00, kFwmarkAllSourcesMask.Value());
EXPECT_EQ(0xffff0000, kFwmarkRoutingMask.Value());
EXPECT_EQ(0x00000001, kFwmarkLegacySNAT.Value());
EXPECT_EQ(0x0000c000, kFwmarkVpnMask.Value());
EXPECT_EQ(0x00008000, kFwmarkRouteOnVpn.Value());
EXPECT_EQ(0x00004000, kFwmarkBypassVpn.Value());
EXPECT_EQ(0x00002000, kFwmarkForwardedSourcesMask.Value());
EXPECT_EQ(0x000000e0, kFwmarkQoSCategoryMask.Value());
}
TEST_F(RoutingServiceTest, FwmarkSources) {
EXPECT_EQ("0x00000000",
Fwmark::FromSource(TrafficSource::kUnknown).ToString());
EXPECT_EQ("0x00000100",
Fwmark::FromSource(TrafficSource::kChrome).ToString());
EXPECT_EQ("0x00000200", Fwmark::FromSource(TrafficSource::kUser).ToString());
EXPECT_EQ("0x00000300",
Fwmark::FromSource(TrafficSource::kUpdateEngine).ToString());
EXPECT_EQ("0x00000400",
Fwmark::FromSource(TrafficSource::kSystem).ToString());
EXPECT_EQ("0x00000500",
Fwmark::FromSource(TrafficSource::kHostVpn).ToString());
EXPECT_EQ("0x00002000", Fwmark::FromSource(TrafficSource::kArc).ToString());
EXPECT_EQ("0x00002100",
Fwmark::FromSource(TrafficSource::kCrostiniVM).ToString());
EXPECT_EQ("0x00002200",
Fwmark::FromSource(TrafficSource::kParallelsVM).ToString());
EXPECT_EQ("0x00002300",
Fwmark::FromSource(TrafficSource::kTetherDownstream).ToString());
EXPECT_EQ("0x00002400",
Fwmark::FromSource(TrafficSource::kArcVpn).ToString());
for (auto ts : kLocalSources) {
EXPECT_EQ(
"0x00000000",
(Fwmark::FromSource(ts) & kFwmarkForwardedSourcesMask).ToString());
}
for (auto ts : kForwardedSources) {
EXPECT_EQ(
kFwmarkForwardedSourcesMask.ToString(),
(Fwmark::FromSource(ts) & kFwmarkForwardedSourcesMask).ToString());
}
for (auto ts : kLocalSources) {
EXPECT_EQ("0x00000000",
(Fwmark::FromSource(ts) & ~kFwmarkAllSourcesMask).ToString());
}
for (auto ts : kForwardedSources) {
EXPECT_EQ("0x00000000",
(Fwmark::FromSource(ts) & ~kFwmarkAllSourcesMask).ToString());
}
}
TEST_F(RoutingServiceTest, FwmarkQoSCategories) {
constexpr QoSCategory kAllCategories[] = {
QoSCategory::kDefault, QoSCategory::kRealTimeInteractive,
QoSCategory::kMultimediaConferencing, QoSCategory::kNetworkControl,
QoSCategory::kWebRTC};
// The offset of the qos fields defined in Fwmark.
constexpr auto kOffset = 5;
for (const auto category : kAllCategories) {
uint32_t category_int = base::to_underlying(category);
EXPECT_EQ(category_int, Fwmark::FromQoSCategory(category).qos_category);
EXPECT_EQ(category_int << kOffset,
Fwmark::FromQoSCategory(category).Value());
EXPECT_EQ(hex(category_int << kOffset),
Fwmark::FromQoSCategory(category).ToString());
}
}
TEST_F(RoutingServiceTest, TagSocket) {
ON_CALL(system_, IfNametoindex("eth0")).WillByDefault(Return(1));
ON_CALL(system_, IfNametoindex("eth1")).WillByDefault(Return(2));
ON_CALL(system_, IfNametoindex("eth2")).WillByDefault(Return(3));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
svc->getsockopt_ret = 0;
svc->setsockopt_ret = 0;
svc->AssignInterfaceToNetwork(1, "eth0", MakeTestSocket());
svc->AssignInterfaceToNetwork(34567, "eth1", MakeTestSocket());
svc->AssignInterfaceToNetwork(12, "eth2", MakeTestSocket());
using Policy = VPNRoutingPolicy;
struct {
std::optional<int> network_id;
Policy policy;
std::optional<TrafficAnnotationId> annotation_id;
uint32_t initial_fwmark;
uint32_t expected_fwmark;
} testcases[] = {
{std::nullopt, Policy::kRouteOnVPN, std::nullopt, 0x0, 0x00008000},
{std::nullopt, Policy::kBypassVPN, std::nullopt, 0x0, 0x00004000},
{std::nullopt, Policy::kRouteOnVPN, std::nullopt, 0x1, 0x00008001},
{1, Policy::kBypassVPN, std::nullopt, 0xabcd00ef, 0x03e940ef},
{std::nullopt, Policy::kRouteOnVPN, std::nullopt, 0x11223344, 0x0000b344},
{34567, Policy::kBypassVPN, std::nullopt, 0x11223344, 0x03ea7344},
{std::nullopt, Policy::kRouteOnVPN, std::nullopt, 0x00008000, 0x00008000},
{std::nullopt, Policy::kBypassVPN, std::nullopt, 0x00004000, 0x00004000},
{std::nullopt, Policy::kBypassVPN, std::nullopt, 0x00008000, 0x00004000},
{std::nullopt, Policy::kRouteOnVPN, std::nullopt, 0x00004000, 0x00008000},
{1, Policy::kDefault, std::nullopt, 0x00008000, 0x03e90000},
{12, Policy::kDefault, std::nullopt, 0x00004000, 0x03eb0000},
};
for (const auto& tt : testcases) {
SetOptval(&svc->sockopt, tt.initial_fwmark);
EXPECT_TRUE(svc->TagSocket(4, tt.network_id, tt.policy, tt.annotation_id));
EXPECT_EQ(4, svc->sockopt.sockfd);
EXPECT_EQ(SOL_SOCKET, svc->sockopt.level);
EXPECT_EQ(SO_MARK, svc->sockopt.optname);
EXPECT_EQ(hex(tt.expected_fwmark), hex(GetOptval(svc->sockopt)));
}
// ROUTE_ON_VPN should not be set with network_id at the same time.
EXPECT_FALSE(
svc->TagSocket(4, /*network_id=*/123, Policy::kRouteOnVPN, std::nullopt));
// getsockopt() returns failure.
svc->getsockopt_ret = -1;
svc->setsockopt_ret = 0;
EXPECT_FALSE(
svc->TagSocket(4, std::nullopt, Policy::kRouteOnVPN, std::nullopt));
// setsockopt() returns failure.
svc->getsockopt_ret = 0;
svc->setsockopt_ret = -1;
EXPECT_FALSE(
svc->TagSocket(4, std::nullopt, Policy::kRouteOnVPN, std::nullopt));
}
TEST_F(RoutingServiceTest, SetFwmark) {
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
svc->getsockopt_ret = 0;
svc->setsockopt_ret = 0;
struct {
uint32_t initial_fwmark;
uint32_t fwmark_value;
uint32_t fwmark_mask;
uint32_t expected_fwmark;
} testcases[] = {
{0x0, 0x0, 0x0, 0x0},
{0x1, 0x0, 0x0, 0x1},
{0x1, 0x0, 0x1, 0x0},
{0xaabbccdd, 0x11223344, 0xf0f0f0f0, 0x1a2b3c4d},
{0xaabbccdd, 0x11223344, 0xffff0000, 0x1122ccdd},
{0xaabbccdd, 0x11223344, 0x0000ffff, 0xaabb3344},
};
for (const auto& tt : testcases) {
SetOptval(&svc->sockopt, tt.initial_fwmark);
EXPECT_TRUE(
svc->SetFwmark(4, fwmark(tt.fwmark_value), fwmark(tt.fwmark_mask)));
EXPECT_EQ(4, svc->sockopt.sockfd);
EXPECT_EQ(SOL_SOCKET, svc->sockopt.level);
EXPECT_EQ(SO_MARK, svc->sockopt.optname);
EXPECT_EQ(hex(tt.expected_fwmark), hex(GetOptval(svc->sockopt)));
}
}
TEST_F(RoutingServiceTest, SetFwmark_Failures) {
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
svc->getsockopt_ret = -1;
svc->setsockopt_ret = 0;
EXPECT_FALSE(svc->SetFwmark(4, fwmark(0x1), fwmark(0x01)));
svc = std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
svc->getsockopt_ret = 0;
svc->setsockopt_ret = -1;
EXPECT_FALSE(svc->SetFwmark(5, fwmark(0x1), fwmark(0x01)));
svc = std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
svc->getsockopt_ret = 0;
svc->setsockopt_ret = 0;
EXPECT_TRUE(svc->SetFwmark(6, fwmark(0x1), fwmark(0x01)));
}
TEST_F(RoutingServiceTest, LocalSourceSpecsPrettyPrinting) {
struct {
LocalSourceSpecs source;
std::string expected_output;
} testcases[] = {
{{}, "{source: UNKNOWN, uid: , classid: 0, is_on_vpn: false}"},
{{TrafficSource::kChrome, kUidChronos, 0, true},
"{source: CHROME, uid: chronos, classid: 0, is_on_vpn: true}"},
{{TrafficSource::kUser, kUidDebugd, 0, true},
"{source: USER, uid: debugd, classid: 0, is_on_vpn: true}"},
{{TrafficSource::kSystem, kUidTlsdate, 0, true},
"{source: SYSTEM, uid: tlsdate, classid: 0, is_on_vpn: true}"},
{{TrafficSource::kUser, kUidPluginvm, 0, true},
"{source: USER, uid: pluginvm, classid: 0, is_on_vpn: true}"},
{{TrafficSource::kUpdateEngine, "", 1234, false},
"{source: UPDATE_ENGINE, uid: , classid: 1234, is_on_vpn: false}"},
};
for (const auto& tt : testcases) {
std::ostringstream stream;
stream << tt.source;
EXPECT_EQ(tt.expected_output, stream.str());
}
}
TEST_F(RoutingServiceTest, AllocateNetworkIDs) {
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
std::set<int> net_ids;
for (int i = 0; i < 100; i++) {
int id = svc->AllocateNetworkID();
ASSERT_FALSE(net_ids.contains(id));
net_ids.insert(id);
}
}
TEST_F(RoutingServiceTest, AssignInterfaceToNetwork) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(12));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
}
TEST_F(RoutingServiceTest, AssignInterfaceToMultipleNetworks) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(12));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
int network2 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_FALSE(
svc->AssignInterfaceToNetwork(network2, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
ASSERT_EQ(nullptr, svc->GetInterface(network2));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network2));
}
TEST_F(RoutingServiceTest, AssignMultipleInterfacesToNetwork) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(12));
ON_CALL(system_, IfNametoindex("eth0")).WillByDefault(Return(13));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_FALSE(
svc->AssignInterfaceToNetwork(network1, "eth0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
ASSERT_EQ(std::nullopt, svc->GetNetworkID("eth0"));
}
TEST_F(RoutingServiceTest, ReassignDifferentInterfacesToNetwork) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(12));
ON_CALL(system_, IfNametoindex("eth0")).WillByDefault(Return(13));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
svc->ForgetNetworkID(network1);
ASSERT_EQ(nullptr, svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetNetworkID("wlan0"));
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "eth0", MakeTestSocket()));
ASSERT_EQ("eth0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1013}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(std::nullopt, svc->GetNetworkID("wlan0"));
ASSERT_EQ(1, svc->GetNetworkID("eth0"));
}
TEST_F(RoutingServiceTest, ReassignInterfaceToDifferentNetworks) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(12));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
svc->ForgetNetworkID(network1);
ASSERT_EQ(nullptr, svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network1));
ASSERT_EQ(std::nullopt, svc->GetNetworkID("wlan0"));
int network2 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network2, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network2));
ASSERT_EQ(Fwmark{.rt_table_id = 1012}, svc->GetRoutingFwmark(network2));
ASSERT_EQ(network2, svc->GetNetworkID("wlan0"));
ASSERT_EQ(nullptr, svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network1));
}
TEST_F(RoutingServiceTest, AssignUnknownInterfaceToNetwork) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(-1));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
}
TEST_F(RoutingServiceTest, NetworkAssignmentAutomaticCleanup) {
ON_CALL(system_, IfNametoindex("wlan0")).WillByDefault(Return(-1));
auto svc =
std::make_unique<TestableRoutingService>(&system_, &lifeline_fd_svc_);
int network1 = svc->AllocateNetworkID();
base::OnceClosure on_lifeline_fd_event;
EXPECT_CALL(lifeline_fd_svc_, AddLifelineFD)
.WillOnce(WithArgs<1>([&](base::OnceClosure cb) {
on_lifeline_fd_event = std::move(cb);
return base::ScopedClosureRunner(base::DoNothing());
}));
ASSERT_TRUE(
svc->AssignInterfaceToNetwork(network1, "wlan0", MakeTestSocket()));
ASSERT_EQ("wlan0", *svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network1));
ASSERT_EQ(network1, svc->GetNetworkID("wlan0"));
std::move(on_lifeline_fd_event).Run();
ASSERT_EQ(nullptr, svc->GetInterface(network1));
ASSERT_EQ(std::nullopt, svc->GetRoutingFwmark(network1));
ASSERT_EQ(std::nullopt, svc->GetNetworkID("wlan0"));
ASSERT_EQ(0, svc->GetNetworkIDs().size());
}
} // namespace patchpanel