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 std::vector<std::string> paths{"/bin/ip", "/sbin/ip", "/usr/bin/ip", "/usr/sbin/ip"};
64 for (const auto &path : paths)
65 {
66 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"
130 Argv argv;
131 argv.emplace_back(path_to_ip);
132 argv.emplace_back("a");
133 argv.emplace_back("show");
134 argv.emplace_back("dev");
135 argv.emplace_back(dev);
136 cmd(argv, cb);
137 }
138
139 template <typename CALLBACK>
140 void ip_route_get(std::string dst, CALLBACK cb)
141 {
142 // get route with "ip route get"
144 Argv argv;
145 argv.emplace_back(path_to_ip);
146 argv.emplace_back("route");
147 argv.emplace_back("get");
148 argv.emplace_back(std::move(dst));
149 cmd(argv, cb);
150 }
151
152 std::string dev = "tun999";
153 std::string dev2 = "tun9999";
154
155 std::string addr4 = "10.10.0.2";
156 std::string route4 = "10.110.0.0/24";
157 std::string gw4 = "10.10.0.1";
158
159 std::string addr6 = "fe80:20c3:aaaa:bbbb::cccc";
160 std::string route6 = "fe80:20c3:cccc:dddd::0/64";
161 std::string gw6 = "fe80:20c3:aaaa:bbbb:cccc:dddd:eeee:1";
162
165 int mtu = 1234;
166};
167
168TEST_F(SitnlTest, TestAddrAdd4)
169{
170 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
171 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
172
173 ip_a_show_dev([this, &broadcast](std::vector<std::string> &v, const std::string &out, bool &called)
174 {
175 if (v[0] == "inet")
176 {
177 called = true;
178 ASSERT_EQ(v[1], addr4 + "/" + std::to_string(ipv4_prefix_len)) << out;
179 ASSERT_EQ(v[3], broadcast.to_string()) << out;
180 } });
181}
182
183TEST_F(SitnlTest, TestAddrAdd6)
184{
185 ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), static_cast<unsigned char>(ipv6_prefix_len)), 0);
186
187 ip_a_show_dev([this](std::vector<std::string> &v, const std::string &out, bool &called)
188 {
189 if (v[0] == "inet6")
190 {
191 called = true;
192 ASSERT_EQ(v[1], addr6 + "/" + std::to_string(ipv6_prefix_len)) << out;
193 } });
194}
195
196TEST_F(SitnlTest, TestSetMTU)
197{
198 ASSERT_EQ(SITNL::net_iface_mtu_set(dev, mtu), 0);
199
200 ip_a_show_dev([this](std::vector<std::string> &v, const std::string &out, bool &called)
201 {
202 if ((v.size() > 1) && (v[1] == dev + ":"))
203 {
204 called = true;
205 ASSERT_EQ(v[4], std::to_string(mtu)) << out;
206 } });
207}
208
209TEST_F(SitnlTest, TestAddRoute4)
210{
211 // add address
212 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
213 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
214
215 // up interface
216 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
217
218 // add route
219 ASSERT_EQ(SITNL::net_route_add(IP::Route4(route4), IPv4::Addr::from_string(gw4), dev, 0, 0), 0);
220
221 std::string dst{"10.110.0.100"};
222
223 ip_route_get(dst, [this, &dst](std::vector<std::string> &v, const std::string &out, bool &called)
224 {
225 if (v[0] == dst)
226 {
227 called = true;
228 v.resize(7);
229 auto expected = std::vector<std::string>{dst, "via", gw4, "dev", dev, "src", addr4};
230 ASSERT_EQ(v, expected) << out;
231 } });
232}
233
234TEST_F(SitnlTest, TestAddRoute6)
235{
236 // add address
237 ASSERT_EQ(SITNL::net_addr_add(dev, IPv6::Addr::from_string(addr6), static_cast<unsigned char>(ipv6_prefix_len)), 0);
238
239 // up interface
240 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
241
242 // add route
243 ASSERT_EQ(SITNL::net_route_add(IP::Route6(route6), IPv6::Addr::from_string(gw6), dev, 0, 0), 0);
244
245 std::string dst{"fe80:20c3:cccc:dddd:cccc:dddd:eeee:ffff"};
246 /* Bug in iproute 6.1.0 with glibc 2.37+ that truncates
247 long ipv6 addresses due to strcpy on overlapping buffers.
248 See also https://bugzilla.redhat.com/show_bug.cgi?id=2209701 */
249 std::string dst_trunc{dst};
250 dst_trunc.resize(31);
251
252 ip_route_get(dst, [this, &dst, &dst_trunc](std::vector<std::string> &v1, const std::string &out, bool &called)
253 {
254 if (v1[0] == dst || v1[0] == dst_trunc)
255 {
256 std::string dst_out = (v1[0] == dst) ? dst : dst_trunc;
257 called = true;
258 v1.resize(7);
259 // iproute 4.15 (Ubuntu 18)
260 auto expected1 = std::vector<std::string>{dst_out, "from", "::", "via", gw6, "dev", dev};
261 auto ok1 = (v1 == expected1);
262
263 auto v2 = v1;
264 v2.resize(5);
265 // iproute 4.11 (CentOS 7)
266 auto expected2 = std::vector<std::string>{dst_out, "via", gw6, "dev", dev};
267 auto ok2 = (v2 == expected2);
268
269 if (!ok1 && !ok2)
270 {
271 // this is just a way to print actual value and all expected values
272 EXPECT_EQ(v1, expected1);
273 EXPECT_EQ(v2, expected2);
274 }
275 } });
276}
277
278TEST_F(SitnlTest, TestBestGw4)
279{
280 // add address
281 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
282 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
283
284 // up interface
285 ASSERT_EQ(SITNL::net_iface_up(dev, true), 0);
286
287 // add routes
288
289 // shortest prefix
290 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);
291 // longest prefix, lowest metric
292 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);
293 // short prefix
294 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);
295 // longest prefix, highest metric
296 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);
297
298 IPv4::Addr best_gw;
299 std::string best_iface;
300 ASSERT_EQ(SITNL::net_route_best_gw(IP::Route4("10.10.10.1/32"), best_gw, best_iface), 0);
301
302 // we should get a gateway with longest prefix and lowest metric
303
304 ASSERT_EQ(best_gw.to_string(), "10.10.10.13");
305 ASSERT_EQ(best_iface, dev);
306}
307
308TEST_F(SitnlTest, TestBestGw4FilterIface)
309{
310 // add addresses
311 auto broadcast = IPv4::Addr::from_string(addr4) | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
312 ASSERT_EQ(SITNL::net_addr_add(dev, IPv4::Addr::from_string(addr4), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
313
314 broadcast = IPv4::Addr::from_string("10.20.0.2") | ~IPv4::Addr::netmask_from_prefix_len(ipv4_prefix_len);
315 ASSERT_EQ(SITNL::net_addr_add(dev2, IPv4::Addr::from_string("10.20.0.2"), static_cast<unsigned char>(ipv4_prefix_len), broadcast), 0);
316
317 // up interfaces
318 SITNL::net_iface_up(dev, true);
319 SITNL::net_iface_up(dev2, true);
320
321 // add routes
322 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);
323 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);
324
325 IPv4::Addr best_gw;
326 std::string best_iface;
327
328 // filter out gateway with longest prefix route
329 SITNL::net_route_best_gw(IP::Route4("10.11.12.13/32"), best_gw, best_iface, dev2);
330
331 ASSERT_EQ(best_gw.to_string(), "10.10.0.1");
332 ASSERT_EQ(best_iface, dev);
333}
334} // namespace unittests
std::string to_string() const
Definition ipv4.hpp:232
static Addr from_string(const std::string &ipstr, const TITLE &title)
Definition ipv4.hpp:205
static Addr from_string(const std::string &ipstr, const TITLE &title)
Definition ipv6.hpp:104
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