Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
fa6c2ad
Clippy fixes
g2p Sep 22, 2021
f701d20
Fix mojibake in the protocol specification
g2p Sep 27, 2021
93cd650
Refactor version2 parsing
g2p Sep 22, 2021
de308f6
Add support for TLV extensions
g2p Sep 27, 2021
65cb8c2
Merge pull request #1 from g2p/tlv
pigri Oct 22, 2025
2900ffd
Fix EOF panics
junderw Sep 4, 2024
c6a4b3b
feat: add ProxyAddresses::from_socket_addrs helper
timvisee Nov 19, 2021
d1abed8
Merge branch 'timvisee-main'
pigri Oct 22, 2025
949db2c
fix: add missing closing brace in parse function to ensure proper syntax
pigri Oct 22, 2025
cd1eb7f
chore: update dependencies and refactor error handling
pigri Oct 22, 2025
ac28b27
Merge pull request #4 from arxignis/refactor
pigri Oct 22, 2025
a2344ad
Update Dependabot configuration for cargo and daily updates
pigri Mar 12, 2026
be8c10b
chore(deps): update snafu requirement from ~0.8 to ~0.9
dependabot[bot] Mar 12, 2026
4b77eef
Add GitHub Actions to Dependabot configuration
pigri Mar 12, 2026
4f7e754
chore(deps): bump actions/cache from 2 to 5
dependabot[bot] Mar 12, 2026
1276d5d
chore(deps): bump actions/checkout from 2 to 6
dependabot[bot] Mar 12, 2026
1d00dc9
Merge pull request #8 from gen0sec/dependabot/github_actions/actions/…
pigri Mar 12, 2026
c148008
Merge pull request #7 from gen0sec/dependabot/github_actions/actions/…
pigri Mar 12, 2026
0537302
Merge branch 'main' into dependabot/cargo/snafu-approx-0.9
pigri Mar 12, 2026
09af5e1
chore(deps): update rand to version 0.10 and add cargo fmt and test c…
pigri Mar 12, 2026
1a1000b
Refactor error handling in parsing functions for improved readability…
pigri Mar 12, 2026
048869b
Refactor error handling in `parse` function for improved readability
pigri Mar 12, 2026
7a24748
Merge pull request #6 from gen0sec/dependabot/cargo/snafu-approx-0.9
pigri Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
- package-ecosystem: "cargo" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
- package-ecosystem: "github-actions" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "daily"
6 changes: 4 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,24 @@ jobs:
name: Build, test, check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v6
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
default: true
components: rustfmt, clippy
- name: Cache target
uses: actions/cache@v2
uses: actions/cache@v5
with:
path: |
~/.cargo/git/
~/.cargo/registry/
target/
key: ${{ runner.os }}-proxy-protocol-${{ hashFiles('**/Cargo.toml') }}
restore-keys: ${{ runner.os }}-proxy-protocol
- run: cargo fmt --check
- run: cargo test -- --include-ignored
- run: cargo build
- uses: actions-rs/cargo@v1
with:
Expand Down
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ documentation = "https://docs.rs/proxy-protocol/"
repository = "https://github.com/Proximyst/proxy-protocol.git"

[dependencies]
snafu = "~0.6"
snafu = "~0.9"
bytes = "~1"

[dev-dependencies]
pretty_assertions = "^0.7"
rand = "~0.8"
pretty_assertions = "^1.4"
rand = "~0.10"

[features]
default = []
Expand Down
2 changes: 1 addition & 1 deletion proxy-protocol.txt
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Revision history
reserved TLV type ranges, added TLV documentation, clarified
string encoding. With contributions from Andriy Palamarchuk
(Amazon.com).
2020/03/05 - added the unique ID TLV type (Tim Düsterhus)
2020/03/05 - added the unique ID TLV type (Tim Düsterhus)


1. Background
Expand Down
82 changes: 52 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ pub enum EncodeError {
/// An error occurred while encoding version 1.
#[snafu(display("there was an error while encoding the v1 header: {}", source))]
WriteVersion1 { source: version1::EncodeError },
/// An error occurred while encoding version 2.
#[snafu(display("there was an error while encoding the v2 header: {}", source))]
WriteVersion2 { source: version2::EncodeError },
}

