compress

package
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 8, 2026 License: LGPL-2.1, LGPL-2.1-or-later Imports: 5 Imported by: 0

Documentation

Overview

Package compress implements OpenVPN's data-channel compression framing and LZO decompression.

Nothing is compressed on send: every payload goes out behind the uncompressed marker. A payload the peer LZO-compressed is decompressed; one it LZ4-compressed is refused with ErrCompressed.

Why the framing is load-bearing

A peer with a compression directive frames *every* data packet, whether or not it compressed that packet, and drops what it cannot parse. A client that sends an unframed packet to such a peer is silently dropped, and a client that strips the wrong number of bytes hands the tunnel an IP packet shifted by one — one that decrypts cleanly, passes every authentication check, and means nothing.

The four framings

There are four distinct shapes on the wire, and which one applies is decided by the directive *and* its COMP_F_SWAP flag rather than by the algorithm's name. All four were measured from real 2.4.12, 2.5.11 and 2.6.22 peers; see docker/COMPRESSION-VECTORS.md §6 and testdata/vectors.json.

directive           payload           wire
comp-lzo            not compressed    0xFA ‖ plain
comp-lzo            compressed        0x66 ‖ LZO(plain)
compress (bare)     not compressed    0xFB ‖ plain[1:] ‖ plain[0]
compress lz4        not compressed    0xFB ‖ plain[1:] ‖ plain[0]
compress lz4        compressed        0x69 ‖ LZ4(plain), swapped the same way
compress lz4-v2     not compressed    plain, unchanged
compress lz4-v2     compressed        0x50 0x01 ‖ LZ4(plain)
compress stub-v2    not compressed    plain, unchanged
comp-lzo no         not compressed    0xFA ‖ plain

The swap is the one a reading of the header cannot predict: the framing byte *replaces* the payload's first byte, which moves to the tail, so that the rest of the payload keeps its alignment. Prepending where OpenVPN swaps shifts every subsequent byte by one, which is the shape of corruption that looks like a decryption failure.

References

Read from the OpenVPN 2.4.12, 2.5.11 and 2.6.22 release tarballs the test rig pins (SHA-256 in docker/openvpn-server/versions.env):

  • src/openvpn/comp.h — the framing bytes and COMP_F_SWAP
  • src/openvpn/options.c, add_option() — which directive sets which flags
  • src/openvpn/lzo.c — lzo_compress(), lzo_decompress()
  • src/openvpn/compstub.c — stub_compress(), stubv2_decompress()
  • src/openvpn/comp-lz4.c — lz4_compress(), lz4v2_compress()
  • src/openvpn/comp.c — check_compression_settings_valid()

The LZO1X codec is github.com/buengese/go-lzo.

Index

Constants

This section is empty.

Variables

View Source
var ErrCompressed = errors.New("compress: peer sent a compressed payload and no codec is linked")

ErrCompressed reports a peer that compressed a payload with an algorithm this package cannot decompress. Codec.Unwrap returns a *CompressedPayloadError, which names the algorithm and satisfies errors.Is against this sentinel.

Functions

func PeerDeclaresFraming

func PeerDeclaresFraming(occ string) bool

PeerDeclaresFraming reports whether a peer's options string says it has a compression framework enabled.

The options string is OpenVPN's OCC block, exchanged inside the key-method-2 packet, and it is the only place a peer states its compression setting: 2.4, 2.5 and 2.6 all omit the directive from PUSH_REPLY entirely.

What it can answer is narrower than it looks, and was measured rather than assumed. OpenVPN writes the literal token "comp-lzo" whenever any framework is on, the v2 stub included: a server configured "compress stub-v2", with no comp-lzo directive anywhere, still advertises "comp-lzo" here, while a server with no compression advertises nothing. So this answers whether the peer expects a framing at all, and never which one — the profile answers that. A client that framed on its own profile against a peer that declared nothing would put a leading byte on every packet the peer discards: a handshake that completes over a tunnel that carries nothing.

Types

type AllowCompression

type AllowCompression int

AllowCompression is the --allow-compression policy, which 2.5 introduced and which decides whether a compressing algorithm may be enabled at all.

const (
	// AllowUnset is the directive being absent, which is OpenVPN's default
	// and places no restriction of its own.
	AllowUnset AllowCompression = iota
	// AllowNo is "allow-compression no": COMP_F_ALLOW_STUB_ONLY. A framing
	// stub stays legal; an algorithm that can compress is refused, which is
	// what check_compression_settings_valid() enforces.
	AllowNo
	// AllowAsym is "allow-compression asym": accept a compressed payload from
	// the peer, never send one, which this client does whatever the policy.
	AllowAsym
	// AllowYes is "allow-compression yes": compression permitted in both
	// directions. It is the only value under which a 2.5 or 2.6 peer will
	// compress on send at all.
	AllowYes
)

