0
0
mirror of https://github.com/OpenVPN/openvpn3.git synced 2024-09-20 12:12:15 +02:00
openvpn3/openvpn/compress/compress.hpp
David Sommerseth 3fbe0a2701
Update copyrights
Signed-off-by: David Sommerseth <davids@openvpn.net>
2020-03-18 19:37:32 +01:00

549 lines
11 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-2020 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.
// If not, see <http://www.gnu.org/licenses/>.
// Base class and factory for compression/decompression objects.
// Currently we support LZO, Snappy, and LZ4 implementations.
#ifndef OPENVPN_COMPRESS_COMPRESS_H
#define OPENVPN_COMPRESS_COMPRESS_H
#include <openvpn/common/size.hpp>
#include <openvpn/common/exception.hpp>
#include <openvpn/common/rc.hpp>
#include <openvpn/common/likely.hpp>
#include <openvpn/buffer/buffer.hpp>
#include <openvpn/frame/frame.hpp>
#include <openvpn/log/sessionstats.hpp>
#define OPENVPN_LOG_COMPRESS(x)
#define OPENVPN_LOG_COMPRESS_VERBOSE(x)
#if defined(OPENVPN_DEBUG_COMPRESS)
#if OPENVPN_DEBUG_COMPRESS >= 1
#undef OPENVPN_LOG_COMPRESS
#define OPENVPN_LOG_COMPRESS(x) OPENVPN_LOG(x)
#endif
#if OPENVPN_DEBUG_COMPRESS >= 2
#undef OPENVPN_LOG_COMPRESS_VERBOSE
#define OPENVPN_LOG_COMPRESS_VERBOSE(x) OPENVPN_LOG(x)
#endif
#endif
namespace openvpn {
class Compress : public RC<thread_unsafe_refcount>
{
public:
typedef RCPtr<Compress> Ptr;
// Compressor name
virtual const char *name() const = 0;
// Compression method implemented by underlying compression class.
// hint should normally be true to compress the data. If hint is
// false, the data may be uncompressible or already compressed,
// so method shouldn't attempt compression.
virtual void compress(BufferAllocated& buf, const bool hint) = 0;
// Decompression method implemented by underlying compression class.
virtual void decompress(BufferAllocated& buf) = 0;
protected:
// magic numbers to indicate no compression
enum {
NO_COMPRESS = 0xFA,
NO_COMPRESS_SWAP = 0xFB, // for better alignment handling, replace this byte with last byte of packet
};
// Compress V2 constants
enum {
COMPRESS_V2_ESCAPE=0x50,
// Compression algs
OVPN_COMPv2_NONE=0,
OVPN_COMPv2_LZ4=1,
};
Compress(const Frame::Ptr& frame_arg,
const SessionStats::Ptr& stats_arg)
: frame(frame_arg), stats(stats_arg) {}
void error(BufferAllocated& buf)
{
stats->error(Error::COMPRESS_ERROR);
buf.reset_size();
}
void do_swap(Buffer& buf, unsigned char op)
{
if (buf.size())
{
buf.push_back(buf[0]);
buf[0] = op;
}
else
buf.push_back(op);
}
void do_unswap(Buffer& buf)
{
if (buf.size() >= 2)
{
const unsigned char first = buf.pop_back();
buf.push_front(first);
}
}
// Push a COMPRESS_V2 header byte (value).
// Pass value == 0 to omit push.
void v2_push(Buffer& buf, int value)
{
unsigned char uc = buf[0];
if (value == 0 && uc != COMPRESS_V2_ESCAPE)
return;
unsigned char *esc = buf.prepend_alloc(2);
esc[0] = COMPRESS_V2_ESCAPE;
esc[1] = value;
}
// Pull a COMPRESS_V2 header byte.
// Returns the compress op (> 0) on success.
// Returns 0 if no compress op.
int v2_pull(Buffer& buf)
{
unsigned char uc = buf[0];
if (uc != COMPRESS_V2_ESCAPE)
return 0;
uc = buf[1];
buf.advance(2);
return uc;
}
Frame::Ptr frame;
SessionStats::Ptr stats;
};
}
// include compressor implementations here
#include <openvpn/compress/compnull.hpp>
#include <openvpn/compress/compstub.hpp>
#ifndef NO_LZO
#include <openvpn/compress/lzoselect.hpp>
#endif
#ifdef HAVE_LZ4
#include <openvpn/compress/lz4.hpp>
#endif
#ifdef HAVE_SNAPPY
#include <openvpn/compress/snappy.hpp>
#endif
namespace openvpn {
class CompressContext
{
public:
enum Type {
NONE,
COMP_STUB, // generic compression stub
COMP_STUBv2, // generic compression stub using v2 protocol
ANY, // placeholder for any method on client, before server assigns it
ANY_LZO, // placeholder for LZO or LZO_STUB methods on client, before server assigns it
LZO,
LZO_SWAP,
LZO_STUB,
LZ4,
LZ4v2,
SNAPPY,
};
OPENVPN_SIMPLE_EXCEPTION(compressor_unavailable);
CompressContext() {}
CompressContext(const Type t, const bool asym)
: asym_(asym) // asym indicates asymmetrical compression where only downlink is compressed
{
if (!compressor_available(t))
throw compressor_unavailable();
type_ = t;
}
Type type() const { return type_; }
bool asym() const { return asym_; }
unsigned int extra_payload_bytes() const
{
switch (type_)
{
case NONE:
return 0;
case COMP_STUBv2:
case LZ4v2:
return 2; // worst case
default:
return 1;
}
}
Compress::Ptr new_compressor(const Frame::Ptr& frame, const SessionStats::Ptr& stats)
{
switch (type_)
{
case NONE:
return new CompressNull(frame, stats);
case ANY:
case ANY_LZO:
case LZO_STUB:
return new CompressStub(frame, stats, false);
case COMP_STUB:
return new CompressStub(frame, stats, true);
case COMP_STUBv2:
return new CompressStubV2(frame, stats);
#ifndef NO_LZO
case LZO:
return new CompressLZO(frame, stats, false, asym_);
case LZO_SWAP:
return new CompressLZO(frame, stats, true, asym_);
#endif
#ifdef HAVE_LZ4
case LZ4:
return new CompressLZ4(frame, stats, asym_);
case LZ4v2:
return new CompressLZ4v2(frame, stats, asym_);
#endif
#ifdef HAVE_SNAPPY
case SNAPPY:
return new CompressSnappy(frame, stats, asym_);
#endif
default:
throw compressor_unavailable();
}
}
static bool compressor_available(const Type t)
{
switch (t)
{
case NONE:
case ANY:
case ANY_LZO:
case LZO_STUB:
case COMP_STUB:
case COMP_STUBv2:
return true;
case LZO:
case LZO_SWAP:
#ifndef NO_LZO
return true;
#else
return false;
#endif
case LZ4:
#ifdef HAVE_LZ4
return true;
#else
return false;
#endif
case LZ4v2:
#ifdef HAVE_LZ4
return true;
#else
return false;
#endif
case SNAPPY:
#ifdef HAVE_SNAPPY
return true;
#else
return false;
#endif
default:
return false;
}
}
// On the client, used to tell server which compression methods we support.
// Includes compression V1 and V2 methods.
const char *peer_info_string() const
{
switch (type_)
{
#ifndef NO_LZO
case LZO:
return "IV_LZO=1\n";
case LZO_SWAP:
return "IV_LZO_SWAP=1\n";
#endif
#ifdef HAVE_LZ4
case LZ4:
return "IV_LZ4=1\n";
#endif
#ifdef HAVE_LZ4
case LZ4v2:
return "IV_LZ4v2=1\n";
#endif
#ifdef HAVE_SNAPPY
case SNAPPY:
return "IV_SNAPPY=1\n";
#endif
case LZO_STUB:
case COMP_STUB:
case COMP_STUBv2:
return
"IV_LZO_STUB=1\n"
"IV_COMP_STUB=1\n"
"IV_COMP_STUBv2=1\n"
;
case ANY:
return
#ifdef HAVE_SNAPPY
"IV_SNAPPY=1\n"
#endif
#ifndef NO_LZO
"IV_LZO=1\n"
"IV_LZO_SWAP=1\n"
#else
"IV_LZO_STUB=1\n"
#endif
#ifdef HAVE_LZ4
"IV_LZ4=1\n"
"IV_LZ4v2=1\n"
#endif
"IV_COMP_STUB=1\n"
"IV_COMP_STUBv2=1\n"
;
case ANY_LZO:
return
#ifndef NO_LZO
"IV_LZO=1\n"
"IV_LZO_SWAP=1\n"
#else
"IV_LZO_STUB=1\n"
#endif
"IV_COMP_STUB=1\n"
"IV_COMP_STUBv2=1\n"
;
default:
return nullptr;
}
}
// On the client, used to tell server which compression methods we support.
// Limited only to compression V1 methods.
const char *peer_info_string_v1() const
{
switch (type_)
{
#ifndef NO_LZO
case LZO:
return "IV_LZO=1\n";
case LZO_SWAP:
return "IV_LZO_SWAP=1\n";
#endif
#ifdef HAVE_LZ4
case LZ4:
return "IV_LZ4=1\n";
#endif
#ifdef HAVE_SNAPPY
case SNAPPY:
return "IV_SNAPPY=1\n";
#endif
case LZO_STUB:
case COMP_STUB:
return
"IV_LZO_STUB=1\n"
"IV_COMP_STUB=1\n"
;
case ANY:
return
#ifdef HAVE_SNAPPY
"IV_SNAPPY=1\n"
#endif
#ifndef NO_LZO
"IV_LZO=1\n"
"IV_LZO_SWAP=1\n"
#else
"IV_LZO_STUB=1\n"
#endif
#ifdef HAVE_LZ4
"IV_LZ4=1\n"
#endif
"IV_COMP_STUB=1\n"
;
case ANY_LZO:
return
#ifndef NO_LZO
"IV_LZO=1\n"
"IV_LZO_SWAP=1\n"
#else
"IV_LZO_STUB=1\n"
#endif
"IV_COMP_STUB=1\n"
;
default:
return nullptr;
}
}
const char *options_string() const
{
switch (type_)
{
case LZO:
case LZO_STUB:
case SNAPPY:
case LZ4:
case LZ4v2:
case LZO_SWAP:
case COMP_STUB:
case COMP_STUBv2:
case ANY:
case ANY_LZO:
return "comp-lzo";
default:
return nullptr;
}
}
const char *str() const
{
switch (type_)
{
case LZO:
return "LZO";
case LZO_SWAP:
return "LZO_SWAP";
case LZ4:
return "LZ4";
case LZ4v2:
return "LZ4v2";
case SNAPPY:
return "SNAPPY";
case LZO_STUB:
return "LZO_STUB";
case COMP_STUB:
return "COMP_STUB";
case COMP_STUBv2:
return "COMP_STUBv2";
case ANY:
return "ANY";
case ANY_LZO:
return "ANY_LZO";
default:
return "NONE";
}
}
/* This function returns a parseable string representation of the compress
* method. NOTE: returns nullptr if no mapping is possible */
const char *method_to_string() const
{
switch (type_)
{
case LZO:
return "lzo";
case LZO_SWAP:
return "lzo-swap";
case LZO_STUB:
return "lzo-stub";
case LZ4:
return "lz4";
case LZ4v2:
return "lz4v2";
case SNAPPY:
return "snappy";
case COMP_STUB:
return "stub";
case COMP_STUBv2:
return "stub-v2";
default:
return nullptr;
}
}
static Type parse_method(const std::string& method)
{
if (method == "stub-v2")
return COMP_STUBv2;
else if (method == "lz4-v2")
return LZ4v2;
else if (method == "lz4")
return LZ4;
else if (method == "lzo")
return LZO;
else if (method == "lzo-swap")
return LZO_SWAP;
else if (method == "lzo-stub")
return LZO_STUB;
else if (method == "snappy")
return SNAPPY;
else if (method == "stub")
return COMP_STUB;
else
return NONE;
}
static Type stub(const Type t)
{
switch (t)
{
case COMP_STUBv2:
case LZ4v2:
return COMP_STUBv2;
default:
return COMP_STUB;
}
}
/**
* Checks if the compression type is one of the available stub modes
*
* @param t The CompressContext::Type value
* @return Returns true if the type is one of the *_STUB{,v2} types,
* otherwise false.
*/
static bool is_any_stub(const Type t)
{
switch (t)
{
case LZO_STUB:
case COMP_STUB:
case COMP_STUBv2:
return true;
default:
return false;
}
}
static void init_static()
{
#ifndef NO_LZO
CompressLZO::init_static();
#endif
}
private:
Type type_ = NONE;
bool asym_ = false;
};
} // namespace openvpn
#endif // OPENVPN_COMPRESS_COMPRESS_H