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:
parent
7fe39be0df
commit
36f8122389
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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>>();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user