Skip to content

feat: incoming proxy protocol v2 support on listener port#15

Merged
hUwUtao merged 2 commits into
mainfrom
feat/proxy-protocol
Jun 6, 2026
Merged

feat: incoming proxy protocol v2 support on listener port#15
hUwUtao merged 2 commits into
mainfrom
feat/proxy-protocol

Conversation

@hUwUtao

@hUwUtao hUwUtao commented Jun 6, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added optional HAProxy PROXY protocol v2 support for improved compatibility with reverse proxy environments (configurable via proxy_protocol setting)
  • Improvements

    • Enhanced connection logging with clearer error diagnostics for proxy protocol failures
    • Strengthened internal packet handling consistency and type validation

@coderabbitai

coderabbitai Bot commented Jun 6, 2026

Copy link
Copy Markdown
Contributor

Lost in the diff? Review this PR in Change Stack to follow the change map from intent to exact ranges.

Review Change Stack

Warning

Review limit reached

@hUwUtao, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 44 minutes and 6 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0b876609-e124-4f59-8699-33f08b7f55d7

📥 Commits

Reviewing files that changed from the base of the PR and between 56477d5 and 8b8e981.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • src/bin/epoll_stat_probe.rs
  • src/bin/instance_tcp_test.rs
  • src/bin/session_traffic_test.rs
  • src/connection.rs
  • src/packet/mod.rs
  • src/proxy/backend/mod.rs
  • src/proxy/helpers.rs
  • src/proxy/mod.rs
  • src/proxy/query.rs
  • src/tunnel/registry.rs
  • src/utils/logging.rs
  • tun/src/client/runtime.rs
  • tun/src/lib.rs
📝 Walkthrough

Walkthrough

Minecraft packet fields are refactored to use strongly-typed net::mc wrappers throughout. HAProxy PROXY protocol v2 support is introduced with real client address extraction. JSON field access patterns are updated accordingly, with various formatting adjustments applied across the codebase.

Changes

Minecraft Packet Field Type Wrapping

Layer / File(s) Summary
Test handshake packet construction
src/bin/epoll_stat_probe.rs, src/bin/instance_tcp_test.rs, src/bin/session_traffic_test.rs
Test binaries construct HandshakeC2s packets using VarInt, BoundedStr, and BEu16 typed wrappers instead of raw literals, with protocol version dereferencing for body encoding.
Owned packet type conversion
src/packet/mod.rs
OwnedPacket implementation for OwnedHandshake converts between owned fields and HandshakeC2s wrapper types, extracting inner values in from_packet and constructing explicit wrappers in as_packet.
Login disconnect reason encoding
src/connection.rs
The disconnect_player function wraps JSON disconnect reasons in BoundedStr when constructing LoginDisconnectS2c packets.
Backend and proxy query handlers
src/proxy/backend/mod.rs, src/proxy/query.rs
Backend handshake initialization and status response handlers construct packets using net::mc typed wrappers for protocol version, server address, and server port.
Tunnel client query handshake
tun/src/client/runtime.rs
The tunnel client's handle_query_inner constructs HandshakeC2s using typed wrappers and extracts status JSON via response.json.0.to_owned().
Tunnel status fast-path handshake
src/proxy/mod.rs
The tunnel status-check fast path constructs handshake packets with typed net::mc wrappers when querying server status.

HAProxy PROXY Protocol v2 Support

Layer / File(s) Summary
PROXY protocol header parsing helper
src/proxy/mod.rs, src/proxy/helpers.rs
New read_proxy_protocol_addr async helper parses HAProxy v2 headers from accepted TCP connections to derive real client addresses, with fallback to peer address and error handling for unsupported families.
PROXY support in start_with_shutdown
src/proxy/mod.rs
The accept loop conditionally reads/parses PROXY headers when enabled, resolves real client_addr, logs failures, and passes the resolved address to the connection handler.
PROXY support in start
src/proxy/mod.rs
Identical PROXY protocol parsing logic is applied in the start accept loop, including header reading, address resolution, and error logging.
Connection handler signature refactoring
src/proxy/mod.rs
handle_connection explicitly accepts a client_addr parameter instead of deriving it from the connection object.
PROXY protocol failure logging
src/utils/logging.rs
New LureLogger::proxy_protocol_failure method logs PROXY failures with peer identification at info level and formatted error chains at warn level.

JSON Field Updates & Formatting

