Repository navigation
Releases
PortRedirect 1.0.0-rc.2
A release candidate of PortRedirect 1.0.0: please try it in real use, and report problems in the issues. 1.0.0 follows once a release candidate ran without problems.
Changes
The second release candidate of 1.0.0. 1.0.0-rc.1 ran in real use without problems; this one has newer dependencies and the fixes of a review of the changes since the security review, so they are in real use before 1.0.0, too. Protocol version 5 (pr-5), as in 1.0.0-rc.1.
Changed
- Updated dependencies: rustls 0.23.46 and rustls-webpki 0.103.16 for the advisories RUSTSEC-2026-0346 to RUSTSEC-2026-0349 and RUSTSEC-2026-0382, tokio 1.53.3 for its advisory about
AsyncFd, which PortRedirect doesn't use, tokio-util 0.7.20, hyper 1.12.0, toml 1.1.8, and the Debian base image of the Docker images. - The README's examples forward to port 8443 instead of 4433, the server's default QUIC port, which they could be mistaken for.
- ARCHITECTURE.md describes how server and client call each other in Mermaid diagrams of the current code.
- A reload of the server's configuration no longer lists the metrics of the clients it removed.
- A reload applies its log level before its other settings, so what they cause, e.g. tunnels that end, is logged at the new level.
- docs/SECURITY-REVIEW.md also covers IPv6, the JSON and connection logs, and the reload, with findings 6 to 8.
- For developers:
make version VERSION=…prepares a release, and Dependabot groups its updates into one pull request per ecosystem.
Fixed
- A
SIGHUPwhile the server started, e.g. a reload right after the start, ended it; now the server listens for it first thing and reloads once it runs. Likewise, aSIGINTorSIGTERMright after the start ended either program without shutting down gracefully. - The server could stop accepting clients if quinn refused to ask a client to validate its address; it now refuses that one connection.
- A reload read the configuration file and the PSK files on a thread of the runtime, which could hold up other work while a file was slow to read.
Downloads
| Architecture | glibc (default) | musl |
|---|---|---|
| x86_64: PCs and most servers | portredirect-linux-amd64.tar.gz |
portredirect-linux-amd64-musl.tar.gz |
| 64-bit ARM (AArch64), e.g. Raspberry Pi 3 or newer with a 64-bit system | portredirect-linux-arm64.tar.gz |
portredirect-linux-arm64-musl.tar.gz |
| 32-bit ARM (ARMv7 or newer), e.g. Raspberry Pi 2 or newer with a 32-bit system | portredirect-linux-armv7.tar.gz |
portredirect-linux-armv7-musl.tar.gz |
- glibc: for distributions with glibc 2.17 or newer, i.e. practically all but those with musl, like Alpine. Faster than musl where the CPU limits the throughput.
- musl: linked statically, so they run on any distribution.
Each archive holds portredirect_server and portredirect_client, with the README, the changelog, the license and example configuration files with every setting, examples/server.toml and examples/client.toml.
Update server and clients together: they must speak the same protocol version.
To check a download, compare its checksum, and verify with the GitHub CLI that this repository's workflow built it:
sha256sum --check --ignore-missing SHA256SUMS
gh attestation verify portredirect-linux-amd64.tar.gz --repo FionaPreroll/vibe-portredirectLatest build of main (641d71c, 2026-10-23)
Linux binaries of the latest commit on main, 641d71c of 2026-10-23: version 1.0.0-rc.2 and the changes since then.
Each new commit on main replaces them.
| Architecture | glibc (default) | musl |
|---|---|---|
| x86_64: PCs and most servers | portredirect-linux-amd64.tar.gz |
portredirect-linux-amd64-musl.tar.gz |
| 64-bit ARM (AArch64), e.g. Raspberry Pi 3 or newer with a 64-bit system | portredirect-linux-arm64.tar.gz |
portredirect-linux-arm64-musl.tar.gz |
| 32-bit ARM (ARMv7 or newer), e.g. Raspberry Pi 2 or newer with a 32-bit system | portredirect-linux-armv7.tar.gz |
portredirect-linux-armv7-musl.tar.gz |
- glibc: for distributions with glibc 2.17 or newer, i.e. practically all but those with musl, like Alpine. Faster than musl where the CPU limits the throughput.
- musl: linked statically, so they run on any distribution.
Each archive holds portredirect_server and portredirect_client, with the README, the changelog, the license and example configuration files with every setting, examples/server.toml and examples/client.toml.
Update server and clients together: they must speak the same protocol version.
To check a download, compare its checksum, and verify with the GitHub CLI that this repository's workflow built it:
sha256sum --check --ignore-missing SHA256SUMS
gh attestation verify portredirect-linux-amd64.tar.gz --repo FionaPreroll/vibe-portredirectPortRedirect 1.0.0-rc.1
A release candidate of PortRedirect 1.0.0: please try it in real use, and report problems in the issues. 1.0.0 follows once a release candidate ran without problems.
Changes
The first release candidate of 1.0.0, to run in real use before 1.0.0 is published on crates.io. From 1.0.0 on, the protocol, the command line, the environment variables, the configuration file and the metric names stay compatible in all 1.x versions. Protocol version 5 (pr-5), as in 0.7.0.
Added
- Docker images of both programs for amd64, arm64 and armv7:
ghcr.io/fionapreroll/portredirect-serverandghcr.io/fionapreroll/portredirect-client, on Debian with glibc or on Alpine with musl (tags ending in-alpine), running as an unprivileged user.latestandalpineare the newest release that isn't a pre-release, until 1.0.0 the latest commit onmain, which is alwaysedgeandedge-alpine.docker/examplehas a Docker Compose stack in which the client forwards to nginx, without published ports, and the server for it; CI runs both for each commit. See the README. - Every option can also be given in an environment variable, e.g. for containers:
PORTREDIRECT_and the option's name in capitals, e.g.PORTREDIRECT_DESTINATION_HOSTfor--destination-host. The command line takes precedence over the environment, and the environment over the configuration file. Flags taketrueorfalse, several values are separated by commas.--helpnames the variables. - Both programs can read their settings from a TOML configuration file,
--config-file. Its keys are the names of the options, and options on the command line or in the environment take precedence. It only names the files that hold PSKs, and relative paths in it are relative to the file. Unknown keys are errors, so typos don't go unnoticed. See the README. - Graceful shutdown: on
SIGINTorSIGTERM, both programs start no new forwarded connections and sendDRAIN, let running ones finish for up to--shutdown-timeoutseconds (default 5), and then close the rest. A second signal closes them right away. A client that shuts down makes the server release its port right away, e.g. for a standby client, and a server that shuts down refuses new clients. - The server serves Prometheus metrics, too:
--provide-metricsand--metrics-listen, on127.0.0.1:9899by default. Metrics of its clients have the labelclient. New metrics include failed authentication attempts, refused QUIC connections by reason, and gauges of the running tunnels and forwarded connections on both sides, e.g. the client'sportredirect_client_tunnel_up. The README lists all metrics. - A rate limit for new forwarded connections per external address (IPv6: per /64 network): after
--max-connection-burst-per-ipat once (default 64), at most--max-connection-rate-per-ipper second (default 20). Further connections are closed right away and counted inportredirect_server_forwarded_connections_refused_totalwith the labelreason="rate_limit"; the address limit counts asreason="address_limit". Each forwarded connection makes the client connect to the destination, so this limits the load a single host can put on it. - Clients can trust the server's certificate by its SHA-256 fingerprint,
--quic-cert-fingerprint, instead of a copy ofcert.der, and by several fingerprints while the server's certificate changes. The server logs its certificate's fingerprint when it starts, and--print-quic-cert-fingerprintprints it, generating the certificate first if there is none. The README describes how to change the server's certificate. - The server's configuration file can list several clients, each with its own name, one or two PSK files (two while changing the PSK) and ports. A client can only use its own ports. Clients may share ports on purpose, e.g. an active and a standby client; the server logs which clients share which ports when it starts.
- Fuzz targets for the parsers of the protocol, for cargo-fuzz in
fuzz/: the authentication on both sides,HELLO,WELCOME, the control messages and the header of data streams. CI runs each for 30 seconds on pull requests and for 15 minutes once a week, andcargo testruns a short smoke test of them on stable Rust. - CI checks the dependencies with cargo-deny: known vulnerabilities, licenses that don't go with the GPL, and sources other than crates.io (
deny.toml,make deny). - docs/SECURITY-REVIEW.md: the internal security review before 1.0, with what was looked at, how, and what was found.
--congestion-control bbr, for both programs and in their configuration files: BBR instead of CUBIC decides how fast a side sends. On links that lose packets for other reasons than congestion, it is much faster: with 1 % loss, the tunnel kept 176 to 1300 Mbit/s instead of less than 4. Set it on both sides; quinn marks BBR experimental.- docs/PERFORMANCE.md: measurements over links with 50 and 150 ms round-trip time and 1 % loss, and the chosen values.
utils/link_emulator.rsemulates such links without privileges,utils/link_benchmark.pyruns the measurements, and a BATS test checks that data crosses such a link unchanged. - Prebuilt binaries for Linux on x86_64, 64-bit ARM and 32-bit ARM (ARMv7) of the latest commit on
main, in the pre-releasesnapshot: by default for glibc 2.17 or newer, and linked statically with musl for any distribution. For each commit, CI builds them, runs the tests for their architecture and replaces the previous ones, with checksums and build provenance attestations. See the README. - Releases of versions, starting with this one: the binaries with checksums and attestations, and the version's changes as notes, in a GitHub release, e.g.
v1.0.0-rc.1, and Docker images tagged with the version, e.g.1.0.0-rc.1and1.0.0-rc.1-alpine. A release candidate is a pre-release. Pushing the tag of a version publishes them, see Releasing in the README. - The package for crates.io only has what builds and tests the programs, the example configurations, the README, the changelog and the license, not CI, Docker or the benchmark tools.
--log-format jsonfor both programs, alsolog-formatin their configuration files: one JSON object per line, with the fields of each message, e.g. for log collectors.- The client logs each forwarded connection with
--log-connections, off by default: when it opens, and when it closes or fails, with the external client's address, the destination, the duration and the bytes in each direction, independently of--log-level. - Example configuration files of both programs with every setting, explained and with the defaults: examples/server.toml and examples/client.toml. Tests check that they are valid and have every option. The archives of the prebuilt binaries have them, too.
- A troubleshooting section in the README, starting with why the server refuses new connections: an address blocked after failed attempts, e.g. of a client the server doesn't know yet, or too many connections.
- The server reads its configuration file and PSK files again on
SIGHUP, e.g. withsystemctl reload, and applies the clients, their PSKs and ports, the limits and the log level without a restart. Tunnels that the new configuration doesn't accept end, those of removed clients, of removed PSKs and of ports no longer allowed, with close code 1 or 5 and the reason; the others go on. Other settings, e.g. the listen addresses, take effect after a restart, and the server warns about their changes. An invalid configuration, e.g. with a missing PSK file, changes nothing. A reload also lifts the blocks of addresses after failed attempts. So adding a client or changing a PSK no longer needs restarts of the server. - IPv6, tested in CI: the client connects to the server over IPv6, too, the server accepts external clients over IPv6, and destinations may have IPv6 addresses. A server on
::, with--listen-hostor--quic-listen-host, accepts IPv6 and IPv4, also on systems where that isn't the default, and logs IPv4 clients with their IPv4 addresses, not IPv4-mapped IPv6 ones. Options take IPv6 addresses with or without brackets, e.g.[2001:db8::1].
Changed
- The server warns when it refuses QUIC connections, with the reason, e.g. how much longer the address is blocked, at most once a minute for each reason. So far, it logged refusals only at the debug level, apart from its connection limit. A client that the server refuses names the possible reasons, once until it connects again.
- Log messages of a tunnel name its client as
tunnel{client="web"}instead ofclient{name="web"}, so JSON log messages don't have two keysname. - Without
--quic-local-host, the client sends from any address, of IPv6 and IPv4, instead of from0.0.0.0, so it also reaches servers that only have IPv6 addresses. If the server has addresses of both, the client still connects to an IPv4 one, with--quic-local-host ::to an IPv6 one. On a system without IPv6, it sends from0.0.0.0as before. - The client looks up names when it uses them, instead of once when it starts: the destination's for each forwarded connection, and the server's for each connection attempt. So it follows changes of their addresses, e.g. of a container that was created again, or of a server with a dynamic address. It tries each address of the destination, e.g. IPv6 and IPv4 for
localhost, and takes an address of the server in the family of--quic-local-host. A name without address when the client starts is no error any more: the clie...