OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_continuation.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// #define OPENVPN_BUFFER_ABORT
14
15#include <algorithm>
16
17#include "test_common.hpp"
18
21
24
25using namespace openvpn;
26
27static void require_equal(const OptionList &opt1, const OptionList &opt2, const std::string &title)
28{
29 if (opt1 != opt2)
30 {
31 OPENVPN_LOG(title);
33 }
34}
35
36static void require_equal(const Buffer &buf1, const Buffer &buf2, const std::string &title)
37{
38 if (buf1 != buf2)
39 {
40 OPENVPN_LOG(title);
41 ASSERT_EQ(buf_to_string(buf1), buf_to_string(buf2));
42 }
43}
44
45// push continuation mode
47{
51};
52
53static std::string get_csv(Buffer buf, const PCMode pc_mode, const std::string &prefix)
54{
55 // verify PUSH_REPLY then remove it
56 if (!string::starts_with(buf, prefix + ','))
57 throw Exception("expected that buffer would begin with " + prefix);
58 buf.advance(prefix.length() + 1);
59
60 // possibly remove push-continuation options from tail of buffer
61 if (pc_mode == PC_1)
62 {
63 if (!string::ends_with(buf, ",push-continuation 1"))
64 throw Exception("expected that buffer would end with push-continuation 1");
65 buf.set_size(buf.size() - 20);
66 }
67 else if (pc_mode == PC_2)
68 {
69 if (!string::ends_with(buf, ",push-continuation 2"))
70 throw Exception("expected that buffer would end with push-continuation 2");
71 buf.set_size(buf.size() - 20);
72 }
73
74 return buf_to_string(buf);
75}
76
77static std::string get_csv_from_frag(Buffer buf, const size_t index, const size_t size, const std::string &prefix)
78{
79 if (size < 2)
80 return get_csv(std::move(buf), NO_PC, prefix);
81 else if (index == size - 1)
82 return get_csv(std::move(buf), PC_1, prefix);
83 else
84 return get_csv(std::move(buf), PC_2, prefix);
85}
86
87static std::string random_term(RandomAPI &prng)
88{
89 static const std::string rchrs = "012abcABC,\"\\";
90
91 std::string ret;
92 const int len = prng.randrange32(1, 16);
93 ret.reserve(len);
94 for (int i = 0; i < len; ++i)
95 ret += rchrs[prng.randrange32(static_cast<uint32_t>(rchrs.size()))];
96 return ret;
97}
98
100{
101 Option ret;
102 const int len = prng.randrange32(1, 4);
103 ret.reserve(len);
104 for (int i = 0; i < len; ++i)
105 ret.push_back(random_term(prng));
106 return ret;
107}
108
110{
111 static const int sizes[3] = {10, 100, 1000};
112
114 const int len = prng.randrange32(1, sizes[prng.randrange32(3)]);
115 ret.reserve(len);
116 for (int i = 0; i < len; ++i)
117 ret.push_back(random_opt(prng));
118 return ret;
119}
120
121static void test_roundtrip(const OptionList &opt_orig, const std::string &prefix)
122{
123 // first render to CSV
124 BufferAllocated buf(opt_orig.size() * 128, BufAllocFlags::GROW);
125 buf_append_string(buf, prefix + ',');
126 buf_append_string(buf, opt_orig.render_csv());
127
128 // parse back to OptionList and verify round trip
129 const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(buf, NO_PC, prefix), nullptr);
130 require_equal(opt_orig, opt, "TEST_ROUNDTRIP #1");
131
132 // fragment into multiple buffers using push-continuation
133 const PushContinuationFragment frag(buf, prefix);
134
135 // parse fragments separately and verify with original
136 OptionList new_opt;
137 for (size_t i = 0; i < frag.size(); ++i)
138 new_opt.parse_from_csv(get_csv_from_frag(*frag[i], i, frag.size(), prefix), nullptr);
139 require_equal(opt_orig, new_opt, "TEST_ROUNDTRIP #2");
140
141 // test client-side continuation parser
143 for (size_t i = 0; i < frag.size(); ++i)
144 {
145 const OptionList cli_opt = OptionList::parse_from_csv_static(get_csv(*frag[i], NO_PC, prefix), nullptr);
146 cc.add(cli_opt, nullptr);
147 ASSERT_TRUE(cc.partial());
148 ASSERT_EQ(cc.complete(), i == frag.size() - 1);
149 }
150
151 // remove client-side push-continuation directives before comparison
152 cc.erase(std::remove_if(cc.begin(), cc.end(), [](const Option &o)
153 { return o.size() >= 1 && o.ref(0) == "push-continuation"; }),
154 cc.end());
155 require_equal(opt_orig, cc, "TEST_ROUNDTRIP #3");
156
157 // defragment back to original form
158 BufferPtr defrag = PushContinuationFragment::defragment(frag, prefix);
159 require_equal(buf, *defrag, "TEST_ROUNDTRIP #4");
160}
161
162// test maximum fragment sizes and optionally generate
163// push-list for further testing
164static void test_prefix_fragment(const std::string &prefix)
165{
167 buf_append_string(buf, prefix + ",route-gateway 10.213.0.1,ifconfig 10.213.0.48 255.255.0.0,ifconfig-ipv6 fdab::48/64 fdab::1,client-ip 192.168.4.1,ping 1,ping-restart 8,reneg-sec 60,cipher AES-128-GCM,compress stub-v2,peer-id 4,topology subnet,explicit-exit-notify");
168
169 // pack the buffers, so several reach the maximum
170 // fragment size of PushContinuationFragment::FRAGMENT_SIZE
171 for (int i = 0; i < 1000; ++i)
172 {
173 if (i % 100 == 0)
174 buf_append_string(buf, ",echo rogue-agent-neptune-" + std::to_string(i / 100));
175 buf_append_string(buf, ",echo test-" + std::to_string(i));
176 }
177
178 // fragment into multiple buffers using push-continuation
179 const PushContinuationFragment frag(buf, prefix);
180
181 // verify that no buffer exceeds PushContinuationFragment::FRAGMENT_SIZE
182 for (auto &e : frag)
183 {
184 // OPENVPN_LOG(e->size());
185 ASSERT_LE(e->size(), PushContinuationFragment::FRAGMENT_SIZE);
186 }
187
188 // we should have fragmented into 15 buffers
189 ASSERT_EQ(frag.size(), 15);
190
191 // defragment the buffer
192 BufferPtr defrag = PushContinuationFragment::defragment(frag, prefix);
193 const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(*defrag, NO_PC, prefix), nullptr);
194
195#if 0
196 // dump for inclusion in JSON push list
197 for (const auto &e : opt)
198 {
199 OPENVPN_LOG(" \"" << e.render(0) << "\",");
200 }
201#endif
202}
203
204// test roundtrip for random configurations
205static void test_prefix_random(const std::string &prefix)
206{
207 RandomAPI::Ptr prng(new MTRand);
208
209 // Note: this code runs ~100x slower with valgrind
210 const int n = 100;
211
212 for (int i = 0; i < n; ++i)
213 {
214 const OptionList opt = random_optionlist(*prng);
215 test_roundtrip(opt, prefix);
216 }
217}
218
219TEST(continuation, test_random_push_reply)
220{
221 test_prefix_random("PUSH_REPLY");
222}
223
224TEST(continuation, test_random_push_update)
225{
226 test_prefix_random("PUSH_UPDATE");
227}
228
229TEST(continuation, test_fragment_push_reply)
230{
231 test_prefix_fragment("PUSH_REPLY");
232}
233
234TEST(continuation, test_fragment_push_update)
235{
236 test_prefix_fragment("PUSH_UPDATE");
237}
238
239TEST(continuation, push_update_add)
240{
242
243 auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
244 cc.add(orig_opts, nullptr);
245 cc.finalize(nullptr);
246
247 cc.reset_completion();
248
249 auto update = OptionList::parse_from_csv_static("dns,ifconfig", nullptr);
250 cc.add(update, nullptr, true);
251 cc.finalize(nullptr);
252
253 ASSERT_EQ(cc.size(), 5);
254}
255
256TEST(continuation, push_update_add_unsupported)
257{
259
260 auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
261 cc.add(orig_opts, nullptr);
262 cc.finalize(nullptr);
263
264 cc.reset_completion();
265
266 auto update = OptionList::parse_from_csv_static("my_unsupported_option,?e", nullptr);
267 JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
268 cc.finalize(nullptr);
269
270 update = OptionList::parse_from_csv_static("?f,?g", nullptr);
271 cc.add(update, nullptr, true);
272 cc.finalize(nullptr);
273
274 ASSERT_EQ(cc.size(), 5);
275}
276
277TEST(continuation, push_update_remove)
278{
280
281 auto update = OptionList::parse_from_csv_static("-my_unsupported_option", nullptr);
282 JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
283 cc.finalize(nullptr);
284 cc.reset_completion();
285
286 update = OptionList::parse_from_csv_static("-?my_unsupported_optional_option", nullptr);
287 cc.add(update, nullptr, true);
288 cc.finalize(nullptr);
289 cc.reset_completion();
290}
291
292TEST(continuation, push_update_add_multiple)
293{
295
296 // this adds 7 options
297 auto orig_opts = OptionList::parse_from_csv_static("a,b,c,route 0,ifconfig,f,dns", nullptr);
298 cc.add(orig_opts, nullptr);
299 cc.finalize(nullptr);
300
301 cc.reset_completion();
302
303 // after we should have 9 options
304 auto update = OptionList::parse_from_csv_static("route 1,route 2,-ifconfig,?bla,push-continuation 2", nullptr);
305 cc.add(update, nullptr, true);
306
307 // after we should have 10 options (9 + push-continuation)
308 update = OptionList::parse_from_csv_static("route 3,route 4,-dns", nullptr);
309 cc.add(update, nullptr, true);
310
311 cc.finalize(nullptr);
312
313 ASSERT_TRUE(cc.exists("f"));
314 ASSERT_FALSE(cc.exists("dns"));
315 ASSERT_FALSE(cc.exists("ifconfig"));
316 ASSERT_TRUE(cc.exists("bla"));
317
318 const auto &idx = cc.get_index_ptr("route");
319 ASSERT_EQ(idx->size(), 4);
320
321 ASSERT_EQ(cc.size(), 10);
322}
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1242
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1277
void set_size(const size_t size)
After an external method, operating on the array as a mutable unsigned char buffer,...
Definition buffer.hpp:1384
void reset_completion()
Resets completion flag. Intended to use by PUSH_UPDATE.
void finalize(const PushOptionsMerger::Ptr merger)
void add(const OptionList &other, OptionList::FilterBase *filt, bool push_update=false)
const IndexList * get_index_ptr(const std::string &name) const
Definition options.hpp:1276
static OptionList parse_from_csv_static(const std::string &str, Limits *lim)
Definition options.hpp:844
void parse_from_csv(const std::string &str, Limits *lim)
Definition options.hpp:890
static OptionList parse_from_csv_static_nomap(const std::string &str, Limits *lim)
Definition options.hpp:852
std::string render_csv() const
Definition options.hpp:1481
std::string render(const unsigned int flags) const
Definition options.hpp:1465
bool exists(const std::string &name) const
Definition options.hpp:1325
static BufferPtr defragment(const std::vector< BufferPtr > &bv, const std::string &prefix)
Abstract base class for random number generators.
Definition randapi.hpp:39
std::uint32_t randrange32(const std::uint32_t end)
Return a uniformly distributed random number in the range [0, end)
Definition randapi.hpp:147
#define OPENVPN_LOG(args)
constexpr BufferFlags GROW(1u<< 2)
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
bool starts_with(const STRING &str, const std::string &prefix)
Definition string.hpp:79
bool ends_with(const STRING &str, const std::string &suffix)
Definition string.hpp:111
void buf_append_string(Buffer &buf, const std::string &str)
Definition bufstr.hpp:82
std::string buf_to_string(const Buffer &buf)
Definition bufstr.hpp:22
std::string ret
static void test_roundtrip(const OptionList &opt_orig, const std::string &prefix)
static std::string get_csv_from_frag(Buffer buf, const size_t index, const size_t size, const std::string &prefix)
static std::string get_csv(Buffer buf, const PCMode pc_mode, const std::string &prefix)
static Option random_opt(RandomAPI &prng)
static void test_prefix_fragment(const std::string &prefix)
TEST(continuation, test_random_push_reply)
static void require_equal(const OptionList &opt1, const OptionList &opt2, const std::string &title)
static OptionList random_optionlist(RandomAPI &prng)
static std::string random_term(RandomAPI &prng)
static void test_prefix_random(const std::string &prefix)
#define JY_EXPECT_THROW