mirror of
https://github.com/OpenVPN/openvpn3.git
synced 2024-09-20 12:12:15 +02:00
b55f78dd1d
On CentOS 7 "ip route get" produces different output comparison to Ubuntu 18 etc. Signed-off-by: Lev Stipakov <lev@openvpn.net>
327 lines
9.8 KiB
C++
327 lines
9.8 KiB
C++
// 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);
|
|
}
|
|
}
|