mirror of
https://github.com/deneraraujo/OpenVPNAdapter.git
synced 2026-04-06 00:00:03 +08:00
Merge commit '86cc97e55fe346502462284d2e636a2b3708163e' as 'Sources/OpenVPN3'
This commit is contained in:
326
Sources/OpenVPN3/test/unittests/test_sitnl.cpp
Normal file
326
Sources/OpenVPN3/test/unittests/test_sitnl.cpp
Normal file
@@ -0,0 +1,326 @@
|
||||
// OpenVPN -- An application to securely tunnel IP networks
|
||||
// over a single port, with support for SSL/TLS-based
|
||||
// session authentication and key exchange,
|
||||
// packet encryption, packet authentication, and
|
||||
// packet compression.
|
||||
//
|
||||
// Copyright (C) 2012-2019 OpenVPN Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License Version 3
|
||||
// as published by the Free Software Foundation.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program in the COPYING file.
|
||||
|
||||
#include <fstream>
|
||||
#include "test_common.h"
|
||||
|
||||
#include "openvpn/common/argv.hpp"
|
||||
#include "openvpn/common/process.hpp"
|
||||
#include "openvpn/common/redir.hpp"
|
||||
#include "openvpn/common/splitlines.hpp"
|
||||
|
||||
#include "openvpn/tun/linux/client/sitnl.hpp"
|
||||
|
||||
using namespace openvpn;
|
||||
using namespace TunNetlink;
|
||||
|
||||
namespace unittests
|
||||
{
|
||||
static std::string path_to_ip;
|
||||
|
||||
class SitnlTest : public testing::Test
|
||||
{
|
||||
private:
|
||||
void add_device(std::string name)
|
||||
{
|
||||
RedirectPipe::InOut pipe;
|
||||
Argv argv;
|
||||
argv.emplace_back(path_to_ip);
|
||||
argv.emplace_back("tuntap");
|
||||
argv.emplace_back("add");
|
||||
argv.emplace_back("mode");
|
||||
argv.emplace_back("tun");
|
||||
argv.emplace_back(std::move(name));
|
||||
system_cmd(argv[0], argv, nullptr, pipe, 0);
|
||||
}
|
||||
|
||||
void remove_device(std::string name)
|
||||
{
|
||||
RedirectPipe::InOut pipe;
|
||||
Argv argv;
|
||||
argv.emplace_back(path_to_ip);
|
||||
argv.emplace_back("tuntap");
|
||||
argv.emplace_back("delete");
|
||||
argv.emplace_back("mode");
|
||||
argv.emplace_back("tun");
|
||||
argv.emplace_back(std::move(name));
|
||||
system_cmd(argv[0], argv, nullptr, pipe, 0);
|
||||
}
|
||||
|
||||
protected:
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
// different distros have ip tool in different places
|
||||
std::vector<std::string> paths{"/bin/ip", "/sbin/ip", "/usr/bin/ip", "/usr/sbin/ip"};
|
||||
for (const auto& path : paths)
|
||||
{
|
||||
std::ifstream f(path);
|
||||
if (f)
|
||||
{
|
||||
path_to_ip = path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ASSERT_FALSE(path_to_ip.empty()) << "unable to find ip tool";
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
if (geteuid() != 0)
|
||||
GTEST_SKIP() << "Need root to run this test";
|
||||
|
||||
add_device(dev);
|
||||
add_device(dev2);
|
||||
}
|
||||
|
||||
virtual void TearDown()
|
||||
{
|
||||
remove_device(dev);
|
||||
remove_device(dev2);
|
||||
}
|
||||
|
||||
template <typename CALLBACK>
|
||||
void cmd(Argv argv, CALLBACK cb)
|
||||
{
|
||||
// runs command, reads output and calls a callback
|
||||
RedirectPipe::InOut pipe;
|
||||
ASSERT_EQ(system_cmd(argv[0], argv, nullptr, pipe, 0), 0) << "failed to run command " << argv[0];
|
||||
|
||||
SplitLines sl(pipe.out);
|
||||
bool called = false;
|
||||
while (sl()) {
|
||||
const std::string &line = sl.line_ref();
|
||||
|
||||
std::vector<std::string> v = Split::by_space<std::vector<std::string>, NullLex, SpaceMatch, Split::NullLimit>(line);
|
||||
|
||||
// blank line?
|
||||
if (v.empty())
|
||||
continue;
|
||||
|
||||
cb(v, pipe.out, called);
|
||||
}
|
||||
|
||||
ASSERT_TRUE(called) << pipe.out;
|
||||
}
|
||||
|
||||
template <typename CALLBACK>
|
||||
void ip_a_show_dev(CALLBACK cb)
|
||||
{
|
||||
// get addrs with "ip a show dev"
|
||||
RedirectPipe::InOut pipe;
|
||||
Argv argv;
|
||||
argv.emplace_back(path_to_ip);
|
||||
argv.emplace_back("a");
|
||||
argv.emplace_back("show");
|
||||
argv.emplace_back("dev");
|
||||
argv.emplace_back(dev);
|
||||
cmd(argv, cb);
|
||||
}
|
||||
|
||||
template <typename CALLBACK>
|
||||
void ip_route_get(std::string dst, CALLBACK cb)
|
||||
{
|
||||
// get route with "ip route get"
|
||||
RedirectPipe::InOut pipe;
|
||||
Argv argv;
|
||||
argv.emplace_back(path_to_ip);
|
||||
argv.emplace_back("route");
|
||||
argv.emplace_back("get");
|
||||
argv.emplace_back(std::move(dst));
|
||||
cmd(argv, cb);
|
||||
}
|
||||
|
||||
std::string dev = "tun999";
|
||||
std::string dev2 = "tun9999";
|
||||
|
||||
std::string addr4 = "10.10.0.2";
|
||||
std::string route4 = "10.110.0.0/24";
|
||||
std::string gw4 = "10.10.0.1";
|
||||
|
||||
std::string addr6 = "fe80:20c3:aaaa:bbbb::cccc";
|
||||
std::string route6 = "fe80:20c3:cccc:dddd::0/64";
|
||||
std::string gw6 = "fe80:20c3:aaaa:bbbb:cccc:dddd:eeee:1";
|
||||
|
||||
int ipv4_prefix_len = 16;
|
||||
int ipv6_prefix_len = 64;
|
||||
int mtu = 1234;
|
||||
};
|
||||
|
||||
TEST_F(SitnlTest, TestAddrAdd4)
|
||||
{
|
||||
auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), ipv4_prefix_len, broadcast), 0);
|
||||
|
||||
ip_a_show_dev([this, &broadcast](std::vector<std::string>& v, const std::string& out, bool& called) {
|
||||
if (v[0] == "inet")
|
||||
{
|
||||
called = true;
|
||||
ASSERT_EQ(v[1], addr4 + "/" + std::to_string(ipv4_prefix_len)) << out;
|
||||
ASSERT_EQ(v[3], broadcast.to_string()) << out;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestAddrAdd6)
|
||||
{
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), ipv6_prefix_len), 0);
|
||||
|
||||
ip_a_show_dev([this](std::vector<std::string>& v, const std::string& out, bool& called) {
|
||||
if (v[0] == "inet6")
|
||||
{
|
||||
called = true;
|
||||
ASSERT_EQ(v[1], addr6 + "/" + std::to_string(ipv6_prefix_len)) << out;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestSetMTU)
|
||||
{
|
||||
ASSERT_EQ(SITNL::net_iface_mtu_set(dev, mtu), 0);
|
||||
|
||||
ip_a_show_dev([this](std::vector<std::string>& v, const std::string& out, bool& called) {
|
||||
if (dev + ":" == v[1])
|
||||
{
|
||||
called = true;
|
||||
ASSERT_EQ(v[4], std::to_string(mtu)) << out;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestAddRoute4)
|
||||
{
|
||||
// add address
|
||||
auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), ipv4_prefix_len, broadcast), 0);
|
||||
|
||||
// up interface
|
||||
ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
|
||||
|
||||
// add route
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4(route4), IPv4::Addr::from_string(gw4), dev, 0, 0), 0);
|
||||
|
||||
std::string dst{"10.110.0.100"};
|
||||
|
||||
ip_route_get(dst, [this, &dst](std::vector<std::string>& v, const std::string& out, bool& called) {
|
||||
if (v[0] == dst)
|
||||
{
|
||||
called = true;
|
||||
v.resize(7);
|
||||
auto expected = std::vector<std::string>{dst, "via", gw4, "dev", dev, "src", addr4};
|
||||
ASSERT_EQ(v, expected) << out;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestAddRoute6)
|
||||
{
|
||||
// add address
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), ipv6_prefix_len), 0);
|
||||
|
||||
// up interface
|
||||
ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
|
||||
|
||||
// add route
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route6(route6), IPv6::Addr::from_string(gw6), dev, 0, 0), 0);
|
||||
|
||||
std::string dst{"fe80:20c3:cccc:dddd:cccc:dddd:eeee:ffff"};
|
||||
|
||||
ip_route_get(dst, [this, &dst](std::vector<std::string> &v1, const std::string &out, bool &called) {
|
||||
if (v1[0] == dst)
|
||||
{
|
||||
called = true;
|
||||
v1.resize(7);
|
||||
// iptools 4.15 (Ubuntu 18)
|
||||
auto expected1 = std::vector<std::string>{dst, "from", "::", "via", gw6, "dev", dev};
|
||||
auto ok1 = (v1 == expected1);
|
||||
|
||||
auto v2 = v1;
|
||||
v2.resize(5);
|
||||
// iptools 4.11 (CentOS 7)
|
||||
auto expected2 = std::vector<std::string>{dst, "via", gw6, "dev", dev};
|
||||
auto ok2 = (v2 == expected2);
|
||||
|
||||
if (!ok1 && !ok2)
|
||||
{
|
||||
// this is just a way to print actual value and all expected values
|
||||
EXPECT_EQ(v1, expected1);
|
||||
EXPECT_EQ(v2, expected2);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestBestGw4)
|
||||
{
|
||||
// add address
|
||||
auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), ipv4_prefix_len, broadcast), 0);
|
||||
|
||||
// up interface
|
||||
ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
|
||||
|
||||
// add routes
|
||||
|
||||
// shortest prefix
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.0.0.0/8"), IPv4::Addr::from_string("10.10.10.10"), dev, 0, 0), 0);
|
||||
// longest prefix, lowest metric
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.10.10.0/24"), IPv4::Addr::from_string("10.10.10.13"), dev, 0, 0), 0);
|
||||
// short prefix
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.10.0.0/16"), IPv4::Addr::from_string("10.10.10.11"), dev, 0, 0), 0);
|
||||
// longest prefix, highest metric
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.10.10.0/24"), IPv4::Addr::from_string("10.10.10.12"), dev, 0, 10), 0);
|
||||
|
||||
IPv4::Addr best_gw;
|
||||
std::string best_iface;
|
||||
ASSERT_EQ(SITNL::net_route_best_gw(IP::Route4("10.10.10.1/32"), best_gw, best_iface), 0);
|
||||
|
||||
// we should get a gateway with longest prefix and lowest metric
|
||||
|
||||
ASSERT_EQ(best_gw.to_string(), "10.10.10.13");
|
||||
ASSERT_EQ(best_iface, dev);
|
||||
}
|
||||
|
||||
TEST_F(SitnlTest, TestBestGw4FilterIface)
|
||||
{
|
||||
// add addresses
|
||||
auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), ipv4_prefix_len, broadcast), 0);
|
||||
|
||||
broadcast = IPv4::Addr::from_string("10.20.0.2") | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
|
||||
ASSERT_EQ(SITNL::net_addr_add(dev2, IPv4::Addr::from_string("10.20.0.2"), ipv4_prefix_len, broadcast), 0);
|
||||
|
||||
// up interfaces
|
||||
SITNL::net_iface_up(dev, true);
|
||||
SITNL::net_iface_up(dev2, true);
|
||||
|
||||
// add routes
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.11.0.0/16"), IPv4::Addr::from_string("10.10.0.1"), dev, 0, 0), 0);
|
||||
ASSERT_EQ(SITNL::net_route_add(IP::Route4("10.11.12.0/24"), IPv4::Addr::from_string("10.20.0.1"), dev2, 0, 0), 0);
|
||||
|
||||
IPv4::Addr best_gw;
|
||||
std::string best_iface;
|
||||
|
||||
// filter out gateway with longest prefix route
|
||||
SITNL::net_route_best_gw(IP::Route4("10.11.12.13/32"), best_gw, best_iface, dev2);
|
||||
|
||||
ASSERT_EQ(best_gw.to_string(), "10.10.0.1");
|
||||
ASSERT_EQ(best_iface, dev);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user