blob: 0349424a463ebbe70230f5585dffeeac0f357d7d [file] [log] [blame]
// Copyright 2020 The Chromium OS 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 "patchpanel/routing_service.h"
#include <algorithm>
#include <memory>
#include <sstream>
#include <base/strings/stringprintf.h>
#include <gtest/gtest.h>
namespace patchpanel {
namespace {
auto& BYPASS_VPN = patchpanel::SetVpnIntentRequest::BYPASS_VPN;
auto& DEFAULT_ROUTING = patchpanel::SetVpnIntentRequest::DEFAULT_ROUTING;
auto& ROUTE_ON_VPN = patchpanel::SetVpnIntentRequest::ROUTE_ON_VPN;
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};
}
class TestableRoutingService : public RoutingService {
public:
TestableRoutingService() = default;
~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 {}
};
} // 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(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());
}
TEST_F(RoutingServiceTest, FwmarkSources) {
EXPECT_EQ("0x00000000", Fwmark::FromSource(UNKNOWN).ToString());
EXPECT_EQ("0x00000100", Fwmark::FromSource(CHROME).ToString());
EXPECT_EQ("0x00000200", Fwmark::FromSource(USER).ToString());
EXPECT_EQ("0x00000300", Fwmark::FromSource(UPDATE_ENGINE).ToString());
EXPECT_EQ("0x00000400", Fwmark::FromSource(SYSTEM).ToString());
EXPECT_EQ("0x00000500", Fwmark::FromSource(HOST_VPN).ToString());
EXPECT_EQ("0x00002000", Fwmark::FromSource(ARC).ToString());
EXPECT_EQ("0x00002100", Fwmark::FromSource(CROSVM).ToString());
EXPECT_EQ("0x00002200", Fwmark::FromSource(PLUGINVM).ToString());
EXPECT_EQ("0x00002300", Fwmark::FromSource(TETHER_DOWNSTREAM).ToString());
EXPECT_EQ("0x00002400", Fwmark::FromSource(ARC_VPN).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, SetVpnFwmark) {
auto svc = std::make_unique<TestableRoutingService>();
svc->getsockopt_ret = 0;
svc->setsockopt_ret = 0;
struct {
patchpanel::SetVpnIntentRequest::VpnRoutingPolicy policy;
uint32_t initial_fwmark;
uint32_t expected_fwmark;
} testcases[] = {
{ROUTE_ON_VPN, 0x0, 0x00008000},
{BYPASS_VPN, 0x0, 0x00004000},
{ROUTE_ON_VPN, 0x1, 0x00008001},
{BYPASS_VPN, 0xabcd00ef, 0xabcd40ef},
{ROUTE_ON_VPN, 0x11223344, 0x1122b344},
{BYPASS_VPN, 0x11223344, 0x11227344},
{ROUTE_ON_VPN, 0x00008000, 0x00008000},
{BYPASS_VPN, 0x00004000, 0x00004000},
{BYPASS_VPN, 0x00008000, 0x00004000},
{ROUTE_ON_VPN, 0x00004000, 0x00008000},
{DEFAULT_ROUTING, 0x00008000, 0x00000000},
{DEFAULT_ROUTING, 0x00004000, 0x00000000},
};
for (const auto& tt : testcases) {
SetOptval(&svc->sockopt, tt.initial_fwmark);
EXPECT_TRUE(svc->SetVpnFwmark(4, tt.policy));
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)));
}
svc->getsockopt_ret = -1;
svc->setsockopt_ret = 0;
EXPECT_FALSE(svc->SetVpnFwmark(4, ROUTE_ON_VPN));
svc->getsockopt_ret = 0;
svc->setsockopt_ret = -1;
EXPECT_FALSE(svc->SetVpnFwmark(4, ROUTE_ON_VPN));
svc->getsockopt_ret = 0;
svc->setsockopt_ret = 0;
EXPECT_FALSE(svc->SetVpnFwmark(
4, (patchpanel::SetVpnIntentRequest::VpnRoutingPolicy)-1));
}
TEST_F(RoutingServiceTest, SetFwmark) {
auto svc = std::make_unique<TestableRoutingService>();
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>();
svc->getsockopt_ret = -1;
svc->setsockopt_ret = 0;
EXPECT_FALSE(svc->SetFwmark(4, fwmark(0x1), fwmark(0x01)));
svc = std::make_unique<TestableRoutingService>();
svc->getsockopt_ret = 0;
svc->setsockopt_ret = -1;
EXPECT_FALSE(svc->SetFwmark(5, fwmark(0x1), fwmark(0x01)));
svc = std::make_unique<TestableRoutingService>();
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::CHROME, kUidChronos, 0, true},
"{source: CHROME, uid: chronos, classid: 0, is_on_vpn: true}"},
{{TrafficSource::USER, kUidDebugd, 0, true},
"{source: USER, uid: debugd, classid: 0, is_on_vpn: true}"},
{{TrafficSource::SYSTEM, kUidTlsdate, 0, true},
"{source: SYSTEM, uid: tlsdate, classid: 0, is_on_vpn: true}"},
{{TrafficSource::USER, kUidPluginvm, 0, true},
"{source: USER, uid: pluginvm, classid: 0, is_on_vpn: true}"},
{{TrafficSource::UPDATE_ENGINE, "", 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());
}
}
} // namespace patchpanel