Layer / File(s) Summary
JSON field access pattern updates
src/proxy/mod.rs, tun/src/client/runtime.rs
JSON caching and extraction are updated to use json.as_bytes() and response.json.0 accessors; server address resolution error handling is refactored with formatted messages.
Code formatting & restructuring
src/tunnel/registry.rs, tun/src/client/runtime.rs, tun/src/lib.rs
Method chains, tuple construction, and calculation expressions are reformatted for readability without behavioral changes.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • illnet/mach#14: The tunnel v5 query protocol changes in tun/src/client/runtime.rs and JSON handling are directly connected to this PR's introduction of the QueryRequestV5/QueryResponseV5 protocol.
  • illnet/mach#13: The updates to tun/src/client/runtime.rs for typed packet wrapper construction are connected to the grand refactor that rewired the tunnel client runtime codepaths.

Suggested labels

codex

Poem

✨ Packets dressed in types so fine,
Wrappers making protocols align,
Proxies now know who's really there,
HAProxy v2 with utmost care—
From raw to wrapped, a refactor's dare! 🎯

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 24.14% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding HAProxy PROXY protocol v2 support to the listener port, which is the primary feature across multiple files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/proxy-protocol

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/bin/session_traffic_test.rs (1)

105-119: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ehh~? Using a hardcoded 758 when you've got the wrapper right there?

Line 119 passes the raw literal 758 to encode_body_with_version, but the other test binaries (epoll_stat_probe.rs line 277, instance_tcp_test.rs line 437) properly dereference the wrapper with *hs.protocol_version. This inconsistency is kinda sloppy, don'tcha think? ♪

Keeping the pattern consistent makes maintenance way easier~

✨ Suggested fix to stay consistent with the other test files
     let mut hs_raw = Vec::new();
     let _ = encode_packet(&mut hs_raw, &hs);
     let mut login_body = Vec::new();
-    let _ = login.encode_body_with_version(&mut login_body, 758);
+    let _ = login.encode_body_with_version(&mut login_body, *hs.protocol_version);
     let mut login_raw = Vec::new();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/bin/session_traffic_test.rs` around lines 105 - 119, The test uses a
hardcoded protocol version (758) when calling login.encode_body_with_version;
update the call to use the HandshakeC2s wrapper by dereferencing
hs.protocol_version (i.e., pass *hs.protocol_version) so it matches the pattern
used elsewhere (see HandshakeC2s, hs.protocol_version and the call to
encode_body_with_version on LoginStartC2s).
src/proxy/mod.rs (2)

349-372: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

The rate limiter is still bucketing the load balancer, not the player.

Heh, with proxy_protocol enabled you already decode the real client IP, but the limiter still runs on addr.ip() before that happens. That makes every player behind one HAProxy instance share a single quota, which is a nasty self-DoS footgun and pretty useless for per-client abuse control. Move the check after a successful PPv2 parse and key it on client_addr.ip().

Also applies to: 508-531

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proxy/mod.rs` around lines 349 - 372, The rate limiter is currently
applied to the upstream/load‑balancer address `addr.ip()` before you decode the
real client IP with `read_proxy_protocol_addr`, causing shared buckets; move the
`rate_limiter.check(...)` logic so it runs after you compute `client_addr` (use
`client_addr.ip()` as the key) and keep the same handling (call
`LureLogger::rate_limited(&client_addr.ip())`, `drop(client)`, `continue`) on
disallowed results; make the identical change for the other occurrence that
mirrors the same pattern (the block that currently mirrors 508-531) so both
proxy‑protocol code paths use the decoded client IP for rate limiting.

1162-1175: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't collapse the queried hostname into a resolved SocketAddr.

You resolve handshake.server_address here and only send that socket address through request_query_from_agent. On the tunnel side the status handshake is rebuilt from query.server_address.to_string(), so the backend never sees the hostname the player actually asked for. Any vhost / forced-host / status plugin keyed off the handshake host can answer with the wrong status. This request needs to carry the original host string and port separately from the resolved target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/proxy/mod.rs` around lines 1162 - 1175, The code collapses
handshake.server_address and handshake.server_port into a resolved SocketAddr
(via resolve_socket_addr) and passes only server_address into
request_query_from_agent, which loses the original hostname; change request
handling so the original host string and port are preserved and sent alongside
(or instead of) the resolved SocketAddr: update the call/site around
resolve_socket_addr, request_query_from_agent, and any Query/Status handshake
construction to include handshake.server_address (string) and
handshake.server_port (u16) separately (or add fields like
original_host/original_port to the query struct) so the tunnel/backend can
rebuild the handshake from the original hostname (see
query.server_address.to_string() usage) rather than the resolved socket address.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/proxy/mod.rs`:
- Around line 570-573: The parsed PPv2 client_addr is dropped after
LureLogger::new_connection and handle_handshake; subsequent code (handle_status,
handle_proxy and any logic using client.as_inner().addr()) therefore uses the
HAProxy peer IP — fix by threading the parsed client_addr through the call chain
or storing it on the connection wrapper: update handle_handshake, handle_status,
handle_proxy signatures (and any callers) to accept a client_addr parameter (or
add a field like parsed_addr on the connection type and set it in
handle_handshake), then replace uses of client.as_inner().addr() where real
client identity is required with the parsed client_addr/connection.parsed_addr
and adjust logging (e.g., LureLogger::new_connection) to use the parsed address.
- Around line 57-87: The code currently discards any bytes read beyond the PROXY
v2 header; preserve leftovers by collecting buf[..total_len] for parsing and
capturing buf[total_len..] as leftover bytes instead of dropping them. After
parsing with net::ha::parse, return or forward the leftover slice (e.g., a
Vec<u8>) together with the real address from read_ingress_hello (or push
leftovers back into client/read_chunk buffer) so downstream code can resume
decoding from the correct position; update the read_ingress_hello signature and
callers to accept the leftover bytes and ensure client.read_chunk remains the
single reader.

---

Outside diff comments:
In `@src/bin/session_traffic_test.rs`:
- Around line 105-119: The test uses a hardcoded protocol version (758) when
calling login.encode_body_with_version; update the call to use the HandshakeC2s
wrapper by dereferencing hs.protocol_version (i.e., pass *hs.protocol_version)
so it matches the pattern used elsewhere (see HandshakeC2s, hs.protocol_version
and the call to encode_body_with_version on LoginStartC2s).

In `@src/proxy/mod.rs`:
- Around line 349-372: The rate limiter is currently applied to the
upstream/load‑balancer address `addr.ip()` before you decode the real client IP
with `read_proxy_protocol_addr`, causing shared buckets; move the
`rate_limiter.check(...)` logic so it runs after you compute `client_addr` (use
`client_addr.ip()` as the key) and keep the same handling (call
`LureLogger::rate_limited(&client_addr.ip())`, `drop(client)`, `continue`) on
disallowed results; make the identical change for the other occurrence that
mirrors the same pattern (the block that currently mirrors 508-531) so both
proxy‑protocol code paths use the decoded client IP for rate limiting.
- Around line 1162-1175: The code collapses handshake.server_address and
handshake.server_port into a resolved SocketAddr (via resolve_socket_addr) and
passes only server_address into request_query_from_agent, which loses the
original hostname; change request handling so the original host string and port
are preserved and sent alongside (or instead of) the resolved SocketAddr: update
the call/site around resolve_socket_addr, request_query_from_agent, and any
Query/Status handshake construction to include handshake.server_address (string)
and handshake.server_port (u16) separately (or add fields like
original_host/original_port to the query struct) so the tunnel/backend can
rebuild the handshake from the original hostname (see
query.server_address.to_string() usage) rather than the resolved socket address.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a9b665a6-2822-42ef-8993-c5465b074531

📥 Commits

Reviewing files that changed from the base of the PR and between 22a3b4b and 56477d5.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • src/bin/epoll_stat_probe.rs
  • src/bin/instance_tcp_test.rs
  • src/bin/session_traffic_test.rs
  • src/connection.rs
  • src/packet/mod.rs
  • src/proxy/backend/mod.rs
  • src/proxy/helpers.rs
  • src/proxy/mod.rs
  • src/proxy/query.rs
  • src/tunnel/registry.rs
  • src/utils/logging.rs
  • tun/src/client/runtime.rs
  • tun/src/lib.rs

Comment thread src/proxy/mod.rs
Comment thread src/proxy/mod.rs
hUwUtao added 2 commits June 6, 2026 12:35
Incoming HAProxy PROXY protocol v2 parsing on the listener port.
When proxy_protocol = true in settings.toml, the PPv2 header
is read from the accepted connection and the real client address
is used throughout the proxy pipeline.

Also fixes API compatibility with net crate commit 2b02d51 which
changed Minecraft packet fields to wrapper types (VarInt,
BoundedStr, BEu16, PacketDecode/Encode traits).
- Thread parsed PPv2 client_addr through to handle_status/handle_proxy
  instead of dropping it (they now use the real client IP, not proxy IP)
- Preserve leftover bytes after PPv2 header and pass them to
  read_ingress_hello so pipelined MC handshake data isn't lost
- Move rate limiter check after PPv2 parsing so it keys on the real
  client IP, not the load-balancer IP
- session_traffic_test: deref hs.protocol_version instead of hardcoding 758

Fix 5 (original hostname in tunnel query path) skipped: server_address
resolves to the backend IP which is functionally correct for the
handshake; fixing it would require adding a wire field to the tunnel
protocol, disproportionate for this change.
@hUwUtao hUwUtao force-pushed the feat/proxy-protocol branch from 56477d5 to 8b8e981 Compare June 6, 2026 05:41
@hUwUtao hUwUtao merged commit b11a491 into main Jun 6, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant