moon-proxyproto

    Strict HAProxy PROXY protocol v1/v2 codec, streaming decoder, TLV parser, and CRC32C validator for MoonBit.

    Download zip
    Author
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    2 months ago
    Downloads
    39

    No dependencies

    #moon-proxyproto

    CI

    moon-proxyproto is a strict MoonBit codec for HAProxy PROXY Protocol v1 and v2. It is not an HTTP proxy, reverse proxy, VPN, or connection forwarder.

    The library decodes one PROXY header and returns the untouched application payload. It supports v1 TCP4/TCP6/UNKNOWN, v2 LOCAL/PROXY, IPv4, IPv6, UNIX address fields, STREAM and DGRAM transports, TLV boundary parsing, SSL TLV parsing, and Castagnoli CRC32C validation.

    Wire validation is deliberately strict: the v1 limit is 107 bytes including CRLF, PROXY v2 accepts only registered family/transport combinations, and LOCAL v2 consumes its declared body while ignoring and normalizing its address metadata.

    #Installation

    Add the published package from Mooncakes:

    moon add P7001/moon-proxyproto

    Import the library package in moon.pkg:

    import {
    "P7001/moon-proxyproto" @proxyproto,
    }

    #Security

    Only enable PROXY parsing on a connection accepted from a trusted proxy. A source address carried in a PROXY header is metadata, not an authenticated identity. The decoder never silently treats a malformed PROXY header as HTTP, TLS, SMTP, or another protocol. Configure an explicit policy at the trust boundary.

    #Protocol reference and provenance

    The wire behavior follows the HAProxy PROXY protocol v1/v2 specification. This repository is an independent MoonBit implementation based on the public specification; it does not copy or translate another language implementation. Generated protocol vectors are produced by the repository scripts and verified by SHA-256 metadata.

    #Examples

    let policy = DecodePolicy::v2_only()
    match decode_v2(input, policy) {
    Ok(frame) => println("payload bytes: \{frame.remaining.length()}")
    Err(error) => println(error.context)
    }

    For streams, use Decoder::new(policy) and call feed with each received chunk. For UDP, call decode_datagram; it never joins two datagrams.

    Executable examples are available under examples/decode_v1, decode_v2, preserve_payload, stream_chunks, tlv_inspect, http_preamble, and tls_preamble. Run one with, for example, moon run examples/decode_v1.

    #Command-line inspection

    proxyproto-tool provides decode-v1, decode-v2, decode-auto, encode-v1, encode-v2, inspect-tlv, verify-crc32c, split-test, and vector-check. Output is stable JSON. decode-auto is only an offline diagnostic command and must not be used as automatic protocol detection on a public listener.

    #Validation

    The local suite contains 156 named tests, 3888 chunk-split and truncation cases, 1000 deterministic v2 roundtrips, and 71 generated vectors. All 156 tests pass on wasm, wasm-gc, JavaScript, and native with warnings denied. Vector metadata is checked against actual MoonBit parser results, including precise failure kinds and payload boundaries. See docs/test-matrix.md, docs/protocol-matrix.md, and docs/security.md for the tested matrix and trust boundary.

    #Project status

    This repository contains the release candidate for moon-proxyproto 0.1.1.

    • GitHub repository: P7001/moon-proxyproto
    • Module namespace: P7001/moon-proxyproto
    • Current status: 0.1.1 final verification
    • Current version: 0.1.1
    • Published baseline: P7001/moon-proxyproto@0.1.0

    AddressBlock

    pub(all) enum AddressBlock {
    NoAddress
    Ipv4(Ipv4Endpoints)
    Ipv6(Ipv6Endpoints)
    UnixAddress(UnixEndpoints)
    RawUnsupported(Bytes)
    } derive(
    Debug
    )

    AddressFamily

    pub(all) enum AddressFamily {
    Unspec
    Inet
    Inet6
    Unix
    } derive(Eq,
    Debug
    )

    ByteCursor

    pub struct ByteCursor {
    input : Bytes
    position_value : Int
    } derive(
    Debug
    )

    ByteCursor::checkpoint

    fn ByteCursor::checkpoint(self : ByteCursor) -> Int

    ByteCursor::new

    fn ByteCursor::new(input : Bytes) -> ByteCursor

    ByteCursor::peek

    fn ByteCursor::peek(self : ByteCursor) -> Byte?

    ByteCursor::peek_n

    fn ByteCursor::peek_n(self : ByteCursor, count : Int) -> Bytes?

    ByteCursor::position

    fn ByteCursor::position(self : ByteCursor) -> Int

    ByteCursor::read_exact

    fn ByteCursor::read_exact(self : ByteCursor, count : Int) -> Bytes?

    ByteCursor::read_u16_be

    fn ByteCursor::read_u16_be(self : ByteCursor) -> Int?

    ByteCursor::read_u32_be

    fn ByteCursor::read_u32_be(self : ByteCursor) -> UInt?

    ByteCursor::read_u8

    fn ByteCursor::read_u8(self : ByteCursor) -> Byte?

    ByteCursor::remaining

    fn ByteCursor::remaining(self : ByteCursor) -> Int

    ByteCursor::restore

    fn ByteCursor::restore(self : ByteCursor, checkpoint : Int) -> Unit

    ByteCursor::skip

    fn ByteCursor::skip(self : ByteCursor, count : Int) -> Bool

    ChunkBuffer

    pub struct ChunkBuffer {
    chunks : Array[Bytes]
    first_chunk : Int
    first_offset : Int
    available_bytes : Int
    copied_bytes_value : Int
    } derive(
    Debug
    )

    A queue of immutable input chunks. Bytes are copied only by copy_exact.

    ChunkBuffer::append

    fn ChunkBuffer::append(self : ChunkBuffer, chunk : Bytes) -> Unit

    ChunkBuffer::available

    fn ChunkBuffer::available(self : ChunkBuffer) -> Int

    ChunkBuffer::chunk_count

    fn ChunkBuffer::chunk_count(self : ChunkBuffer) -> Int

    ChunkBuffer::clear

    fn ChunkBuffer::clear(self : ChunkBuffer) -> Unit

    ChunkBuffer::copied_bytes

    fn ChunkBuffer::copied_bytes(self : ChunkBuffer) -> Int

    ChunkBuffer::copy_exact

    fn ChunkBuffer::copy_exact(self : ChunkBuffer, length : Int) -> Bytes?

    ChunkBuffer::discard

    fn ChunkBuffer::discard(self : ChunkBuffer, length : Int) -> Bool

    ChunkBuffer::new

    ChunkBuffer::peek_byte

    fn ChunkBuffer::peek_byte(self : ChunkBuffer, offset : Int) -> Byte?

    Command

    pub(all) enum Command {
    Local
    Proxy
    } derive(Eq,
    Debug
    )

    DecodePolicy

    pub(all) struct DecodePolicy {
    expected_protocol : ExpectedProtocol
    max_v1_line_bytes : Int
    max_v2_payload_bytes : Int
    verify_crc32c : Bool
    require_crc32c : Bool
    allow_local_command : Bool
    allow_unspec : Bool
    preserve_unknown_tlvs : Bool
    } derive(
    Debug
    )

    DecodePolicy::strict

    fn DecodePolicy::strict() -> DecodePolicy

    DecodePolicy::v1_only

    fn DecodePolicy::v1_only() -> DecodePolicy

    DecodePolicy::v2_only

    fn DecodePolicy::v2_only() -> DecodePolicy

    DecodeProgress

    pub(all) enum DecodeProgress {
    NeedMore
    Done(DecodedFrame)
    Failed(ProxyError)
    } derive(
    Debug
    )

    DecodedFrame

    pub(all) struct DecodedFrame {
    header : ProxyHeader
    remaining : Bytes
    consumed : Int
    } derive(
    Debug
    )

    Decoder

    pub struct Decoder {
    policy : DecodePolicy
    buffer : ChunkBuffer
    state_value : DecoderState
    failure : ProxyError?
    } derive(
    Debug
    )

    Decoder::buffered_len

    fn Decoder::buffered_len(self : Decoder) -> Int

    Decoder::feed

    fn Decoder::feed(self : Decoder, chunk : Bytes) -> DecodeProgress

    Decoder::new

    fn Decoder::new(policy : DecodePolicy) -> Decoder

    Decoder::reset

    fn Decoder::reset(self : Decoder) -> Unit

    Decoder::state

    fn Decoder::state(self : Decoder) -> DecoderState

    DecoderState

    pub(all) enum DecoderState {
    AwaitingPrefix
    ReadingV1Line
    ReadingV2Fixed
    ReadingV2Body(Int)
    Done
    Failed
    } derive(Eq,
    Debug
    )

    ExpectedProtocol

    pub(all) enum ExpectedProtocol {
    V1Only
    V2Only
    V1OrV2
    } derive(Eq,
    Debug
    )

    GeneratedVector

    pub(all) struct GeneratedVector {
    name : String
    parser : String
    data : Bytes
    expected_success : Bool
    expected_error : String
    expected_consumed : Int
    expected_remaining_length : Int
    expected_family : String
    expected_transport : String
    }

    Ipv4Address

    pub(all) struct Ipv4Address {
    octets : FixedArray[Byte]
    } derive(
    Debug
    )

    Ipv4Address::new

    fn Ipv4Address::new(octets : FixedArray[Byte]) -> Result[Ipv4Address, ProxyError]

    Ipv4Endpoints

    pub(all) struct Ipv4Endpoints {
    source_address : Ipv4Address
    destination_address : Ipv4Address
    source_port : Int
    destination_port : Int
    } derive(
    Debug
    )

    Ipv6Address

    pub(all) struct Ipv6Address {
    octets : FixedArray[Byte]
    } derive(
    Debug
    )

    Ipv6Address::new

    fn Ipv6Address::new(octets : FixedArray[Byte]) -> Result[Ipv6Address, ProxyError]

    Ipv6Endpoints

    pub(all) struct Ipv6Endpoints {
    source_address : Ipv6Address
    destination_address : Ipv6Address
    source_port : Int
    destination_port : Int
    } derive(
    Debug
    )

    ParsedTlv

    pub(all) enum ParsedTlv {
    Alpn(Bytes)
    Authority(String)
    Crc32c(UInt)
    Noop(Bytes)
    UniqueId(Bytes)
    Ssl(SslInfo)
    Netns(String)
    Custom(Byte, Bytes)
    Experimental(Byte, Bytes)
    FutureReserved(Byte, Bytes)
    Unknown(Byte, Bytes)
    } derive(
    Debug
    )

    PropertyGenerator

    pub struct PropertyGenerator {
    state : UInt
    } derive(
    Debug
    )

    Deterministic xorshift32 generator for reproducible protocol tests.

    PropertyGenerator::new

    fn PropertyGenerator::new(seed : UInt) -> PropertyGenerator

    PropertyGenerator::next_byte

    fn PropertyGenerator::next_byte(self : PropertyGenerator) -> Byte

    PropertyGenerator::next_bytes

    fn PropertyGenerator::next_bytes(self : PropertyGenerator, length : Int) -> Bytes

    PropertyGenerator::next_ipv4

    PropertyGenerator::next_ipv6

    PropertyGenerator::next_payload

    fn PropertyGenerator::next_payload(self : PropertyGenerator) -> Bytes

    PropertyGenerator::next_port

    fn PropertyGenerator::next_port(self : PropertyGenerator) -> Int

    PropertyGenerator::next_u32

    fn PropertyGenerator::next_u32(self : PropertyGenerator) -> UInt

    PropertyGenerator::next_unknown_tlv

    fn PropertyGenerator::next_unknown_tlv(self : PropertyGenerator) -> RawTlv

    PropertyGenerator::next_v2_header

    fn PropertyGenerator::next_v2_header(self : PropertyGenerator) -> ProxyHeader

    ProxyError

    pub(all) struct ProxyError {
    kind : ProxyErrorKind
    offset : Int
    context : String
    } derive(
    Debug
    )

    ProxyErrorKind

    pub(all) enum ProxyErrorKind {
    NeedMoreData
    InvalidPrefix
    UnsupportedVersion
    InvalidCommand
    InvalidFamily
    InvalidTransport
    InvalidFamilyTransport
    InvalidV1Line
    V1LineTooLong
    MissingCrlf
    InvalidIpv4
    InvalidIpv6
    InvalidPort
    InvalidV2Signature
    InvalidLength
    HeaderTooLarge
    TruncatedAddress
    TruncatedTlv
    InvalidTlv
    DuplicateCrc32c
    MissingCrc32c
    Crc32cMismatch
    InvalidSslTlv
    PolicyViolation
    DecoderAlreadyFinished
    DecoderFailed
    } derive(Eq,
    Debug
    )

    ProxyHeader

    pub(all) struct ProxyHeader {
    version : ProxyVersion
    command : Command
    family : AddressFamily
    transport : Transport
    address : AddressBlock
    tlvs : Array[RawTlv]
    } derive(
    Debug
    )

    ProxyHeader::new

    fn ProxyHeader::new(version : ProxyVersion, command : Command, family : AddressFamily, transport : Transport, address : AddressBlock, tlvs : Array[RawTlv]) -> Result[ProxyHeader, ProxyError]

    ProxyVersion

    pub(all) enum ProxyVersion {
    V1
    V2
    } derive(Eq,
    Debug
    )

    RawTlv

    pub(all) struct RawTlv {
    type_code : Byte
    value : Bytes
    } derive(
    Debug
    )

    SslInfo

    pub(all) struct SslInfo {
    client_flags : Byte
    verify : UInt
    sub_tlvs : Array[RawTlv]
    } derive(
    Debug
    )

    SslTlv

    pub(all) struct SslTlv {
    client_flags : Byte
    verify : UInt
    sub_tlvs : Array[RawTlv]
    } derive(
    Debug
    )

    TlvRange

    pub(all) enum TlvRange {
    Registered
    CustomRange
    ExperimentalRange
    FutureReservedRange
    UnknownRange
    } derive(Eq,
    Debug
    )

    Transport

    pub(all) enum Transport {
    Unspec
    Stream
    Datagram
    } derive(Eq,
    Debug
    )

    UnixEndpoints

    pub(all) struct UnixEndpoints {
    source : Bytes
    destination : Bytes
    } derive(
    Debug
    )

    VectorMetadata

    pub(all) struct VectorMetadata {
    name : String
    category : String
    expected_success : Bool
    expected_error : String
    expected_consumed : Int
    expected_remaining_length : Int
    expected_family : String
    expected_transport : String
    } derive(
    Debug
    )

    GENERATED_VECTOR_COUNT

    let GENERATED_VECTOR_COUNT : Int

    TLV_ALPN

    let TLV_ALPN : Byte

    TLV_AUTHORITY

    let TLV_AUTHORITY : Byte

    TLV_CRC32C

    let TLV_CRC32C : Byte

    TLV_NETNS

    let TLV_NETNS : Byte

    TLV_NOOP

    let TLV_NOOP : Byte

    TLV_SSL

    let TLV_SSL : Byte

    TLV_SSL_CIPHER

    let TLV_SSL_CIPHER : Byte

    TLV_SSL_CLIENT_CERT

    let TLV_SSL_CLIENT_CERT : Byte

    TLV_SSL_CN

    let TLV_SSL_CN : Byte

    TLV_SSL_GROUP

    let TLV_SSL_GROUP : Byte

    TLV_SSL_KEY_ALG

    let TLV_SSL_KEY_ALG : Byte

    TLV_SSL_SIG_ALG

    let TLV_SSL_SIG_ALG : Byte

    TLV_SSL_SIG_SCHEME

    let TLV_SSL_SIG_SCHEME : Byte

    TLV_SSL_VERSION

    let TLV_SSL_VERSION : Byte

    TLV_UNIQUE_ID

    let TLV_UNIQUE_ID : Byte

    assert_all_prefix_truncations

    fn assert_all_prefix_truncations(header : Bytes, policy : DecodePolicy) -> Int

    assert_all_single_splits

    fn assert_all_single_splits(input : Bytes, policy : DecodePolicy) -> Int

    assert_bytes_eq

    fn assert_bytes_eq(actual : Bytes, expected : Bytes) -> Unit

    assert_err_kind

    fn assert_err_kind(result : Result[DecodedFrame, ProxyError], expected : ProxyErrorKind) -> Unit

    assert_header_eq

    fn assert_header_eq(actual : ProxyHeader, expected : ProxyHeader) -> Unit

    assert_ok

    fn assert_ok(result : Result[DecodedFrame, ProxyError]) -> DecodedFrame

    assert_remaining_eq

    fn assert_remaining_eq(frame : DecodedFrame, expected : Bytes) -> Unit

    assert_roundtrip_v1

    fn assert_roundtrip_v1(header : ProxyHeader) -> Unit

    assert_roundtrip_v2

    fn assert_roundtrip_v2(header : ProxyHeader) -> Unit

    assert_selected_three_way_splits

    fn assert_selected_three_way_splits(input : Bytes, cuts : Array[Int], policy : DecodePolicy) -> Int

    bytes_concat

    fn bytes_concat(left : Bytes, right : Bytes) -> Bytes

    bytes_equal_constant_time

    fn bytes_equal_constant_time(left : Bytes, right : Bytes) -> Bool

    bytes_slice

    fn bytes_slice(input : Bytes, start : Int, end : Int) -> Result[Bytes, ProxyError]

    classify_tlv_range

    fn classify_tlv_range(type_code : Byte) -> TlvRange

    count_single_split_cases

    fn count_single_split_cases(input : Bytes) -> Int

    count_three_way_cases

    fn count_three_way_cases(first_cuts : Array[Int], second_cuts : Array[Int]) -> Int

    crc32c

    fn crc32c(data : Bytes) -> UInt

    crc32c_reference

    fn crc32c_reference(data : Bytes) -> UInt

    decode_datagram

    fn decode_datagram(datagram : Bytes, policy : DecodePolicy) -> Result[DecodedFrame, ProxyError]

    decode_v1

    fn decode_v1(input : Bytes, policy : DecodePolicy) -> Result[DecodedFrame, ProxyError]

    decode_v2

    fn decode_v2(input : Bytes, policy : DecodePolicy) -> Result[DecodedFrame, ProxyError]

    encode

    fn encode(header : ProxyHeader) -> Result[Bytes, ProxyError]

    encode_ssl_tlv

    fn encode_ssl_tlv(ssl : SslTlv) -> Result[RawTlv, ProxyError]

    encode_tlvs

    fn encode_tlvs(tlvs : Array[RawTlv]) -> Result[Bytes, ProxyError]

    encode_v1

    fn encode_v1(header : ProxyHeader) -> Result[Bytes, ProxyError]

    encode_v2

    fn encode_v2(header : ProxyHeader) -> Result[Bytes, ProxyError]

    encode_with_payload

    fn encode_with_payload(header : ProxyHeader, payload : Bytes) -> Result[Bytes, ProxyError]

    feed_single_split

    fn feed_single_split(input : Bytes, split : Int, policy : DecodePolicy) -> DecodeProgress

    feed_three_way

    fn feed_three_way(input : Bytes, first_cut : Int, second_cut : Int, policy : DecodePolicy) -> DecodeProgress

    find_all_tlvs

    fn find_all_tlvs(tlvs : Array[RawTlv], type_code : Byte) -> Array[RawTlv]

    find_tlv

    fn find_tlv(tlvs : Array[RawTlv], type_code : Byte) -> RawTlv?

    format_ipv4

    fn format_ipv4(address : Ipv4Address) -> String

    format_ipv6

    fn format_ipv6(address : Ipv6Address) -> String

    generated_vectors

    fn generated_vectors() -> Array[GeneratedVector]

    header_semantic_eq

    fn header_semantic_eq(left : ProxyHeader, right : ProxyHeader) -> Bool

    hex_decode

    fn hex_decode(input : String) -> Result[Bytes, ProxyError]

    hex_encode

    fn hex_encode(input : Bytes) -> String

    insert_proxy_crc32c

    fn insert_proxy_crc32c(header_without_checksum : ProxyHeader) -> Result[Bytes, ProxyError]

    interpret_all_tlvs

    fn interpret_all_tlvs(tlvs : Array[RawTlv]) -> Result[Array[ParsedTlv], ProxyError]

    interpret_tlv

    fn interpret_tlv(tlv : RawTlv, offset? : Int) -> Result[ParsedTlv, ProxyError]

    parse_ipv4_text

    fn parse_ipv4_text(input : String) -> Result[Ipv4Address, ProxyError]

    parse_ipv6_text

    fn parse_ipv6_text(input : String) -> Result[Ipv6Address, ProxyError]

    parse_port_text

    fn parse_port_text(input : String) -> Result[Int, ProxyError]

    parse_ssl_tlv

    fn parse_ssl_tlv(value : Bytes, offset? : Int) -> Result[SslTlv, ProxyError]

    parse_tlvs

    fn parse_tlvs(input : Bytes, offset? : Int, preserve_unknown? : Bool) -> Result[Array[RawTlv], ProxyError]

    proxy_error

    fn proxy_error(kind : ProxyErrorKind, offset : Int, context : String) -> ProxyError

    read_u16_be

    fn read_u16_be(input : Bytes, offset : Int) -> Result[Int, ProxyError]

    read_u32_be

    fn read_u32_be(input : Bytes, offset : Int) -> Result[UInt, ProxyError]

    tlv_type_name

    fn tlv_type_name(type_code : Byte) -> String

    unix_address_field

    fn unix_address_field(input : Bytes) -> Result[Bytes, ProxyError]

    unix_address_text_view

    fn unix_address_text_view(raw : Bytes) -> String?

    v2_signature

    fn v2_signature() -> Bytes

    vector_metadata

    fn vector_metadata(name : String, category : String, expected_success : Bool, expected_error : String, expected_consumed : Int, expected_remaining_length : Int, expected_family : String, expected_transport : String) -> VectorMetadata

    verify_generated_vectors

    fn verify_generated_vectors() -> Result[Int, ProxyError]

    verify_proxy_crc32c

    fn verify_proxy_crc32c(complete_header : Bytes) -> Result[Unit, ProxyError]

    write_u16_be

    fn write_u16_be(value : Int) -> Bytes

    write_u32_be

    fn write_u32_be(value : UInt) -> Bytes