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 (!buf_to_string(buf).starts_with(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 (!buf_to_string(buf).ends_with(",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 (!buf_to_string(buf).ends_with(",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 if (index == size - 1)
82 return get_csv(std::move(buf), PC_1, prefix);
83 return get_csv(std::move(buf), PC_2, prefix);
84}
85
86static std::string random_term(RandomAPI &prng)
87{
88 static const std::string rchrs = "012abcABC,\"\\";
89
90 std::string ret;
91 const int len = prng.randrange32(1, 16);
92 ret.reserve(len);
93 for (int i = 0; i < len; ++i)
94 ret += rchrs[prng.randrange32(static_cast<uint32_t>(rchrs.size()))];
95 return ret;
96}
97
99{
100 Option ret;
101 const int len = prng.randrange32(1, 4);
102 ret.reserve(len);
103 for (int i = 0; i < len; ++i)
104 ret.push_back(random_term(prng));
105 return ret;
106}
107
109{
110 static const int sizes[3] = {10, 100, 1000};
111
113 const int len = prng.randrange32(1, sizes[prng.randrange32(3)]);
114 ret.reserve(len);
115 for (int i = 0; i < len; ++i)
116 ret.push_back(random_opt(prng));
117 return ret;
118}
119
120static void test_roundtrip(const OptionList &opt_orig, const std::string &prefix)
121{
122 // first render to CSV
123 BufferAllocated buf(opt_orig.size() * 128, BufAllocFlags::GROW);
124 buf_append_string(buf, prefix + ',');
125 buf_append_string(buf, opt_orig.render_csv());
126
127 // parse back to OptionList and verify round trip
128 const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(buf, NO_PC, prefix), nullptr);
129 require_equal(opt_orig, opt, "TEST_ROUNDTRIP #1");
130
131 // fragment into multiple buffers using push-continuation
132 const PushContinuationFragment frag(buf, prefix);
133
134 // parse fragments separately and verify with original
135 OptionList new_opt;
136 for (size_t i = 0; i < frag.size(); ++i)
137 new_opt.parse_from_csv(get_csv_from_frag(*frag[i], i, frag.size(), prefix), nullptr);
138 require_equal(opt_orig, new_opt, "TEST_ROUNDTRIP #2");
139
140 // test client-side continuation parser
142 for (size_t i = 0; i < frag.size(); ++i)
143 {
144 const OptionList cli_opt = OptionList::parse_from_csv_static(get_csv(*frag[i], NO_PC, prefix), nullptr);
145 cc.add(cli_opt, nullptr);
146 ASSERT_TRUE(cc.partial());
147 ASSERT_EQ(cc.complete(), i == frag.size() - 1);
148 }
149
150 // remove client-side push-continuation directives before comparison
151 std::erase_if(cc, [](const Option &o)
152 { return !o.empty() && o.ref(0) == "push-continuation"; });
153 require_equal(opt_orig, cc, "TEST_ROUNDTRIP #3");
154
155 // defragment back to original form
156 BufferPtr defrag = PushContinuationFragment::defragment(frag, prefix);
157 require_equal(buf, *defrag, "TEST_ROUNDTRIP #4");
158}
159
160// test maximum fragment sizes and optionally generate
161// push-list for further testing
162static void test_prefix_fragment(const std::string &prefix)
163{
165 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");
166
167 // pack the buffers, so several reach the maximum
168 // fragment size of PushContinuationFragment::FRAGMENT_SIZE
169 for (int i = 0; i < 1000; ++i)
170 {
171 if (i % 100 == 0)
172 buf_append_string(buf, ",echo rogue-agent-neptune-" + std::to_string(i / 100));
173 buf_append_string(buf, ",echo test-" + std::to_string(i));
174 }
175
176 // fragment into multiple buffers using push-continuation
177 const PushContinuationFragment frag(buf, prefix);
178
179 // verify that no buffer exceeds PushContinuationFragment::FRAGMENT_SIZE
180 for (auto &e : frag)
181 {
182 // OPENVPN_LOG(e->size());
183 ASSERT_LE(e->size(), PushContinuationFragment::FRAGMENT_SIZE);
184 }
185
186 // we should have fragmented into 15 buffers
187 ASSERT_EQ(frag.size(), 15);
188
189 // defragment the buffer
190 BufferPtr defrag = PushContinuationFragment::defragment(frag, prefix);
191 const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(*defrag, NO_PC, prefix), nullptr);
192
193#if 0
194 // dump for inclusion in JSON push list
195 for (const auto &e : opt)
196 {
197 OPENVPN_LOG(" \"" << e.render(0) << "\",");
198 }
199#endif
200}
201
202// test roundtrip for random configurations
203static void test_prefix_random(const std::string &prefix)
204{
205 RandomAPI::Ptr prng(new MTRand);
206
207 // Note: this code runs ~100x slower with valgrind
208 const int n = 100;
209
210 for (int i = 0; i < n; ++i)
211 {
212 const OptionList opt = random_optionlist(*prng);
213 test_roundtrip(opt, prefix);
214 }
215}
216
217TEST(Continuation, TestRandomPushReply)
218{
219 test_prefix_random("PUSH_REPLY");
220}
221
222TEST(Continuation, TestRandomPushUpdate)
223{
224 test_prefix_random("PUSH_UPDATE");
225}
226
227TEST(Continuation, TestFragmentPushReply)
228{
229 test_prefix_fragment("PUSH_REPLY");
230}
231
232TEST(Continuation, TestFragmentPushUpdate)
233{
234 test_prefix_fragment("PUSH_UPDATE");
235}
236
237TEST(Continuation, PushUpdateAdd)
238{
240
241 auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
242 cc.add(orig_opts, nullptr);
243 cc.finalize(nullptr);
244
245 cc.reset_completion();
246
247 auto update = OptionList::parse_from_csv_static("dns,ifconfig", nullptr);
248 cc.add(update, nullptr, true);
249 cc.finalize(nullptr);
250
251 ASSERT_EQ(cc.size(), 5);
252}
253
254TEST(Continuation, PushUpdateAddUnsupported)
255{
257
258 auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
259 cc.add(orig_opts, nullptr);
260 cc.finalize(nullptr);
261
262 cc.reset_completion();
263
264 auto update = OptionList::parse_from_csv_static("my_unsupported_option,?e", nullptr);
265 JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
266 cc.finalize(nullptr);
267
268 update = OptionList::parse_from_csv_static("?f,?g", nullptr);
269 cc.add(update, nullptr, true);
270 cc.finalize(nullptr);
271
272 ASSERT_EQ(cc.size(), 5);
273}
274
275TEST(Continuation, PushUpdateRemove)
276{
278
279 auto update = OptionList::parse_from_csv_static("-my_unsupported_option", nullptr);
280 JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
281 cc.finalize(nullptr);
282 cc.reset_completion();
283
284 update = OptionList::parse_from_csv_static("-?my_unsupported_optional_option", nullptr);
285 cc.add(update, nullptr, true);
286 cc.finalize(nullptr);
287 cc.reset_completion();
288}
289
290TEST(Continuation, PushUpdateAddMultiple)
291{
293
294 // this adds 7 options
295 auto orig_opts = OptionList::parse_from_csv_static("a,b,c,route 0,ifconfig,f,dns", nullptr);
296 cc.add(orig_opts, nullptr);
297 cc.finalize(nullptr);
298
299 cc.reset_completion();
300
301 // after we should have 9 options
302 auto update = OptionList::parse_from_csv_static("route 1,route 2,-ifconfig,?bla,push-continuation 2", nullptr);
303 cc.add(update, nullptr, true);
304
305 // after we should have 10 options (9 + push-continuation)
306 update = OptionList::parse_from_csv_static("route 3,route 4,-dns", nullptr);
307 cc.add(update, nullptr, true);
308
309 cc.finalize(nullptr);
310
311 ASSERT_TRUE(cc.exists("f"));
312 ASSERT_FALSE(cc.exists("dns"));
313 ASSERT_FALSE(cc.exists("ifconfig"));
314 ASSERT_TRUE(cc.exists("bla"));
315
316 const auto &idx = cc.get_index_ptr("route");
317 ASSERT_EQ(idx->size(), 4);
318
319 ASSERT_EQ(cc.size(), 10);
320}
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1241
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1276
void set_size(const size_t size)
After an external method, operating on the array as a mutable unsigned char buffer,...
Definition buffer.hpp:1381
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:1260
static OptionList parse_from_csv_static(const std::string &str, Limits *lim)
Definition options.hpp:832
void parse_from_csv(const std::string &str, Limits *lim)
Definition options.hpp:878
static OptionList parse_from_csv_static_nomap(const std::string &str, Limits *lim)
Definition options.hpp:840
std::string render_csv() const
Definition options.hpp:1460
std::string render(const unsigned int flags) const
Definition options.hpp:1444
bool exists(const std::string &name) const
Definition options.hpp:1308
bool empty() const
Definition options.hpp:324
const std::string & ref(const size_t i) const
Definition options.hpp:346
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:146
#define OPENVPN_LOG(args)
constexpr BufferFlags GROW(1U<< 2)
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
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, TestRandomPushReply)
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