OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_sitnl.cpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12
13#include <fstream>
14#include <sys/capability.h>
15#include "test_common.hpp"
16
21
23
24using namespace openvpn;
25using namespace TunNetlink;
26
27namespace unittests {
28static std::string path_to_ip;
29
30class SitnlTest : public testing::Test
31{
32 private:
33 void add_device(std::string name)
34 {
36 Argv argv;
37 argv.emplace_back(path_to_ip);
38 argv.emplace_back("tuntap");
39 argv.emplace_back("add");
40 argv.emplace_back("mode");
41 argv.emplace_back("tun");
42 argv.emplace_back(std::move(name));
43 system_cmd(argv[0], argv, nullptr, pipe, 0, nullptr);
44 }
45
46 void remove_device(std::string name)
47 {
49 Argv argv;
50 argv.emplace_back(path_to_ip);
51 argv.emplace_back("tuntap");
52 argv.emplace_back("delete");
53 argv.emplace_back("mode");
54 argv.emplace_back("tun");
55 argv.emplace_back(std::move(name));
56 system_cmd(argv[0], argv, nullptr, pipe, 0, nullptr);
57 }
58
59 protected:
60 static void SetUpTestSuite()
61 {
62 // different distros have ip tool in different places
63 const std::vector<std::string> paths{"/bin/ip", "/sbin/ip", "/usr/bin/ip", "/usr/sbin/ip"};
64 for (const auto &path : paths)
65 {
66 const std::ifstream f(path);
67 if (f)
68 {
69 path_to_ip = path;
70 break;
71 }
72 }
73 ASSERT_FALSE(path_to_ip.empty()) << "unable to find ip tool";
74 }
75
76 static bool haveCapNetAdmin()
77 {
78 cap_t cap = cap_get_proc();
79 cap_flag_value_t v = CAP_CLEAR;
80 cap_get_flag(cap, CAP_NET_ADMIN, CAP_EFFECTIVE, &v);
81 cap_free(cap);
82 return v == CAP_SET;
83 }
84
85 void SetUp() override
86 {
87 if (!haveCapNetAdmin())
88 GTEST_SKIP() << "Need CAP_NET_ADMIN to run this test";
89
92 }
93
94 void TearDown() override
95 {
98 }
99
100 template <typename CALLBACK>
101 void cmd(const Argv &argv, CALLBACK cb)
102 {
103 // runs command, reads output and calls a callback
105 ASSERT_EQ(system_cmd(argv[0], argv, nullptr, pipe, 0, nullptr), 0) << "failed to run command " << argv[0];
106
107 SplitLines sl(pipe.out);
108 bool called = false;
109 while (sl())
110 {
111 const std::string &line = sl.line_ref();
112
113 std::vector<std::string> v = Split::by_space<std::vector<std::string>, NullLex, SpaceMatch, Split::NullLimit>(line);
114
115 // blank line?
116 if (v.empty())
117 continue;
118
119 cb(v, pipe.out, called);
120 }
121
122 ASSERT_TRUE(called) << pipe.out;
123 }
124
125 template <typename CALLBACK>
126 void ip_a_show_dev(CALLBACK cb)
127 {
128 // get addrs with "ip a show dev"
129 Argv argv;
130 argv.emplace_back(path_to_ip);
131 argv.emplace_back("a");
132 argv.emplace_back("show");
133 argv.emplace_back("dev");
134 argv.emplace_back(dev);
135 cmd(argv, cb);
136 }
137
138 template <typename CALLBACK>
139 void ip_route_get(std::string dst, CALLBACK cb)
140 {
141 // get route with "ip route get"
142 Argv argv;
143 argv.emplace_back(path_to_ip);
144 argv.emplace_back("route");
145 argv.emplace_back("get");
146 argv.emplace_back(std::move(dst));
147 cmd(argv, cb);
148 }
149
150 std::string dev = "tun999";
151 std::string dev2 = "tun9999";
152
153 std::string addr4 = "10.10.0.2";
154 std::string route4 = "10.110.0.0/24";
155 std::string gw4 = "10.10.0.1";
156
157 std::string addr6 = "fe80:20c3:aaaa:bbbb::cccc";
158 std::string route6 = "fe80:20c3:cccc:dddd::0/64";
159 std::string gw6 = "fe80:20c3:aaaa:bbbb:cccc:dddd:eeee:1";
160
163 int mtu = 1234;
164};
165
166TEST_F(SitnlTest, TestAddrAdd4)
167{
168 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
169 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
170
171 ip_a_show_dev([this, &broadcast](std::vector<std::string> &v, const std::string &out, bool &called)
172 {
173 if (v[0] == "inet")
174 {
175 called = true;
176 ASSERT_EQ(v[1], addr4 + "/" + std::to_string(ipv4_prefix_len)) << out;
177 ASSERT_EQ(v[3], broadcast.to_string()) << out;
178 } });
179}
180
181TEST_F(SitnlTest, TestAddrAdd6)
182{
183 ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), static_cast<unsigned char>(ipv6_prefix_len)), 0);
184
185 ip_a_show_dev([this](std::vector<std::string> &v, const std::string &out, bool &called)
186 {
187 if (v[0] == "inet6")
188 {
189 called = true;
190 ASSERT_EQ(v[1], addr6 + "/" + std::to_string(ipv6_prefix_len)) << out;
191 } });
192}
193
194TEST_F(SitnlTest, TestSetMTU)
195{
196 ASSERT_EQ(SITNL::net_iface_mtu_set(dev, mtu), 0);
197
198 ip_a_show_dev([this](std::vector<std::string> &v, const std::string &out, bool &called)
199 {
200 if ((v.size() > 1) && (v[1] == dev + ":"))
201 {
202 called = true;
203 ASSERT_EQ(v[4], std::to_string(mtu)) << out;
204 } });
205}
206
207TEST_F(SitnlTest, TestAddRoute4)
208{
209 // add address
210 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
211 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
212
213 // up interface
214 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
215
216 // add route
217 ASSERT_EQ(SITNL::net_route_add(IP::Route4(route4), IPv4::Addr::from_string(gw4), dev, 0, 0), 0);
218
219 std::string dst{"10.110.0.100"};
220
221 ip_route_get(dst, [this, &dst](std::vector<std::string> &v, const std::string &out, bool &called)
222 {
223 if (v[0] == dst)
224 {
225 called = true;
226 v.resize(7);
227 auto expected = std::vector<std::string>{dst, "via", gw4, "dev", dev, "src", addr4};
228 ASSERT_EQ(v, expected) << out;
229 } });
230}
231
232TEST_F(SitnlTest, TestAddRoute6)
233{
234 // add address
235 ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), static_cast<unsigned char>(ipv6_prefix_len)), 0);
236
237 // up interface
238 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
239
240 // add route
241 ASSERT_EQ(SITNL::net_route_add(IP::Route6(route6), IPv6::Addr::from_string(gw6), dev, 0, 0), 0);
242
243 std::string dst{"fe80:20c3:cccc:dddd:cccc:dddd:eeee:ffff"};
244 /* Bug in iproute 6.1.0 with glibc 2.37+ that truncates
245 long ipv6 addresses due to strcpy on overlapping buffers.
246 See also https://bugzilla.redhat.com/show_bug.cgi?id=2209701 */
247 std::string dst_trunc{dst};
248 dst_trunc.resize(31);
249
250 ip_route_get(dst, [this, &dst, &dst_trunc](std::vector<std::string> &v1, const std::string &out, bool &called)
251 {
252 if (v1[0] == dst || v1[0] == dst_trunc)
253 {
254 const std::string dst_out = (v1[0] == dst) ? dst : dst_trunc;
255 called = true;
256 v1.resize(7);
257 // iproute 4.15 (Ubuntu 18)
258 auto expected1 = std::vector<std::string>{dst_out, "from", "::", "via", gw6, "dev", dev};
259 auto ok1 = (v1 == expected1);
260
261 auto v2 = v1;
262 v2.resize(5);
263 // iproute 4.11 (CentOS 7)
264 auto expected2 = std::vector<std::string>{dst_out, "via", gw6, "dev", dev};
265 auto ok2 = (v2 == expected2);
266
267 if (!ok1 && !ok2)
268 {
269 // this is just a way to print actual value and all expected values
270 EXPECT_EQ(v1, expected1);
271 EXPECT_EQ(v2, expected2);
272 }
273 } });
274}
275
276TEST_F(SitnlTest, TestBestGw4)
277{
278 // add address
279 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
280 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
281
282 // up interface
283 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
284
285 // add routes
286
287 // shortest prefix
288 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);
289 // longest prefix, lowest metric
290 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);
291 // short prefix
292 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);
293 // longest prefix, highest metric
294 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);
295
296 IPv4::Addr best_gw;
297 std::string best_iface;
298 ASSERT_EQ(SITNL::net_route_best_gw(IP::Route4("10.10.10.1/32"), best_gw, best_iface), 0);
299
300 // we should get a gateway with longest prefix and lowest metric
301
302 ASSERT_EQ(best_gw.to_string(), "10.10.10.13");
303 ASSERT_EQ(best_iface, dev);
304}
305
306TEST_F(SitnlTest, TestBestGw4FilterIface)
307{
308 // add addresses
309 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
310 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
311
312 broadcast = IPv4::Addr::from_string("10.20.0.2") | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
313 ASSERT_EQ(SITNL::net_addr_add(dev2, IPv4::Addr::from_string("10.20.0.2"), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
314
315 // up interfaces
316 SITNL::net_iface_up(dev, true);
317 SITNL::net_iface_up(dev2, true);
318
319 // add routes
320 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);
321 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);
322
323 IPv4::Addr best_gw;
324 std::string best_iface;
325
326 // filter out gateway with longest prefix route
327 SITNL::net_route_best_gw(IP::Route4("10.11.12.13/32"), best_gw, best_iface, dev2);
328
329 ASSERT_EQ(best_gw.to_string(), "10.10.0.1");
330 ASSERT_EQ(best_iface, dev);
331}
332} // namespace unittests
std::string to_string() const
Definition ipv4.hpp:229
static Addr from_string(const std::string &ipstr, const TITLE &title)
Definition ipv4.hpp:202
static Addr from_string(const std::string &ipstr, const TITLE &title)
Definition ipv6.hpp:101
std::string & line_ref()
void add_device(std::string name)
static void SetUpTestSuite()
void remove_device(std::string name)
void SetUp() override
void ip_route_get(std::string dst, CALLBACK cb)
void cmd(const Argv &argv, CALLBACK cb)
static bool haveCapNetAdmin()
void ip_a_show_dev(CALLBACK cb)
void TearDown() override
int system_cmd(const std::string &cmd, const Argv &argv, RedirectBase *redir, const Environ *env, const sigset_t *sigmask)
Definition process.hpp:90
TEST_F(IpHelperTest, TestAddRoute4)
static std::string path_to_ip
const std::string expected
auto f(const Thing1 t)
static std::stringstream out
Definition test_path.cpp:10