2 releases

Uses new Rust 2024

new 0.1.2 May 4, 2025
0.1.1 May 4, 2025

#2 in #ha-proxy

EUPL-1.2

125KB
2.5K SLoC

HaProx-RS

A HaProxy proxy protocol parser.

Supports

V2 of the protocol revision 3.1 2020/03/05.

V1 is not supported.

Example:

Custom composer:

use std::{fmt, io::Cursor};

use byteorder::{BigEndian, WriteBytesExt};
use haprox_rs::{common::map_io_err, protocol::PP2TlvDump, protocol_composer::{HdrV2OpProxy, ProxyHdrV2}, HaProxRes, PP2TlvClient, ProxyTransportFam, ProxyV2Addr};



#[derive(Clone, Debug)]
pub enum ProxyV2Dummy2 
{
    SomeTlvName(u32, u32),
}

impl fmt::Display for ProxyV2Dummy2
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "DUMMY external reader")
    }
}

impl PP2TlvDump for ProxyV2Dummy2
{
    fn get_type(&self) -> u8 
    {
        let Self::SomeTlvName(..) = self else { panic!("wrong") };

        return 0xE0;
    }

    fn dump(&self, cur: &mut Cursor<Vec<u8>>) -> HaProxRes<()> 
    {
        match self
        {
            Self::SomeTlvName(arg0, arg1) =>
            {
                cur.write_u32::<BigEndian>(*arg0).map_err(map_io_err)?;
                cur.write_u32::<BigEndian>(*arg1).map_err(map_io_err)?;
            }
        }

        return Ok(());
    }
}

fn main()
{
    let addr = ProxyV2Addr::try_from(("127.0.0.1:39754", "127.0.0.67:11883")).unwrap();
        
    let mut comp = 
        ProxyHdrV2::<HdrV2OpProxy>::new(ProxyTransportFam::STREAM, addr).unwrap();

    let plts = comp.set_plts();

    let mut ssl = plts.add_ssl(PP2TlvClient::PP2_CLIENT_SSL, 0).unwrap();

    ssl.add_ssl_sub_version("TLSv1.2").unwrap();
    
    let mut plts = ssl.done().unwrap();


    let cust_plt = ProxyV2Dummy2::SomeTlvName(0x01020304, 0x05060708);

    plts.add_tlv(cust_plt, Some(&[0xE0..=0xE0])).unwrap();

    drop(plts);

    let pkt: Vec<u8> = comp.try_into().unwrap();

    let ctrl = 
b"\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a\x21\x11\x00\x29\
\x7f\x00\x00\x01\x7f\x00\x00\x43\x9b\x4a\x2e\x6b\x20\x00\x0f\x01\
\x00\x00\x00\x00\x21\x00\x07\x54\x4c\x53\x76\x31\x2e\x32\xE0\x00\
\x08\x01\x02\x03\x04\x05\x06\x07\x08";

    assert_eq!(pkt.as_slice(), ctrl.as_slice());
}

Custom parser:

use std::{fmt, io::Cursor};

use byteorder::{BigEndian, ReadBytesExt};
use haprox_rs::{common, protocol::{PP2TlvDump, PP2TlvRestore}, ProxyV2Parser, return_error, HaProxRes, HdrV2Command, PP2TlvClient, PP2Tlvs, ProtocolVersion, ProxyTransportFam, ProxyV2Addr, ProxyV2AddrType, PP2_TYPE_MIN_CUSTOM};


#[derive(Clone, Debug)]
pub enum ProxyV2Dummy2 
{
    SomeTlvName(u32, u32),
    OtherTlv,
}

impl fmt::Display for ProxyV2Dummy2
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        write!(f, "DUMMY external reader")
    }
}

