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.0.

Install

After a release is published:

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 are built from a manylinux2014/glibc 2.17 baseline. 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|splice Forwarding backend; splice is Linux-only and opt-in.
--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 and release status

  • 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.

Before public PyPI release: run manylinux2014 builds on clean release workers, verify all wheel installs, review dependency advisories, test RHEL 8, choose a repository URL and maintainers, configure PyPI Trusted Publishing, publish signed release notes, and measure representative workloads on target hardware.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

portrelay-0.1.0.tar.gz (52.3 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

portrelay-0.1.0-py3-none-win_amd64.whl (1.4 MB view details)

Uploaded Python 3Windows x86-64

portrelay-0.1.0-py3-none-manylinux_2_28_aarch64.whl (1.5 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

portrelay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (1.6 MB view details)

Uploaded Python 3manylinux: glibc 2.17+ x86-64

portrelay-0.1.0-py3-none-macosx_11_0_arm64.whl (1.4 MB view details)

Uploaded Python 3macOS 11.0+ ARM64

portrelay-0.1.0-py3-none-macosx_10_12_x86_64.whl (1.5 MB view details)

Uploaded Python 3macOS 10.12+ x86-64

File details

Details for the file portrelay-0.1.0.tar.gz.

File metadata

  • Download URL: portrelay-0.1.0.tar.gz
  • Upload date:
  • Size: 52.3 kB
  • Tags: Source
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8ce1ecb6c7ed822efd84a7b1dc8505000bfb48c5a6f19a345e61321bc6d0223d
MD5 86e9aa08c1683eb39776d770230fb88c
BLAKE2b-256 57437d48898a990c8c9ddd87bdf7e8fe64f4c992979e91ca808a3a8f38a9909b

See more details on using hashes here.

File details

Details for the file portrelay-0.1.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: portrelay-0.1.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 a254e716d190f8969dbfadbcea142aa07f19a17da2ba63825b0872aef48d5e86
MD5 2d909ee485b4d03a7754c9d725bf8686
BLAKE2b-256 653cdd0d26c1e8435fa8a2594f9b7047ef862e93363bc6bd3c7053a07bc580bd

See more details on using hashes here.

File details

Details for the file portrelay-0.1.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

  • Download URL: portrelay-0.1.0-py3-none-manylinux_2_28_aarch64.whl
  • Upload date:
  • Size: 1.5 MB
  • Tags: Python 3, manylinux: glibc 2.28+ ARM64
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 32c839798d1a0f180bcfa5cfdfa352f6b307d5cb9de7e0f0c9893a69a41c29df
MD5 da404da141db606a3b513012ee453d68
BLAKE2b-256 3d2131a8f24237741754027f508ccfebb59bceddb911dac76089e957dc035898

See more details on using hashes here.

File details

Details for the file portrelay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

  • Download URL: portrelay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
  • Upload date:
  • Size: 1.6 MB
  • Tags: Python 3, manylinux: glibc 2.17+ x86-64
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 ca9d69ddf6d81ceb54a3626553881a3b946dc14cfc069164c4327d80688159fd
MD5 9e2e0a3fc7bd84596fcc603bd7f9df53
BLAKE2b-256 e8d8481132525abb018cf618cd39afd09a7c363b125ad7300d4a39e746a6cee2

See more details on using hashes here.

File details

Details for the file portrelay-0.1.0-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: portrelay-0.1.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f70bf8575a21e6f47022c3018d6b535450de4847df69aa7483ddef66ef35a697
MD5 e415e7bc1b644c7484a0372264a9eb14
BLAKE2b-256 5663fdd1c219c84b6789305dfdd9bde0e8ea615259074cca6583625dce572780

See more details on using hashes here.

File details

Details for the file portrelay-0.1.0-py3-none-macosx_10_12_x86_64.whl.

File metadata

  • Download URL: portrelay-0.1.0-py3-none-macosx_10_12_x86_64.whl
  • Upload date:
  • Size: 1.5 MB
  • Tags: Python 3, macOS 10.12+ x86-64
  • Uploaded using 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}

File hashes

Hashes for portrelay-0.1.0-py3-none-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 a2cdd66794c1714d4fd1a9f916b42c9f52b94cc1003f88103b691c46a70a4632
MD5 b6325e09591a764575b550ecab4d10f9
BLAKE2b-256 f9615797d9448c73107099d1c5078d230fc758c9383f599732c22c680f15cad4

See more details on using hashes here.

Release history Release notifications | RSS feed

0.1.1

6 files

This release

0.1.0 This release

6 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page