0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 04:02:15 +02:00

PacketStream: templatize stream segment length word

PacketStream was originally used in the OpenVPN protocol
to segment a TCP stream into packets.  Then we realized
it could perform the same function for the DNS protocol.
Now there are other protocols of interest (such as the
Vici protocol in Strongswan) that also use stream
segmentation, but use a different word size for the
stream length as represented on the wire protocol.
OpenVPN and DNS use a 16 bit word size, while Vici uses
a 32 bit word size.  Both use network-endian encoding
of the word size.

So this patch makes the stream length word size a
template parameter.

Signed-off-by: James Yonan <james@openvpn.net>
This commit is contained in:
James Yonan 2023-09-03 16:50:13 -06:00
parent 7fe39be0df
commit 36f8122389
3 changed files with 166 additions and 32 deletions

View File

@ -26,14 +26,21 @@
#include <limits>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/socktypes.hpp>
#include <openvpn/common/size.hpp>
#include <openvpn/common/numeric_cast.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/frame/frame.hpp>
namespace openvpn {
// Used to encapsulate OpenVPN or DNS packets onto a stream transport such as TCP,
// or extract them from the stream.
// Used to encapsulate OpenVPN, DNS, or other protocols onto a
// stream transport such as TCP, or extract them from the stream.
// SIZE_TYPE indicates the size of the length word, and should be
// a uint16_t for OpenVPN and DNS protocols, but may be uint32_t
// for other procotols. In all cases, the length word is represented
// by network-endian ordering.
template <typename SIZE_TYPE>
class PacketStream
{
private:
@ -122,17 +129,54 @@ class PacketStream
throw packet_not_fully_formed();
}
// prepend uint16_t size to buffer
// TODO: static_cast below may be unsafe, verify best fix
// this method is provided for prototype compatibility
// with PacketStreamResidual
void get(BufferAllocated &ret, const Frame::Context &frame_context)
{
get(ret);
}
// prepend SIZE_TYPE size to buffer
static void prepend_size(Buffer &buf)
{
const std::uint16_t net_len = htons(static_cast<uint16_t>(buf.size()));
SIZE_TYPE net_len;
host_to_network(net_len, buf.size());
buf.prepend((const unsigned char *)&net_len, sizeof(net_len));
}
// reset the object to default-initialized state
void reset()
{
declared_size = SIZE_UNDEF;
buffer.clear();
}
#ifndef UNIT_TEST
private:
#endif
// specialized methods for ntohl, ntohs, htonl, htons
static size_t network_to_host(const std::uint16_t value)
{
return ntohs(value);
}
static size_t network_to_host(const std::uint32_t value)
{
return ntohl(value);
}
static void host_to_network(std::uint16_t &result, const size_t value)
{
result = htons(numeric_cast<std::uint16_t>(value));
}
static void host_to_network(std::uint32_t &result, const size_t value)
{
result = htonl(numeric_cast<std::uint32_t>(value));
}
bool declared_size_defined() const
{
return declared_size != SIZE_UNDEF;
@ -147,25 +191,77 @@ class PacketStream
static bool size_defined(const Buffer &buf)
{
return buf.size() >= sizeof(std::uint16_t);
return buf.size() >= sizeof(SIZE_TYPE);
}
static size_t read_size(Buffer &buf)
{
std::uint16_t net_len;
SIZE_TYPE net_len;
buf.read((unsigned char *)&net_len, sizeof(net_len));
return ntohs(net_len);
return network_to_host(net_len);
}
static void validate_size(const size_t size, const Frame::Context &frame_context)
{
// Don't validate upper bound on size if BufferAllocated::GROW is set,
// allowing it to range up to 64kb.
// allowing it to range up to larger sizes.
if (!size || (!(frame_context.buffer_flags() & BufferAllocated::GROW) && size > frame_context.payload()))
throw embedded_packet_size_error();
}
size_t declared_size = SIZE_UNDEF; // declared size of packet in leading uint16_t prefix
size_t declared_size = SIZE_UNDEF; // declared size of packet in leading SIZE_TYPE prefix
BufferAllocated buffer; // accumulated packet data
};
// In this variant of PacketStreamResidual, put()
// will absorb all residual data in buf, so that
// buf is always returned empty.
template <typename SIZE_TYPE>
class PacketStreamResidual
{
public:
void put(BufferAllocated &buf, const Frame::Context &frame_context)
{
if (residual.empty())
{
pktstream.put(buf, frame_context);
residual = std::move(buf);
}
else
{
residual.append(buf);
pktstream.put(residual, frame_context);
}
buf.reset_content();
}
void get(BufferAllocated &ret, const Frame::Context &frame_context)
{
pktstream.get(ret);
if (!residual.empty())
pktstream.put(residual, frame_context);
}
bool ready() const
{
return pktstream.ready();
}
static void prepend_size(Buffer &buf)
{
PacketStream<SIZE_TYPE>::prepend_size(buf);
}
// reset the object to default-initialized state
void reset()
{
pktstream.reset();
residual.clear();
}
private:
PacketStream<SIZE_TYPE> pktstream;
BufferAllocated residual;
};
} // namespace openvpn

View File