impl PP2TlvRestore for ProxyV2Dummy2
{
    fn restore(tlv_type: u8, cur: &mut Cursor<&[u8]>) -> HaProxRes<Self> where Self: Sized 
    {
        match tlv_type
        {
            0xE0 =>
            {
                let arg0 = cur.read_u32::<BigEndian>().map_err(common::map_io_err)?;
                let arg1 = cur.read_u32::<BigEndian>().map_err(common::map_io_err)?;

                return Ok(Self::SomeTlvName(arg0, arg1));
            },
            _ => 
                return_error!(ProtocolUnknownData, "unknown tlv_type: {}", tlv_type)
        }
        
    }
    
    fn is_in_range(tlv_type: u8, _tlv_parent_type: Option<u8>) -> bool 
    {
        return tlv_type == PP2_TYPE_MIN_CUSTOM;
    }
    
    fn contains_subtype(&self) -> bool 
    {
        return false;
    }
}

impl PP2TlvDump for ProxyV2Dummy2
{
    fn get_type(&self) -> u8 
    {
        let Self::SomeTlvName(..) = self else { panic!("wrong") };

        return 0xE0;
    }

    fn dump(&self, _cur: &mut Cursor<Vec<u8>>) -> HaProxRes<()> 
    {
        todo!()
    }
}


fn main() -> HaProxRes<()>
{

    let pkt_ssl = 
b"\x0d\x0a\x0d\x0a\x00\x0d\x0a\x51\x55\x49\x54\x0a\x21\x11\x00\x1e\
\x7f\x00\x00\x01\x7f\x00\x00\x43\x9b\x4a\x2e\x6b\x20\x00\x0f\x01\
\x00\x00\x00\x00\x21\x00\x07\x54\x4c\x53\x76\x31\x2e\x32\xE0\x00\
\x08\x01\x02\x03\x04\x05\x06\x07\x08";

        let dec = ProxyV2Parser::<ProxyV2Dummy2>::try_from_slice_custom(pkt_ssl.as_slice()).unwrap();

        assert_eq!(dec.get_transport().is_ok(), true);
        assert_eq!(dec.get_transport().unwrap(), ProxyTransportFam::STREAM);

        assert_eq!(dec.get_proto_version(), ProtocolVersion::V2);
        assert_eq!(dec.get_proto_command(), HdrV2Command::PROXY);

        assert_eq!(dec.get_address_family().is_ok(), true);
        assert_eq!(dec.get_address_family().unwrap(), ProxyV2AddrType::AfInet);
        
        let addr = dec.get_address().unwrap();

        assert_eq!(addr.is_some(), true);

        let addr = addr.unwrap();
        let maddr = ProxyV2Addr::try_from(("127.0.0.1:39754", "127.0.0.67:11883")).unwrap();

        assert_eq!(addr, maddr);

        let tlv_iter = dec.get_tlvs_iter();

        assert_eq!(tlv_iter.is_some(), true);

        let mut tlv_iter = tlv_iter.unwrap();

        let type_ssl = tlv_iter.next().unwrap().take_internal().unwrap();

        assert_eq!(type_ssl.get_type(), PP2Tlvs::TYPE_SSL);
        let PP2Tlvs::TypeSsl { client, verify } = type_ssl else {panic!("wrong")};

        assert_eq!(client, PP2TlvClient::PP2_CLIENT_SSL);
        assert_eq!(verify, 0);

        // --
        let type_ssl_version = tlv_iter.next().unwrap().take_internal().unwrap();

        assert_eq!(type_ssl_version.get_type(), PP2Tlvs::TYPE_SUBTYPE_SSL_VERSION);

        let PP2Tlvs::TypeSubtypeSslVersion(ssl_version) = type_ssl_version else { panic!("wrong") };

        assert_eq!(ssl_version, "TLSv1.2");

        // ---
        let ext_type_e0 = tlv_iter.next().unwrap().take_external().unwrap();

        assert_eq!(ext_type_e0.get_type(), 0xE0);

        let ProxyV2Dummy2::SomeTlvName(arg0, arg1) = ext_type_e0 else {panic!("wrong")};

        assert_eq!(arg0, 0x01020304);
        assert_eq!(arg1, 0x05060708);


    return Ok(());
}

Dependencies

~230–520KB