0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00
openvpn3/test/unittests/test_continuation.cpp
Charlie Vigue ef8da98bd4 Buffer: Prepare to decouple allocated buffer / RC
Rename BufferAllocated --> BufferAllocatedRc

Buffer: split RC from BufferAllocated
Also make changes as needed where BufferAllocated is used

Buffer: Split allocation flags into own struct
Leaving flags in template causes each alias to have identical flags
by different names, which requires each type to pointlessly use
the nested name.

Make RC: Clean up headers buffer.hpp, make_rc.hpp

Signed-off-by: Charlie Vigue <charlie.vigue@openvpn.com>
2024-09-11 13:23:28 +00:00

311 lines
9.5 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-2022 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.
// #define OPENVPN_BUFFER_ABORT
#include <algorithm>
#include "test_common.h"
#include <openvpn/common/options.hpp>
#include <openvpn/random/mtrandapi.hpp>
#include <openvpn/options/continuation_fragment.hpp>
#include <openvpn/options/continuation.hpp>
using namespace openvpn;
static void require_equal(const OptionList &opt1, const OptionList &opt2, const std::string &title)
{
if (opt1 != opt2)
{
OPENVPN_LOG(title);
ASSERT_EQ(opt1.render(Option::RENDER_BRACKET), opt2.render(Option::RENDER_BRACKET));
}
}
static void require_equal(const Buffer &buf1, const Buffer &buf2, const std::string &title)
{
if (buf1 != buf2)
{
OPENVPN_LOG(title);
ASSERT_EQ(buf_to_string(buf1), buf_to_string(buf2));
}
}
// push continuation mode
enum PCMode
{
NO_PC,
PC_1,
PC_2,
};
static std::string get_csv(Buffer buf, const PCMode pc_mode)
{
// verify PUSH_REPLY then remove it
if (!string::starts_with(buf, "PUSH_REPLY,"))
throw Exception("expected that buffer would begin with PUSH_REPLY");
buf.advance(11);
// possibly remove push-continuation options from tail of buffer
if (pc_mode == PC_1)
{
if (!string::ends_with(buf, ",push-continuation 1"))
throw Exception("expected that buffer would end with push-continuation 1");
buf.set_size(buf.size() - 20);
}
else if (pc_mode == PC_2)
{
if (!string::ends_with(buf, ",push-continuation 2"))
throw Exception("expected that buffer would end with push-continuation 2");
buf.set_size(buf.size() - 20);
}
return buf_to_string(buf);
}
static std::string get_csv_from_frag(Buffer buf, const size_t index, const size_t size)
{
if (size < 2)
return get_csv(buf, NO_PC);
else if (index == size - 1)
return get_csv(buf, PC_1);
else
return get_csv(buf, PC_2);
}
static std::string random_term(RandomAPI &prng)
{
static const std::string rchrs = "012abcABC,\"\\";
std::string ret;
const int len = prng.randrange32(1, 16);
ret.reserve(len);
for (int i = 0; i < len; ++i)
ret += rchrs[prng.randrange32(static_cast<uint32_t>(rchrs.size()))];
return ret;
}
static Option random_opt(RandomAPI &prng)
{
Option ret;
const int len = prng.randrange32(1, 4);
ret.reserve(len);
for (int i = 0; i < len; ++i)
ret.push_back(random_term(prng));
return ret;
}
static OptionList random_optionlist(RandomAPI &prng)
{
static const int sizes[3] = {10, 100, 1000};
OptionList ret;
const int len = prng.randrange32(1, sizes[prng.randrange32(3)]);
ret.reserve(len);
for (int i = 0; i < len; ++i)
ret.push_back(random_opt(prng));
return ret;
}
static void test_roundtrip(const OptionList &opt_orig)
{
// first render to CSV
BufferAllocated buf(opt_orig.size() * 128, BufAllocFlags::GROW);
buf_append_string(buf, "PUSH_REPLY,");
buf_append_string(buf, opt_orig.render_csv());
// parse back to OptionList and verify round trip
const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(buf, NO_PC), nullptr);
require_equal(opt_orig, opt, "TEST_ROUNDTRIP #1");
// fragment into multiple buffers using push-continuation
const PushContinuationFragment frag(buf);
// parse fragments separately and verify with original
OptionList new_opt;
for (size_t i = 0; i < frag.size(); ++i)
new_opt.parse_from_csv(get_csv_from_frag(*frag[i], i, frag.size()), nullptr);
require_equal(opt_orig, new_opt, "TEST_ROUNDTRIP #2");
// test client-side continuation parser
OptionListContinuation cc;
for (size_t i = 0; i < frag.size(); ++i)
{
const OptionList cli_opt = OptionList::parse_from_csv_static(get_csv(*frag[i], NO_PC), nullptr);
cc.add(cli_opt, nullptr);
ASSERT_TRUE(cc.partial());
ASSERT_EQ(cc.complete(), i == frag.size() - 1);
}
// remove client-side push-continuation directives before comparison
cc.erase(std::remove_if(cc.begin(), cc.end(), [](const Option &o)
{ return o.size() >= 1 && o.ref(0) == "push-continuation"; }),
cc.end());
require_equal(opt_orig, cc, "TEST_ROUNDTRIP #3");
// defragment back to original form
BufferPtr defrag = PushContinuationFragment::defragment(frag);
require_equal(buf, *defrag, "TEST_ROUNDTRIP #4");
}
// test roundtrip for random configurations
TEST(continuation, test1)
{
RandomAPI::Ptr prng(new MTRand);
// Note: this code runs ~100x slower with valgrind
const int n = 100;
for (int i = 0; i < n; ++i)
{
const OptionList opt = random_optionlist(*prng);
test_roundtrip(opt);
}
}
// test maximum fragment sizes and optionally generate
// push-list for further testing
TEST(continuation, test2)
{
BufferAllocated buf(65536, BufAllocFlags::GROW);
buf_append_string(buf, "PUSH_REPLY,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");
// pack the buffers, so several reach the maximum
// fragment size of PushContinuationFragment::FRAGMENT_SIZE
for (int i = 0; i < 1000; ++i)
{
if (i % 100 == 0)
buf_append_string(buf, ",echo rogue-agent-neptune-" + std::to_string(i / 100));
buf_append_string(buf, ",echo test-" + std::to_string(i));
}
// fragment into multiple buffers using push-continuation
const PushContinuationFragment frag(buf);
int count = 0;
for (auto &e : frag)
{
// OPENVPN_LOG(e->size());
if (e->size() == PushContinuationFragment::FRAGMENT_SIZE)
++count;
}
// 8 buffers should have reached maximum size
ASSERT_EQ(count, 8);
// defragment the buffer
BufferPtr defrag = PushContinuationFragment::defragment(frag);
const OptionList opt = OptionList::parse_from_csv_static_nomap(get_csv(*defrag, NO_PC), nullptr);
#if 0
// dump for inclusion in JSON push list
for (const auto &e : opt)
{
OPENVPN_LOG(" \"" << e.render(0) << "\",");
}
#endif
}
TEST(continuation, push_update_add)
{
OptionListContinuation cc;
auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
cc.add(orig_opts, nullptr);
cc.finalize(nullptr);
cc.reset_completion();
auto update = OptionList::parse_from_csv_static("dns,ifconfig", nullptr);
cc.add(update, nullptr, true);
cc.finalize(nullptr);
ASSERT_EQ(cc.size(), 5);
}
TEST(continuation, push_update_add_unsupported)
{
OptionListContinuation cc;
auto orig_opts = OptionList::parse_from_csv_static("a,b,c", nullptr);
cc.add(orig_opts, nullptr);
cc.finalize(nullptr);
cc.reset_completion();
auto update = OptionList::parse_from_csv_static("my_unsupported_option,?e", nullptr);
JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
cc.finalize(nullptr);
update = OptionList::parse_from_csv_static("?f,?g", nullptr);
cc.add(update, nullptr, true);
cc.finalize(nullptr);
ASSERT_EQ(cc.size(), 5);
}
TEST(continuation, push_update_remove)
{
OptionListContinuation cc;
auto update = OptionList::parse_from_csv_static("-my_unsupported_option", nullptr);
JY_EXPECT_THROW(cc.add(update, nullptr, true), OptionListContinuation::push_update_unsupported_option, "my_unsupported_option");
cc.finalize(nullptr);
cc.reset_completion();
update = OptionList::parse_from_csv_static("-?my_unsupported_optional_option", nullptr);
cc.add(update, nullptr, true);
cc.finalize(nullptr);
cc.reset_completion();
}
TEST(continuation, push_update_add_multiple)
{
OptionListContinuation cc;
// this adds 7 options
auto orig_opts = OptionList::parse_from_csv_static("a,b,c,route 0,ifconfig,f,dns", nullptr);
cc.add(orig_opts, nullptr);
cc.finalize(nullptr);
cc.reset_completion();
// after we should have 9 options
auto update = OptionList::parse_from_csv_static("route 1,route 2,-ifconfig,?bla,push-continuation 2", nullptr);
cc.add(update, nullptr, true);
// after we should have 10 options (9 + push-continuation)
update = OptionList::parse_from_csv_static("route 3,route 4,-dns", nullptr);
cc.add(update, nullptr, true);
cc.finalize(nullptr);
ASSERT_TRUE(cc.exists("f"));
ASSERT_FALSE(cc.exists("dns"));
ASSERT_FALSE(cc.exists("ifconfig"));
ASSERT_TRUE(cc.exists("bla"));
const auto &idx = cc.get_index_ptr("route");
ASSERT_EQ(idx->size(), 4);
ASSERT_EQ(cc.size(), 10);
}