@ -50,6 +50,7 @@ class LinkCommon : public LinkBase
public:
typedef RCPtr<LinkCommon<Protocol, ReadHandler, RAW_MODE_ONLY>> Ptr;
typedef Protocol protocol;
typedef PacketStream<std::uint16_t> OpenVPNPacketStream;
// In raw mode, data is sent and received without any special encapsulation.
// In non-raw mode, data is packetized by prepending a 16-bit length word
@ -158,7 +159,7 @@ class LinkCommon : public LinkBase
buf->swap(b);
if (!is_raw_mode_write())
{
PacketStream::prepend_size(*buf);
OpenVPNPacketStream::prepend_size(*buf);
}
if (mutate)
{
@ -459,7 +460,7 @@ class LinkCommon : public LinkBase
const size_t free_list_max_size;
Queue queue; // send queue
Queue free_list; // recycled free buffers for send queue
PacketStream pktstream;
OpenVPNPacketStream pktstream;
TransportMutateStream::Ptr mutate;
bool raw_mode_read;
bool raw_mode_write;

View File

@ -35,19 +35,23 @@ static size_t rand_size(RandomAPI &prng)
return prng.randrange32(1, 512);
}
TEST(pktstream, test_1)
template <typename PKTSTREAM>
static void do_test(const bool grow, const bool verbose)
{
#ifdef HAVE_VALGRIND
const int n_iter = 1000;
const int n_iter = 500;
#else
const int n_iter = 1000000;
const int n_iter = 250000;
#endif
const Frame::Context fc(256, 512, 256, 0, sizeof(size_t), 0);
const Frame::Context fc_big(256, 4096, 256, 0, sizeof(size_t), 0);
const Frame::Context fc(256, 512, 256, 0, sizeof(size_t), grow ? BufferAllocated::GROW : 0);
const Frame::Context fc_big(256, 4096, 256, 0, sizeof(size_t), grow ? BufferAllocated::GROW : 0);
if (verbose)
{
OPENVPN_LOG("FC " << fc.info());
OPENVPN_LOG("FC BIG " << fc_big.info());
}
MTRand::Ptr prng(new MTRand());
@ -68,7 +72,7 @@ TEST(pktstream, test_1)
const size_t r = rand_size(*prng);
for (size_t i = 0; i < r; ++i)
src.push_back('a' + static_cast<unsigned char>(i % 26));
PacketStream::prepend_size(src);
PKTSTREAM::prepend_size(src);
if (src.size() > fc_big.remaining_payload(big))
break;
big.write(src.data(), src.size());
@ -85,7 +89,7 @@ TEST(pktstream, test_1)
size_t ncmp = 0;
{
PacketStream pktstream;
PKTSTREAM pktstream;
BufferAllocated in;
while (big.size())
{
@ -97,10 +101,10 @@ TEST(pktstream, test_1)
while (in.size())
{
pktstream.put(in, fc);
if (pktstream.ready())
while (pktstream.ready())
{
pktstream.get(out);
PacketStream::prepend_size(out);
pktstream.get(out, fc);
PKTSTREAM::prepend_size(out);
bigcmp.write(out.data(), out.size());
++ncmp;
}
@ -116,17 +120,39 @@ TEST(pktstream, test_1)
ASSERT_EQ(bigorig, bigcmp);
}
if (verbose)
OPENVPN_LOG("count=" << count);
}
TEST(pktstream, test_16)
{
do_test<PacketStream<std::uint16_t>>(false, false);
}
TEST(pktstream, test_32)
{
do_test<PacketStream<std::uint32_t>>(false, false);
}
TEST(pktstream, test_16_residual)
{
do_test<PacketStreamResidual<std::uint16_t>>(true, false);
}
TEST(pktstream, test_32_residual)
{
do_test<PacketStreamResidual<std::uint32_t>>(true, false);
}
template <typename PKTSTREAM>
static void validate_size(const Frame::Context &fc, const size_t size, const bool expect_throw)
{
bool actual_throw = false;
try
{
PacketStream::validate_size(size, fc);
PKTSTREAM::validate_size(size, fc);
}
catch (PacketStream::embedded_packet_size_error &)
catch (typename PKTSTREAM::embedded_packet_size_error &)
{
actual_throw = true;
}
@ -138,7 +164,8 @@ static void validate_size(const Frame::Context &fc, const size_t size, const boo
size);
}
TEST(pktstream, test_2)
template <typename PKTSTREAM>
static void validate_size_test()
{
const size_t payload = 2048;
const size_t headroom = 16;
@ -146,8 +173,18 @@ TEST(pktstream, test_2)
const size_t align_block = 16;
const Frame::Context fixed(headroom, payload, tailroom, 0, align_block, 0);
const Frame::Context grow(headroom, payload, tailroom, 0, align_block, BufferAllocated::GROW);
validate_size(fixed, 2048, false); // succeeds
validate_size(fixed, 2049, true); // exceeded payload, throw
validate_size(grow, 2048, false); // succeeds
validate_size(grow, 2049, false); // exceeded payload, but okay with growable buffer
validate_size<PKTSTREAM>(fixed, 2048, false); // succeeds
validate_size<PKTSTREAM>(fixed, 2049, true); // exceeded payload, throw
validate_size<PKTSTREAM>(grow, 2048, false); // succeeds
validate_size<PKTSTREAM>(grow, 2049, false); // exceeded payload, but okay with growable buffer
}
TEST(pktstream, validate_size_16)
{
validate_size_test<PacketStream<std::uint16_t>>();
}
TEST(pktstream, validate_size_32)
{
validate_size_test<PacketStream<std::uint32_t>>();
}