The --allow-compression values, from src/openvpn/options.c, add_option().

func ParseAllowCompression

func ParseAllowCompression(arg string) (AllowCompression, bool)

ParseAllowCompression maps the argument of an --allow-compression directive to a policy, reporting false for a value OpenVPN would refuse.

func (AllowCompression) String

func (a AllowCompression) String() string

String names the policy as the directive spells its argument.

type Codec

type Codec struct {
	// contains filtered or unexported fields
}

Codec is one session's compression: Wrap frames outgoing payloads for its mode, Unwrap strips the framing from incoming ones and decompresses those the peer LZO-compressed. It is safe for concurrent use.

func NewCodec

func NewCodec(m Mode, maxLen int) *Codec

NewCodec returns the codec for mode m. A decompressed payload may be at most maxLen bytes, the tunnel MTU.

func (*Codec) Decompressed

func (c *Codec) Decompressed() uint64

Decompressed returns how many payloads Unwrap has decompressed.

func (*Codec) Mode

func (c *Codec) Mode() Mode

Mode returns the mode the codec frames for.

func (*Codec) Unwrap

func (c *Codec) Unwrap(payload []byte) ([]byte, error)

Unwrap strips the codec's incoming framing from payload and returns the IP packet. An LZO payload decompresses to at most the codec's maxLen; a longer or malformed stream is an error.

A payload the peer compressed with LZ4 returns a *CompressedPayloadError, which errors.Is matches against ErrCompressed. A framing byte that belongs to no known state is an error too: OpenVPN drops such a packet.

func (*Codec) Wrap

func (c *Codec) Wrap(payload []byte) ([]byte, error)

Wrap applies the codec's outgoing framing to payload.

The payload is never compressed: the byte written is always the uncompressed marker for the framing in force. A peer accepts that marker in every mode, and OpenVPN's own compressors emit exactly this whenever compression would not shrink the packet.

For ModeNone, and for a v2 framing over a payload that needs no escaping, the payload is returned unchanged and unaliased-from; every other case returns a fresh slice.

type CompressedPayloadError

type CompressedPayloadError struct {
	// Mode is the session's compression mode.
	Mode Mode
	// Algorithm is OpenVPN's name for the codec the peer used: "lz4" or
	// "lz4v2".
	Algorithm string
	// Marker is the framing byte that announced the compression.
	Marker byte
	// Length is the size of the compressed payload that was refused.
	Length int
}

CompressedPayloadError is the error Codec.Unwrap returns for a payload compressed with an algorithm it cannot decompress. It carries the algorithm so that the caller can name it in a ClassUnsupported failure.

func (*CompressedPayloadError) Error

func (e *CompressedPayloadError) Error() string

Error describes the refused payload, naming the algorithm and the marker.

func (*CompressedPayloadError) Unwrap

func (e *CompressedPayloadError) Unwrap() error

Unwrap returns ErrCompressed so that errors.Is identifies the class of failure without the caller having to know the algorithm.

type Framing

type Framing int

Framing is the wire framing a mode uses, which is not the same question as which algorithm it names: COMP_F_SWAP is an option flag rather than a property of the codec, so two modes over one algorithm can differ on it.

const (
	// FramingNone adds nothing on send and strips nothing on receive.
	FramingNone Framing = iota
	// FramingV1 prepends one byte: the algorithm's compress marker, or
	// NO_COMPRESS_BYTE. Used by comp-lzo, and by the non-swapping stub that
	// "comp-lzo no" selects.
	FramingV1
	// FramingV1Swap writes one byte in place of the payload's first byte and
	// moves that byte to the tail. Used by bare "compress" and by
	// "compress lz4" — every mode carrying COMP_F_SWAP.
	FramingV1Swap
	// FramingV2 writes nothing at all for an uncompressed payload, and a
	// 0x50 indicator plus an algorithm byte otherwise. Used by
	// "compress lz4-v2" and "compress stub-v2".
	FramingV2
)

The four framings OpenVPN puts on a data-channel payload. The prepending and swapping forms are distinct: COMP_F_SWAP is set independently of the algorithm, so the two cannot be one framing.

func (Framing) String

func (f Framing) String() string

String names the framing as this package's documentation does.

type Mode

type Mode int

Mode is the compression setting in force on the data channel: an algorithm together with the option flags that decide its framing.

It is deliberately finer-grained than the algorithm name. Bare "compress" and "comp-lzo no" both select COMP_ALG_STUB and neither ever compresses, yet they put different bytes on the wire, because only the first carries COMP_F_SWAP — a flag the directive does not spell out.