/// The PROXY header emitted at most once at the start of a new connection.
Expand Down Expand Up @@ -70,12 +73,13 @@ pub enum ProxyHeader {

/// The addresses used to connect to the proxy.
addresses: version2::ProxyAddresses,
extensions: Vec<version2::ExtensionTlv>,
},
}

fn parse_version(buf: &mut impl Buf) -> Result<u32, ParseError> {
// There is a 6 byte header to v1, 12 byte to all binary versions.
ensure!(buf.remaining() >= 6, NotProxyHeader);
ensure!(buf.remaining() >= 6, NotProxyHeaderSnafu);

// V1 is the only version that starts with "PROXY" (0x50 0x52 0x4F 0x58
// 0x59), and we can therefore decide version based on that.
Expand All @@ -87,11 +91,11 @@ fn parse_version(buf: &mut impl Buf) -> Result<u32, ParseError> {
}

// Now we require 13: 12 for the prefix, 1 for the version + command
ensure!(buf.remaining() >= 13, NotProxyHeader);
ensure!(buf.remaining() >= 13, NotProxyHeaderSnafu);
ensure!(
buf.chunk()[..12]
== [0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A],
NotProxyHeader
NotProxyHeaderSnafu
);
buf.advance(12);

Expand All @@ -113,7 +117,7 @@ fn parse_version(buf: &mut impl Buf) -> Result<u32, ParseError> {

// Interesting edge-case! This is the only time version 1 would be invalid.
if version == 1 {
return InvalidVersion { version: 1u32 }.fail();
return InvalidVersionSnafu { version: 1u32 }.fail();
}

Ok(version as u32)
Expand All @@ -125,15 +129,12 @@ fn parse_version(buf: &mut impl Buf) -> Result<u32, ParseError> {
/// available through [Buf::chunk], at the very least for the header. Data that
/// follows may be chunked as you wish.
pub fn parse(buf: &mut impl Buf) -> Result<ProxyHeader, ParseError> {
let version = match parse_version(buf) {
Ok(ver) => ver,
Err(e) => return Err(e),
};
let version = parse_version(buf)?;

Ok(match version {
1 => self::version1::parse(buf).context(Version1)?,
2 => self::version2::parse(buf).context(Version2)?,
_ => return InvalidVersion { version }.fail(),
1 => self::version1::parse(buf).context(Version1Snafu)?,
2 => self::version2::parse(buf).context(Version2Snafu)?,
_ => return InvalidVersionSnafu { version }.fail(),
})
}

Expand All @@ -144,13 +145,15 @@ pub fn parse(buf: &mut impl Buf) -> Result<ProxyHeader, ParseError> {
pub fn encode(header: ProxyHeader) -> Result<BytesMut, EncodeError> {
Ok(match header {
ProxyHeader::Version1 { addresses, .. } => {
version1::encode(addresses).context(WriteVersion1)?
version1::encode(addresses).context(WriteVersion1Snafu)?
}
ProxyHeader::Version2 {
command,
transport_protocol,
addresses,
} => version2::encode(command, transport_protocol, addresses),
extensions,
} => version2::encode(command, transport_protocol, addresses, &extensions[..])
.context(WriteVersion2Snafu)?,

#[allow(unreachable_patterns)] // May be required to be exhaustive.
_ => unimplemented!("Unimplemented version?"),
Expand Down Expand Up @@ -182,7 +185,7 @@ mod parse_tests {
);

let mut random = [0u8; 128];
rand::thread_rng().fill_bytes(&mut random);
rand::rng().fill_bytes(&mut random);
let mut header = b"PROXY UNKNOWN ".to_vec();
header.extend(&random[..]);
header.extend(b"\r\n");
Expand All @@ -191,15 +194,15 @@ mod parse_tests {
assert!(!buf.has_remaining()); // Consume the ENTIRE header!

fn valid_v4(
(a, b, c, d): (u8, u8, u8, u8),
e: u16,
(f, g, h, i): (u8, u8, u8, u8),
j: u16,
(s0, s1, s2, s3): (u8, u8, u8, u8),
sp: u16,
(d0, d1, d2, d3): (u8, u8, u8, u8),
dp: u16,
) -> ProxyHeader {
ProxyHeader::Version1 {
addresses: version1::ProxyAddresses::Ipv4 {
source: SocketAddrV4::new(Ipv4Addr::new(a, b, c, d), e),
destination: SocketAddrV4::new(Ipv4Addr::new(f, g, h, i), j),
source: SocketAddrV4::new(Ipv4Addr::new(s0, s1, s2, s3), sp),
destination: SocketAddrV4::new(Ipv4Addr::new(d0, d1, d2, d3), dp),
},
}
}
Expand All @@ -223,15 +226,25 @@ mod parse_tests {
);

fn valid_v6(
(a, b, c, d, e, f, g, h): (u16, u16, u16, u16, u16, u16, u16, u16),
i: u16,
(j, k, l, m, n, o, p, q): (u16, u16, u16, u16, u16, u16, u16, u16),
r: u16,
(s0, s1, s2, s3, s4, s5, s6, s7): (u16, u16, u16, u16, u16, u16, u16, u16),
sp: u16,
(d0, d1, d2, d3, d4, d5, d6, d7): (u16, u16, u16, u16, u16, u16, u16, u16),
dp: u16,
) -> ProxyHeader {
ProxyHeader::Version1 {
addresses: version1::ProxyAddresses::Ipv6 {
source: SocketAddrV6::new(Ipv6Addr::new(a, b, c, d, e, f, g, h), i, 0, 0),
destination: SocketAddrV6::new(Ipv6Addr::new(j, k, l, m, n, o, p, q), r, 0, 0),
source: SocketAddrV6::new(
Ipv6Addr::new(s0, s1, s2, s3, s4, s5, s6, s7),
sp,
0,
0,
),
destination: SocketAddrV6::new(
Ipv6Addr::new(d0, d1, d2, d3, d4, d5, d6, d7),
dp,
0,
0,
),
},
}
}
Expand Down Expand Up @@ -313,7 +326,7 @@ mod parse_tests {
0x49,
0x54,
0x0A,
(2 << 4) | 0,
2 << 4,
];
const PREFIX_PROXY: [u8; 13] = [
0x0D,
Expand All @@ -337,6 +350,7 @@ mod parse_tests {
command: version2::ProxyCommand::Local,
addresses: version2::ProxyAddresses::Unspec,
transport_protocol: version2::ProxyTransportProtocol::Unspec,
extensions: Vec::new(),
}),
);
assert_eq!(
Expand All @@ -345,6 +359,7 @@ mod parse_tests {
command: version2::ProxyCommand::Proxy,
addresses: version2::ProxyAddresses::Unspec,
transport_protocol: version2::ProxyTransportProtocol::Unspec,
extensions: Vec::new(),
}),
);

Expand Down Expand Up @@ -378,7 +393,7 @@ mod parse_tests {
1,
1,
// TLV
69,
version2::PP2_TYPE_NOOP,
0,
0,
][..]
Expand All @@ -393,6 +408,7 @@ mod parse_tests {
source: SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 65535),
destination: SocketAddrV4::new(Ipv4Addr::new(192, 168, 0, 1), 257),
},
extensions: Vec::new(),
})
);

Expand Down Expand Up @@ -439,6 +455,7 @@ mod parse_tests {
source: SocketAddrV4::new(Ipv4Addr::new(0, 0, 0, 0), 0),
destination: SocketAddrV4::new(Ipv4Addr::new(255, 255, 255, 255), 255 << 8),
},
extensions: Vec::new(),
})
);
assert!(data.remaining() == 4); // Consume the entire header
Expand Down Expand Up @@ -497,7 +514,7 @@ mod parse_tests {
1,
1,
// TLV
69,
version2::PP2_TYPE_NOOP,
0,
0,
][..],
Expand All @@ -522,6 +539,7 @@ mod parse_tests {
0,
)
},
extensions: Vec::new(),
})
);

Expand Down Expand Up @@ -602,12 +620,13 @@ mod parse_tests {
0,
),
},
extensions: Vec::new(),
})
);
assert!(data.remaining() == 4); // Consume the entire header

let mut data = [0u8; 200];
rand::thread_rng().fill_bytes(&mut data);
rand::rng().fill_bytes(&mut data);
data[0] = 99; // Make 100% sure it's invalid.
assert!(parse(&mut &data[..]).is_err());

Expand All @@ -629,6 +648,9 @@ mod parse_tests {
// 3 bytes is clearly too few if we expect 2 IPv4s and ports
0,
3,
0,
0,
0,
][..],
]
.concat()
Expand Down
Loading