A Rust implementation of Cloudflare's Simple Proxy Protocol for UDP traffic. This proxy prepends SPP headers to UDP packets, preserving the original client IP address when forwarding traffic to upstream servers.
- Simple Proxy Protocol (SPP) support as specified by Cloudflare
- Header detection: Automatically detects if a packet already has an SPP header and preserves it
- Multiple proxy instances: Configure multiple listen/upstream pairs
- Session management: Efficient handling of client sessions with configurable timeouts
- IPv4 and IPv6 support (IPv4 addresses are mapped to IPv6 format per RFC 4291)
- Allowed Proxy: Use CIDR IP format to allow IP ranges to be allowed to apply the header, or drop the request
The SPP header is a fixed 38-byte structure prepended to each UDP datagram:
| Field | Size | Description |
|---|---|---|
| Magic | 2 bytes | 0x56EC - Protocol identifier |
| Client Address | 16 bytes | Client's IP in IPv6 format |
| Proxy Address | 16 bytes | Proxy's IP in IPv6 format |
| Client Port | 2 bytes | Client's source port (big-endian) |
| Proxy Port | 2 bytes | Proxy's destination port (big-endian) |
For more details, see Cloudflare's documentation.
- Rust 1.70 or later
- Cargo
# Debug build
cargo build
# Release build (optimized)
cargo build --releaseThe binary will be at target/release/nginx-spp-proxy (or target/debug/nginx-spp-proxy).
# Copy and edit the example config
cp config.example.toml config.toml
# Edit config.toml as needed
# Run the proxy
./nginx-spp-proxy -c config.tomlexport SPP_LISTEN_ADDR="0.0.0.0:5000"
export SPP_UPSTREAM_ADDR="127.0.0.1:25565"
export SPP_SESSION_TIMEOUT=60 # optional
export SPP_LOG_LEVEL=info # optional
./nginx-spp-proxyUSAGE:
nginx-spp-proxy [OPTIONS]
OPTIONS:
-c, --config <FILE> Path to configuration file (TOML)
-h, --help Print help message
-V, --version Print version information
[logging]
level = "info" # trace, debug, info, warn, error
json = false # Enable JSON logging
[[proxies]]
listen_addr = "0.0.0.0:25565"
upstream_addr = "127.0.0.1:25566"
session_timeout_secs = 120
# Add more [[proxies]] sections for multiple proxies| Variable | Description | Required |
|---|---|---|
SPP_LISTEN_ADDR |
Address to listen on (e.g., 0.0.0.0:5000) |
Yes |
SPP_UPSTREAM_ADDR |
Upstream server address (e.g., 127.0.0.1:25565) |
Yes |
SPP_SESSION_TIMEOUT |
Session timeout in seconds | No (default: 60) |
SPP_LOG_LEVEL |
Log level | No (default: info) |
SPP_LOG_JSON |
Set to enable JSON logging | No |
This proxy is designed to work alongside NGINX for UDP traffic. A typical setup:
Client -> This Proxy (adds SPP header) -> NGINX Stream (UDP) -> Backend
stream {
upstream backend {
server 127.0.0.1:25566;
}
server {
listen 25565 udp;
proxy_pass backend;
proxy_timeout 10s;
proxy_responses 1;
}
}This crate can also be used as a library:
use nginx_simple_proxy_protocol::spp::SppHeader;
use std::net::SocketAddr;
// Create an SPP header
let client: SocketAddr = "192.168.1.100:12345".parse().unwrap();
let proxy: SocketAddr = "10.0.0.1:8080".parse().unwrap();
let header = SppHeader::new(client, proxy);
// Prepend to payload
let payload = b"Hello, World!";
let packet = header.prepend_to(payload);
// Check if a packet has SPP header
if SppHeader::is_spp_packet(&packet) {
let (header, payload) = SppHeader::parse(&packet).unwrap();
println!("Client: {}", header.client_socket_addr());
}- Incoming packets: The proxy listens for UDP packets on the configured address
- Header detection: Each packet is checked for an existing SPP header (magic number
0x56EC) - Header handling:
- If the packet already has an SPP header, it's forwarded unchanged
- If not, an SPP header is prepended with the client's original IP/port
- Forwarding: The packet is sent to the upstream server
- Responses: Responses from upstream are sent back to the original client
# Run tests
cargo test
# Run with verbose output
cargo test -- --nocaptureMIT License - See LICENSE file for details.