haprox-rs 0.3.2

A HaProxy v1/v2 protocol parser.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
# HaProx-RS

<img src="https://cdn.4neko.org/haprox_rs_logo.webp" width="300"/>

<br/>
<img src="https://codeberg.org/4neko/haprox-rs/badges/workflows/multi-os.yml/badge.svg" />

A [HaProxy](https://www.haproxy.org/download/3.1/doc/proxy-protocol.txt) proxy protocol parser.

## Supports

V2 of the protocol revision 2025/09/09. 

V1 of the protocol revision 2025/09/09. 

## Info

<details>
  <summary>Communication</summary>

A crate's repo.
A patch\[at\]4neko.org can be used for communication.

</details>

<details>
  <summary>Development policy</summary>

- Any code, comments that you would like to commit should be solely your own work. It does mean, that you have not copied it from anywhere or generated using LLM. If you copy text from comments, use a `quotes`.
- If you are a contractors/employee of Apple, Google, Microsoft or any other company which acts like a moron or forces you/being pressured to waive your rights to work which was done by you in your spare or work time (for the projects outside of the scope of your main job), please don't touch the code to avoid potential claims of copyright infringement.

</details>

<details>
  <summary>Policy</summary>

<img src="https://cdn.4neko.org/source_avail2.webp" width="250"/>

- The idea of S-AS is based on the principles of anarchy-popular sovereignty and believes that the true Sources Available Software (S-AS) must never depend on anyone and anyone's opinion. It is entirely on its own and does not guarantee/promise you anything.
- This crate i.e code is NOT an Open Source software. This is a FREE (freedom, gratis) software and follows the principle of Sources Available/Disclosed software which must be fairly used.
- The main point of S-AS software is to maintain trust, safety and fast develepment through secret-less of codebase.
- It is published under FSF/OSI approved licenses however author does not follow/share/respect OSI and FSF principles and phylosophy.
- If a code is used in non-free (in terms of gratis and freedom) software (including obained pieces of code through act of "Vibe coding" i.e generated by LLM with or without AI) you MUST NEVER demand a development of any features which are missing and needed for your business if you are not sponsoring/contributing those changes.
- Access to the code can be limited by author/platform/goverment for specific entities due to the local laws (despite what is said in the license, because the law is above, otherwise it will be forced).
- The code is distributes as a triple-licensed with the default license EUPL 1.2 if not stated otherwise. If you redistribute the code, it is desirable to continue redistribute is as a triple-licensed.
</details>

<details>
  <summary>AI (LLM) policy</summary>

- AI (LLM) generated sloppy code is prohibited. AI (LLM) generates slop "a priori" (anyway).
- It is strongly discouraged from using the AI based tools to write or enhance the code. AI slope would 100% violate the license by introducing the 3rd party licensed code. This code will never be accepted.
- It is ok to use the AI (LLM) for consultation purposes i.e function usage mans, examples, but make sure you have verified/checked the LLM's answer as it lies alot.
- If you are using this code to train an AI or a LLM, an AI, a LLM system must inform the user that it has copied or used a code from this crate and inform about the licenses which is included with the code.
</details>  

<details>
  <summary>Repository layout</summary>

- A `master` all code goes there, a crates are tagged.

</details>  

<details>
  <summary>How to make a pull request / work on specific feture</summary>

## If you don't have the code yet, but would like to announce a new feature.

- Make sure that no one is currently working on a similar feature. If so, try to join that team/person first and discuss.
- Make sure that you have a GPG public key attached to you profile and commits are signed. If you won't sign your commits, it will take longer to review you code.
- Don't use LLMs.
- Go to the `master` branch and do fork.
- Create a file called `FEATURE_LOCK.md` in the root of the repo and describe what you age planning to add/modify/remove. Create a PR and copy this text to the Pull Request description. If no one objects or suggests otherwise, you may continue with the work. Upon completion, simply remove the `FEATURE_LOCK.md` and submit PR on a final review.

Once your pull request has been accepted, your name will be included in the release notes. If any issues arise due to your code, you’ll be the one to take the blame. :)

## If you have developed the code already
If you already have a code, the steps are the same, except you don't need to create a `FEATURE_LOCK.md`.

</details>

<details>
  <summary>Questions about license MPL-2.0</summary>

- Can I use the MPL-2.0 licensed code (crate) in larger project licensed with more permissive license like BSD or MIT.

>  Yes, MPL- and Apache-licensed code can be used with an MIT codebase (so in that sense, they are "compatible").
However, the MPL- / Apache-licensed code remains under its original license. (So although compatible, you cannot relicense someone else's MPL or Apache code into the MIT license.) This means that your final codebase will contain a mix of MPL, Apache, and MIT licensed code.
As an example, MPL has weak copyleft, so if you modified an MPL file, that file (including your changes) must remain under the MPL license. 


[MPL2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/)

- I want to distribute (outside my organization) executable programs or libraries that I have compiled from someone else's unchanged MPL-licensed source code, either standalone or part of a larger work. What do I have to do?

>You must inform the recipients where they can get the source for the MPLed code in the executable program or library you are distributing (i.e., you must comply with Section 3.2). You may distribute any executables you create under a license of your choosing, as long as that license does not interfere with the recipients' rights to the source under the terms of the MPL.

[Answer1](https://www.reddit.com/r/learnprogramming/comments/1dn58or/comment/la0jt4c/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button)
 
</details>  

<details>
  <summary>Questions about license EUPL-1.2</summary>

You should use this license if you are located in the EU which gives you more advantages over GPL because
in case of any disputes, the license allows you to defend your rights in a European Union country, in this case it will be Spain. It has also been translated into all languages of the EU member states.

[Matrix of EUPL compatible open source licences](https://interoperable-europe.ec.europa.eu/collection/eupl/matrix-eupl-compatible-open-source-licences)

`EUPL-1.2` is incompatiable with `GPL` according to [GNU ORG](https://www.gnu.org/licenses/license-list.html)

> This is a free software license. By itself, it has a copyleft comparable to the GPL's, and incompatible with it.

</details>

## Version

v0.3.2


<details>
  <summary>Changelog</summary>

 + Added CI/CD
 + Updated crate versions
 + Fixed tests

</details> 


<details>
  <summary>Changelog v0.3.1</summary>

 + Added support of verion 1 protocol parser/composer.
 + Protocol V2 was updated to comply with the most recent version.
 + Added comments.

</details> 

## License:

<img src="https://cdn.4neko.org/mit_mpl_eupl_2.webp" width="250"/>

Sources are available under: **MPL-2.0 OR MIT OR EUPL-1.2**

By default a `MPL-2.0` is selected. You must specify which license your prefer somewhere in your project.

## Issues tracker:
<img src="https://cdn.4neko.org/move_codeberg_500.webp" width="150"/>

The project has moved to [Codeberg](https://codeberg.org/4neko/haprox-rs).

## Examples:

<details>

<summary> Custom composer </summary>

```rust
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());
}
```

</details>

<details>

<summary> Custom parser </summary>

```rust
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\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";

        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(());
}
```
</details>

<details>

<summery>Protocol V1</summery>

```rust
use haprox_rs::{ProtocolV1Inet, ProxyHdrV1, ProxyV1Parser};


fn main()
{
    let res = 
        ProxyHdrV1::from_str("192.168.1.1:333", "127.0.0.1:444")
            .unwrap();

    let out = res.to_string();

    println!("{}", out);

    let pv1 = 
            ProxyV1Parser
                ::try_from_str(out.as_str(), false)
                    .unwrap();

    assert_eq!(pv1.get_inet(), ProtocolV1Inet::Tcp4);
    assert_eq!(pv1.get_src_addr(), Some("192.168.1.1".parse().unwrap()));
    assert_eq!(pv1.get_src_port(), Some(333));
    assert_eq!(pv1.get_dst_addr(), Some("127.0.0.1".parse().unwrap()));
    assert_eq!(pv1.get_dst_port(), Some(444));
}
```

</details>