const (
	// ModeNone is no compression framing at all: nothing is added on send
	// and nothing is stripped on receive. It is what a profile with no
	// compression directive gets, and what most servers push.
	ModeNone Mode = iota
	// ModeStub is bare "compress" (and "compress stub"): COMP_ALG_STUB with
	// COMP_F_SWAP.
	ModeStub
	// ModeStubNoSwap is "comp-lzo no": COMP_ALG_STUB with no flags, which is
	// the only spelling that reaches the non-swapping stub framing.
	ModeStubNoSwap
	// ModeLZO is "comp-lzo", "comp-lzo yes", "comp-lzo adaptive" and
	// "compress lzo": COMP_ALG_LZO, which never swaps —
	// lzo_compress_init() asserts !(flags & COMP_F_SWAP).
	ModeLZO
	// ModeLZ4 is "compress lz4": COMP_ALG_LZ4 with COMP_F_SWAP, which
	// lz4_compress_init() asserts is set.
	ModeLZ4
	// ModeLZ4v2 is "compress lz4-v2": COMP_ALGV2_LZ4, the v2 framing that
	// adds nothing unless it compressed.
	ModeLZ4v2
	// ModeStubV2 is "compress stub-v2": COMP_ALGV2_UNCOMPRESSED, the v2
	// framing with no codec behind it, which is a way of asking for no
	// framing at all.
	ModeStubV2
)

The compression modes this client can frame for. None of them compresses on send; ModeLZO, ModeLZ4 and ModeLZ4v2 differ from the stubs only in that a peer using them may compress.

func EffectiveMode

func EffectiveMode(profileMode, pushedMode Mode, allow AllowCompression) (Mode, error)

EffectiveMode returns the compression the data channel must use.

A pushed directive outranks the profile's, because the server names the framing it will actually apply and a client that keeps its own frames every packet wrong. A profile directive applies when the server pushes nothing, the usual case: OpenVPN 2.4's server does not push its compression setting at all, so a config saying comp-lzo and a server saying comp-lzo agree without a word about it on the wire.

"allow-compression no" refuses a compressing algorithm from *either* source: COMP_F_ALLOW_STUB_ONLY, which check_compression_settings_valid() fails the connection over rather than quietly downgrading. A framing stub survives. pushedMode is ModeNone when the server pushed nothing; "comp-lzo no" pushes ModeStubNoSwap, which is distinguishable from it.

func ModeForDirective

func ModeForDirective(keyword, arg string) (Mode, bool)

ModeForDirective maps a compression directive — keyword and optional argument — to the Mode whose framing it selects, reporting false for a keyword that is not a compression directive or an argument OpenVPN refuses.

This is the single place the directive-to-flags table lives: the profile parser, the PUSH_REPLY parser and ParseMode all go through it, because a mode the profile spells one way and the push spells another must still frame the same.

Reference: src/openvpn/options.c, add_option(), the "comp-lzo" and "compress" cases, verified against the 2.4.12 tarball the rig pins.

func ParseMode

func ParseMode(options string) Mode

ParseMode inspects a comma-separated option list — a PUSH_REPLY, or a single directive on its own — and returns the compression Mode it asks for.

The last compression directive wins, as it does in OpenVPN, where each option is applied in turn over the same options struct; a list with no compression directive yields ModeNone. Every mode that reaches the data channel reaches it through here, so a framing that is right for a Mode this parser never produces is not right for anything.

func (Mode) Algorithm

func (m Mode) Algorithm() string

Algorithm returns OpenVPN's own name for the algorithm behind the mode: "lzo", "stub", "stubv2", "lz4" or "lz4v2", matching the compress_alg.name field the capture recorded. ModeNone has none and returns "".

func (Mode) Compresses

func (m Mode) Compresses() bool

Compresses reports whether a peer in this mode may put a genuinely compressed payload on the wire. Only these modes are refused by "allow-compression no".

func (Mode) Decompresses

func (m Mode) Decompresses() bool

Decompresses reports whether a Codec decompresses what a peer in this mode compresses. Only ModeLZO does; ModeLZ4 and ModeLZ4v2 yield ErrCompressed.

func (Mode) Framing

func (m Mode) Framing() Framing

Framing returns the wire framing this mode uses.

func (Mode) Overhead

func (m Mode) Overhead() int

Overhead is the number of bytes this mode's framing adds to an uncompressed payload on send: 1 for the two one-byte framings, 0 for the rest. A v2 framing adds nothing unless the payload's first byte collides with the indicator, which no IP packet's does.

func (Mode) String

func (m Mode) String() string

String returns the directive that selects this mode, as a .ovpn file or a PUSH_REPLY spells it. It is the protocol's own vocabulary rather than a diagnostic one, which is why it can serve both.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL