Skip to main content

portrelay

portrelay is a protocol-agnostic asynchronous TCP relay. Each configured listener maps to exactly one administrator-selected upstream target:

client TCP stream -> portrelay listener -> configured target TCP stream

It forwards bytes without parsing, buffering whole messages, terminating TLS, or understanding SSH, databases, HTTP, SMTP, Redis, or custom protocols.

Version: 0.1.1.

Install

Install the published release from PyPI:

pip install portrelay
portrelay --version
portrelay --help

The wheel contains the native Rust executable. No networking code runs in Python and the package has no runtime Python dependency.

For an unprivileged installation, use a virtual environment or the user site:

python3 -m venv "$HOME/.venvs/portrelay"
"$HOME/.venvs/portrelay/bin/python" -m pip install portrelay
"$HOME/.venvs/portrelay/bin/portrelay" --version

Alternatively:

python3 -m pip install --user portrelay

No administrator permission is required for package installation or relay operation when listeners use ports 1024 or higher. Ports below 1024 require an operating-system capability; use a load balancer or higher external port instead of granting the relay elevated privileges.

Build locally from source:

python3 -m pip install maturin
maturin build --release --bindings bin --out dist
python3 -m venv .venv
.venv/bin/pip install dist/portrelay-*.whl
.venv/bin/portrelay --version

Release Linux wheels use manylinux2014/glibc 2.17 on x86_64 and manylinux_2_28/glibc 2.28 on ARM64. See deployment for RHEL 8 requirements and release-wheel commands.

Single relay

portrelay serve \
  --listen 0.0.0.0:13306 \
  --target mariadb.internal.company:3306

Listener shorthand :10022 binds IPv4 0.0.0.0:10022. Targets accept IPv4, bracketed IPv6, DNS names, and arbitrary TCP ports:

portrelay serve --listen :10022 --target 10.20.30.40:22
portrelay serve --listen [::]:15432 --target '[2001:db8::20]:5432'

Target routing is fixed when the process starts. Remote clients send only application bytes; they cannot select or override target addresses.

CLI

portrelay serve --listen <HOST:PORT> --target <HOST:PORT>
portrelay serve --config <PATH>

Supported serve options:

Option Meaning
--listen HOST:PORT Listener for single-relay mode. :PORT means 0.0.0.0.
--target HOST:PORT Fixed upstream for single-relay mode.
--config PATH TOML file with one or more [[relay]] mappings.
--connect-timeout DURATION Upstream connect deadline; default 10s.
--idle-timeout DURATION Close after no transferred data; disabled by default.
--shutdown-timeout DURATION Graceful shutdown deadline; default 30s.
--max-connections NUMBER Single-relay admission limit.
--max-connecting NUMBER Single-relay upstream-connect admission limit.
--tcp-keepalive DURATION TCP keepalive probe start delay.
--socket-buffer-size BYTES Requested TCP receive/send buffer size.
--buffer-size BYTES Per-direction Tokio copy buffer size.
--buffer-pool-size NUMBER Maximum pooled copy buffers per relay.
--connection-log-sample-rate NUMBER Emit connection debug logs for every Nth connection.
`--forward-mode auto tokio
--reuse-port Enable Unix SO_REUSEPORT for multi-process listeners.
--metrics-listen HOST:PORT Optional Prometheus HTTP or HTTPS endpoint.
--metrics-token TOKEN Require bearer token for metrics requests.
--metrics-tls-cert PATH / --metrics-tls-key PATH Serve metrics over TLS with PEM files.
--log-level LEVEL Tracing filter, for example info, debug, or warn.

--listen and --target must be supplied together without --config. They cannot be combined with --config. In config mode, relay-specific and security options must be set in TOML; only --log-level, --metrics-listen, and --shutdown-timeout are explicit global command-line overrides.

Multi-relay configuration

[global]
log_level = "info"
metrics_listen = "127.0.0.1:9090"
connect_timeout = "10s"
shutdown_timeout = "30s"
max_connections = 100000

[[relay]]
name = "mariadb"
listen = "0.0.0.0:13306"
target = "mariadb.internal:3306"
max_connections = 50000
idle_timeout = "30m"

[[relay]]
name = "ssh"
listen = "0.0.0.0:10022"
target = "linux01.internal:22"

[[relay]]
name = "mongodb"
listen = "0.0.0.0:27018"
target = "mongo01.internal:27017"
max_connections = 50000

Run it:

portrelay serve --config portrelay.toml

Each mapping gets its own listener, metrics label, and optional per-relay semaphore. A bind failure for one mapping is logged and does not stop unrelated mappings. The process exits only if no configured listener can start.

Protocol transparency

The forwarding layer uses the configured streaming backend and never inspects payload bytes. End-to-end application encryption, authentication, TLS, and framing remain between the original client and target.

Examples below are examples only; portrelay contains no application-specific logic.

SSH

portrelay serve --listen 0.0.0.0:10022 --target linux01.internal:22
ssh -p 10022 user@reachable-server

PuTTY: host reachable-server, port 10022.

MariaDB/MySQL

portrelay serve --listen 0.0.0.0:13306 --target mariadb.internal:3306

Connect the client to reachable-server:13306.

MongoDB

portrelay serve --listen 0.0.0.0:27018 --target mongo.internal:27017

PostgreSQL

portrelay serve --listen 0.0.0.0:15432 --target postgres.internal:5432

Metrics

Set --metrics-listen 127.0.0.1:9090 or global.metrics_listen in TOML. Query:

curl http://127.0.0.1:9090/metrics

For a network-reachable endpoint, configure --metrics-token TOKEN; add --metrics-tls-cert cert.pem --metrics-tls-key key.pem for HTTPS:

curl --fail --cacert ca.pem \
  -H 'Authorization: Bearer TOKEN' \
  https://metrics.example:9090/metrics

Metrics use only the low-cardinality administrator-defined relay label. They do not contain connection IDs, client addresses, target payloads, passwords, SQL, or protocol data.

Exported families include:

  • portrelay_active_connections
  • portrelay_connections_accepted_total
  • portrelay_connections_rejected_total
  • portrelay_connections_closed_total
  • portrelay_upstream_connect_failures_total
  • portrelay_upstream_connect_timeouts_total
  • portrelay_bytes_client_to_upstream_total
  • portrelay_bytes_upstream_to_client_total
  • portrelay_connection_duration_seconds
  • portrelay_upstream_connect_duration_seconds

Bind metrics to loopback or protect it at the network boundary. The endpoint is intentionally small and dependency-free; it implements only the /metrics GET needed by Prometheus.

Architecture and concurrency

  • Tokio event-driven networking; no thread per connection.
  • One task per accepted proxy connection, with one upstream TCP socket.
  • auto (default) uses Tokio's bounded bidirectional copy path and preserves TCP backpressure and half-close behavior.
  • Explicit Linux splice uses kernel pipe forwarding and avoids user-space direction buffers; benchmark before enabling because readiness/pipe overhead can dominate.
  • Configured idle timeouts use two bounded direction buffers sized by buffer_size and an activity watchdog.
  • Global/per-relay connection and upstream-connect semaphores reject excess clients before upstream allocation.
  • No process-wide mutex is used in the forwarding path; each relay's bounded buffer pool has a short critical section on buffer reuse.
  • Metrics use relaxed atomics and fixed histograms; rendering happens only on metrics requests.
  • Graceful shutdown stops listeners, waits for active tasks up to the configured deadline, then cancels remaining connections.

For horizontally scaled deployments, run stateless instances behind a TCP load balancer. Each proxied connection normally consumes two file descriptors and two kernel socket buffers.

Security model

portrelay is a fixed TCP forwarder, not a general proxy. It has no SOCKS negotiation, HTTP CONNECT handling, client-supplied destination parsing, DNS resolution based on client bytes, or payload logging. Administrators control each target through CLI or TOML before the listener accepts traffic.

Target hostnames are resolved at startup for validation where possible and at connection time. Configure dns_refresh to periodically replace cached administrator-resolved addresses; client bytes never influence resolution. Resolution or upstream failures close only the affected client connection and are recorded; they do not terminate the process.

Build, test, and lint

cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo build --release
cargo audit

The integration suite uses local generic TCP echo/hold servers. It covers binary data, reverse traffic, half-close, multiple mappings, limits, connect failures, idle timeouts, metrics, bounded shutdown, and concurrent clients.

Benchmarking

examples/load.rs runs an in-process generic TCP target and relay, then measures concurrent connect/round-trip workload without claiming production capacity. Build and run one point:

cargo build --release --example load
/usr/bin/time -v target/release/examples/load 1000 auto
/usr/bin/time -v target/release/examples/load 1000 tokio

Run 100, 1,000, 10,000, 50,000, and 100,000 only when the host has enough file descriptors, ports, memory, and upstream capacity. Record host/kernel/Rust/version/ulimit/sysctls with each run. Results observed in this workspace are recorded in docs/benchmarks.md; no unmeasured throughput claim is made here.

RHEL 8 deployment

See docs/deployment.md and the unprivileged user-level systemd template. Do not let the application change kernel-wide settings automatically. Capacity planning must include file descriptors, two sockets per connection, listen backlog, TCP buffers, keepalive, TIME_WAIT, ephemeral ports, conntrack, memory, CPU, and NIC bandwidth.

Known limitations

  • auto uses bounded Tokio stream copy; explicit Linux splice is available but not assumed faster.
  • DNS target changes are refreshed only when dns_refresh is configured; otherwise each connection uses normal OS/Tokio resolution behavior.
  • Metrics are plain HTTP unless TLS and/or bearer authentication are configured; bind unauthenticated metrics to loopback or protect them at the network boundary.
  • Per-connection debug logs are sampled by connection_log_sample_rate; info remains the default.
  • A single instance is not promised to support one million connections; horizontal scaling and OS tuning remain deployment responsibilities.

Metadata

Release files for portrelay 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for portrelay 0.1.1
File Size Uploaded
portrelay-0.1.1.tar.gz 52.1 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for portrelay 0.1.1
File
portrelay-0.1.1-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details
portrelay-0.1.1-py3-none-manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64 Details
portrelay-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
portrelay-0.1.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
portrelay-0.1.1-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details

Total release size: 7.5 MB

Release files / portrelay-0.1.1.tar.gz

Download URL portrelay-0.1.1.tar.gz
Size 52.1 kB
Tags Source
SHA-256 checksum
How to use checksums
643d5718999673b3baad1a68b9a8912a6468143405e6dca6f85624e4cb6765e8
BLAKE2b-256 checksum
How to use checksums
fb364d278e7bb6c2d8b38ad826a8775a814f50aa6576be352c7210536c12be27
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portrelay-0.1.1-py3-none-win_amd64.whl

Download URL portrelay-0.1.1-py3-none-win_amd64.whl
Size 1.4 MB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
b2182463e9998700d892d64fb8506b1ebedbf47d1a50daa3e0b3e588d5eb1f7f
BLAKE2b-256 checksum
How to use checksums
7de4e404633574a19201c52050fb147c72259e46ff2da57677b7c9dfa5265862
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portrelay-0.1.1-py3-none-manylinux_2_28_aarch64.whl

Download URL portrelay-0.1.1-py3-none-manylinux_2_28_aarch64.whl
Size 1.5 MB
Tags Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
72e5a62f465f7b43df6c32462e33c975bb77f2283a6477d1350e0146e3584f0b
BLAKE2b-256 checksum
How to use checksums
49dbb2754905f57ed62297c6d788f214a83e4da4996b7e9a76376f356ffe47d7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portrelay-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL portrelay-0.1.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 1.6 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
ace1718f6b561ae65ff9da4d004ab416bc1d84740d8c1dbfe568184577931359
BLAKE2b-256 checksum
How to use checksums
ea97987720ae7995398efc50e26b3cab369cba02635e151e076912d62c98e82d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portrelay-0.1.1-py3-none-macosx_11_0_arm64.whl

Download URL portrelay-0.1.1-py3-none-macosx_11_0_arm64.whl
Size 1.4 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
c63c8a914a6d9660146e9ddb0ff2f359b254bc13234fa9312cc271423b971151
BLAKE2b-256 checksum
How to use checksums
9a8a1280f53c948a3fb8860c052a8bdc40c6e5879375cbd39284d4fb4742c495
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / portrelay-0.1.1-py3-none-macosx_10_12_x86_64.whl

Download URL portrelay-0.1.1-py3-none-macosx_10_12_x86_64.whl
Size 1.5 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
5c38aecd4e1e21b5d52f98f17880ea71816046d833cc4660e6c724cb6bfbfa5c
BLAKE2b-256 checksum
How to use checksums
e8640cf137f8c2226bb494927143eefc2d0296eb546d7568fdc21f8d19778d01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.12.3 {"installer":{"name":"uv","version":"0.12.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.1.1 This release

6 release files

0.1.0

6 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page