Skip to content

Releases: vcarus/volto

v1.0.2

Choose a tag to compare

@github-actions github-actions released this 10 Sep 10:48

The release that goes to the hosts: v1.0.1 plus the quinn-proto patch moved forward to the head of upstream's 0.11.x branch. No change to volto's own code, configuration or scripts since v1.0.1, and v1.0.1 was not deployed.

Changed

  • The quinn-proto patch now points at a708a97, the 0.11.x head of 2026-09-07, sixteen commits past the previous rev. The ones on this proxy's path: the DATAGRAM send buffer series backported in quinn-rs/quinn#2844, where the free space is measured by the queue's real memory use and the queue is pruned for the incoming datagram before it is queued, which is the predicate the CONNECT-UDP session already writes when it grades an eviction; queued DATAGRAMs pruned when the active path changes (#2849); no duplicate flow-control credit for a stopped stream (#2848); DATA_BLOCKED and STREAM_DATA_BLOCKED sent when blocked by flow control (#2852); a DATAGRAM at the exact payload limit retained rather than dropped. The patch stays until a 0.11.x release carries the MTU discovery fixes; crates.io is still at 0.11.17.

Testing

  • The moved rev passed the full suite, clippy, rustdoc and both interop suites with --locked, so the lock files are exactly what was tested. The re-resolution cargo update -p performs on the patched crate's whole subtree was read line by line and one unrelated edge it moved was put back.

Upgrading: volto-deploy on the host, or the install line below. No configuration change. Everything in the v1.0.1 notes applies here as well, since this is the first of the two releases to reach a host: a deployment that dials 64:ff9b:1::/48 destinations needs allow_private_networks = true from v1.0.1 on.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v1.0.1...v1.0.2

v1.0.1

Choose a tag to compare

@github-actions github-actions released this 10 Sep 10:20

A patch release for the four findings of an independent full-tree audit of v1.0.0. None of them is on the pre-authentication path or in the protocol layer; one closes a gap in the destination policy, two make the shipped scripts leave a host whole when they fail, one corrects the architecture page.

Fixed

  • The local-use NAT64 prefix 64:ff9b:1::/48 is refused as a whole under allow_private_networks = false. The policy read an IPv4 address out of it at the RFC 6052 /48 layout, but RFC 8215 section 5 lets an operator place the address anywhere inside that reservation and forbids a reader assuming a layout. At the /64 layout the octets read were the operator's subnet, so a subnet spelling a public IPv4 prefix hid a private target. The whole prefix now follows the switch. The well-known 64:ff9b::/96, 6to4 and Teredo are judged by the address they carry, as before.
  • A failed install-selfsigned.sh --force renewal leaves the old certificate and key in place. The script moved the pair to their backup names before running openssl, so a failed generation left the configured paths empty and the next restart could not start. The new pair is generated beside the old one and moved into place only after openssl succeeds; the new key is created under umask 077, and both backups carry one timestamp.
  • A volto-deploy update that fails before the health check restores the previous binary and unit. A copy that failed part-way, or a failure between the binary copy and the unit copy, ended the run with the rollback pair on disk and nothing restored, because the rollback branch ran only after systemctl is-active. An error trap now covers that window.
  • docs/architecture.md no longer claims that a peer closing a QPACK stream is knowingly not a connection error. The server has reported H3_CLOSED_CRITICAL_STREAM for both a clean finish and a reset since v1.0.0, with the one exemption for a connection that has already ended; the page now says so.

Testing

  • The policy change carries a test proven red first, with a translation of 10.0.0.1 at each of the /56, /64 and /96 layouts that the old reader judged public.
  • Both script fixes were driven red then green against stub openssl, install and systemctl; the transcripts are in the private audit record.

Upgrading: volto-deploy on the host, or the install line below. No configuration change. A deployment that dials 64:ff9b:1::/48 destinations needs allow_private_networks = true from this release on.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v1.0.0...v1.0.1

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 08 Sep 00:54

The first stable release. v0.10.0 ran on both production hosts for three days while a second full-tree review of the whole crate, the tests, the scripts and the workflows was applied in five batches and read twice. The compatibility promise stated in v0.10.0 starts here: configuration keys, their defaults and the command line are stable within 1.x, a key is removed only after a deprecation warning in at least one minor release, and log line shapes and the library API are outside the promise.

Added

  • volto --diagnostics prints the running service's descriptor limits, not only the caller's. The two RLIMIT_NOFILE lines are those of the process running the command, which from an SSH shell is the shell; the bundle now says so on the line under them and, on Linux, walks /proc for every running volto process and prints its Max open files soft and hard limits with its pid. Nothing is asked of systemctl.
  • volto-deploy --force reinstalls a release the host already reports. The convergence check compares version strings, so a release deleted and published again under the same tag left a host on the first upload's bytes. --force takes the download, verify, install and restart path anyway, with the rollback guard unchanged, and needs --tag.
  • A password still equal to the shipped example's placeholder draws a startup warning, naming the [auth].users entry by index and never echoing the password. The manual installation in docs/deployment.md now enables the unit, has the operator edit the file, and starts the service as a separate step, so the port stays shut until the file is right.
  • A reload says when a socket buffer key did not apply. limits.socket_recv_buffer and limits.socket_send_buffer bind with the socket, like server.listen; a SIGHUP that moves either now warns once per direction (log_id om0np2q6) instead of applying nothing silently.

Fixed

  • The SNI gate drops an Initial with a short connection ID before decrypting it. The rule sat behind the key derivation, so a 1200 byte datagram with a four-byte connection ID and a payload nobody sealed was passed through to quinn and answered with a 77 byte CONNECTION_CLOSE, at no cryptographic cost to its sender. It is now judged on the header, where quinn judges it, and nothing legitimate is refused: RFC 9000 section 7.2 gives a client's first Initial a floor of eight bytes.
  • A reload no longer stalls the accept loop. With a gate list configured, the reload read and parsed the certificate while holding the guard every accepted connection takes. The file read and PEM parse now happen before the guard.
  • A reload re-checks the descriptor budget. limits.max_connections and limits.max_targets_per_conn are both reloadable and the product was checked at bind only, so numbers raised during an incident drew no warning.
  • Process exit is bounded by the grace in force, not the one the process started on. A reload that raised server.shutdown_grace let the drain wait the new value and then gave the blocking pool the old one.
  • expected_sni refuses an IP address literal. RFC 6066 section 3 does not permit one in HostName, so a dotted quad on that list could never match and the gate's refusals are silent by construction. Startup and reload now refuse it.
  • The duplicate-username refusal no longer echoes the name to stderr and the journal, which Restart=on-failure repeats. The two indices name both entries.
  • A CONNECT 200 that fails to land resets the target it was opened for, as RFC 9114 section 4.4 asks, instead of closing it with a FIN; the lapse and the failure now take the same path.
  • A reset CONNECT-UDP session stops the peer's sending half with the same code it reset the stream with, instead of the implicit STOP_SENDING with code 0 that dropping the reader sent, which RFC 9114 section 8.1 has a peer read as no error.
  • A peer resetting its own control stream takes the same teardown exemption as the QPACK streams, so a connection that has already ended does not report H3_CLOSED_CRITICAL_STREAM into the log.
  • The deploy script's rollback restores the unit as well as the binary, and runs daemon-reload before the restart. The unit carries the version-dependent hardening directives and is the half most likely to fail on an older host; the old guard put the old binary under the new unit and reported a rollback.
  • The self-signed installer cannot exit silent after regenerating the certificate. Two pipelines in its report stage failed under set -o pipefail on a hand-edited user line or a host without hostname -I, at the moment every pinned fingerprint had just gone stale.

Changed

  • Release binaries are asserted stripped before they are published. readelf refuses a packaged binary that names .symtab or .debug_info, in both the release and the cross workflow, after the first v0.10.0 release shipped an unstripped aarch64 asset.
  • The interop job judges the server log. The independent check of src/h3 printed the log for a human and read nothing; it now fails on an unexpected WARN or ERROR, after stripping the colour escapes that had made the local filter match nothing at all.
  • Every tracked shell script is linted, and the MSRV check builds every target with every feature.
  • The rest of the review's simplify items, across the gate, the endpoint, the configuration, the HTTP/3 layer, the tunnels and the test harness, with no behaviour moved: one helper for the three critical streams, one place that decides what a lapsed response leaves behind, five duplicated test helpers shared, and every reader of a tree, a log or a script asserting a floor before it judges.

Testing

  • Every fix above carries a test proven red first. Targeted mutation over the functions batches 1 and 2 changed: 0 missed.
  • The local SNI gate probe ran on the release commit: with expected_sni = ["localhost"] aioquic passes all seven items, and a ClientHello for another name gets silence.
  • Three-second sleeps in the suite became condition waits, the replay's log-derived assertions and the authentication storm have floors under their bounds, and the documentation gate holds the manual installation and every default to one spelling across the page, the example and the unit.

Dependencies

  • quinn-proto stays pinned to the upstream 0.11.x branch head 48455d3 under [patch.crates-io]; no 0.11.x release carries the MTU and datagram fixes yet.

Upgrading: a configuration whose expected_sni names an IP address literal no longer loads; replace it with the host name the client sends. On the wire, only refusal paths moved: an Initial with a connection ID under eight bytes is dropped silently by a gated server where it drew a CONNECTION_CLOSE, a reset CONNECT-UDP session's STOP_SENDING carries the stream's own code, and a target whose CONNECT 200 failed to land gets a reset instead of a FIN. A client that does none of these sees no difference. volto-deploy --tag behaves as before; add --force to replace a binary that already reports the wanted version.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v0.10.0...v1.0.0

v0.10.0

Choose a tag to compare

@github-actions github-actions released this 05 Sep 05:17

The release candidate for v1.0.0. A full-tree audit of v0.9.3 (five directions plus a three-way simplify pass) was applied in full, the residue was ruled on, and a handful of ideas from another Rust QUIC client were taken in. v1.0.0 is tagged from this same tree after a week on both hosts.

Added

  • From v1.0.0, configuration keys, their defaults and the command line are stable within 1.x. A key is removed only after a deprecation warning in at least one minor release. Log line shapes and the library API are stated as outside the promise. The statement lives in docs/configuration.md under "Version compatibility".
  • volto --diagnostics prints a support bundle an operator can paste into an issue: version, the parsed configuration with every password redacted, the effective limits and security settings, the startup warnings, the file-descriptor limits, the four net.core UDP buffer sysctls on Linux, and the kernel. It binds nothing and logs nothing.
  • Every production log line carries a permanent log_id, eight characters that survive a rewording, so a runbook grep and the accounted set of log lines are keyed by identity rather than text.
  • The proxy's own addresses are not tunnel targets. RFC 9298 section 7 names "the UDP proxy's own addresses" beside localhost, link-local, multicast and broadcast, and the fifth class was reachable: a credentialed client could open a tunnel to this listener and nest a handshake inside it, one connection slot and one tunnel slot per level. An address this host holds now draws the same 403 with destination_ip_prohibited as a private one, judged by asking the kernel rather than by a list, one bind per allowed address per tunnel opened. Loopback stays under allow_private_networks, and behind a relay the relay's address is not one this host holds; both are stated in the documentation.

Fixed

  • The authentication failure limit counted requests, not guesses. A request could carry about a thousand Proxy-Authorization values inside one field section and every one was tried, so a single handshake bought thousands of guesses against a documented five. A request carrying more than two credential values (one per header name, which is all the "accept both names" rule needs) is refused with 400, and every value tried is one failure.
  • The unanswered-packet budget is a connection's total, not a session's. Opening a new session to the same silent target restored the RFC 9298 section 7 budget, so the mitigation was a constant factor rather than a bound. A second counter on the connection, eight times the per-session budget, is spent at the same point; a session whose target answers gives back what that session spent, and nothing else does; a session that finds the total spent is reset with H3_REQUEST_CANCELLED rather than muted. The refund was added when the release candidate's own stress test found that without it a long-lived connection of short answered sessions, which is what DNS through the tunnel is, was reset by session count alone.
  • Process exit is bounded by shutdown_grace. The runtime was dropped implicitly, and tokio waits without limit for a blocking name lookup that has started, so a SIGTERM waited for the slowest in-flight lookup, which a client chooses, until systemd's SIGKILL. The runtime is now stopped with a deadline of the grace plus the close flush plus one second of slack.
  • A reload and a drain, or a reload and an accept, can no longer tear. One lock covers the shutdown latch check and the three configuration writes, so a SIGTERM racing a SIGHUP cannot reopen the listener the drain closed; the live configuration is written before the endpoint's; and a connection is accepted and snapshotted under the same lock, so it runs wholly on the old configuration or wholly on the new one.
  • A near-64 KiB HEADERS or SETTINGS frame no longer retains its allocation for the life of the stream. The frame decoder handed out its payload with split, which kept the backing block; per tunnel and per connection that was up to 64 KiB outside every stated budget, about 16 MiB per connection at the defaults. The payload now leaves with its allocation.
  • Refusing an oversized field section stops the receiving side before answering 431, as the other three refusal writers already did; a windowless peer could otherwise park the write for one idle timeout while unread stream data was held.
  • A :protocol value that is not an RFC 9110 token is a malformed request (H3_MESSAGE_ERROR) rather than a 501, and a port is digits only on both routes (+443 is refused, RFC 3986 port = *DIGIT).
  • Closing a peer's QPACK stream is H3_CLOSED_CRITICAL_STREAM, the same construction the control stream uses, with the same exemption for a close the peer announced.
  • The trailing-dot rule for host names was written three times and one copy differed. expected_sni = ["proxy.example.."] passed validation, was held in a form no ClientHello matches, and drew no warning. One rule now, and an empty label is refused at startup and on reload.
  • The read side of a UDP session grades a socket error like the send side, forgiving the per-packet errors a Linux probe showed a recv can report (EMSGSIZE, ENOBUFS, EPERM, EACCES) instead of ending the session.
  • The datagram receive buffer and the CRYPTO buffer are stated in the transport configuration at quinn's current values rather than inherited, so a quinn bump that moves either is reported by a test.

Changed

  • The SNI gate's hot path copies the header and its sample rather than the whole packet, borrows a single CRYPTO fragment rather than copying it, borrows the name out of the ClientHello, refuses a packet too short for a packet number before deriving Initial keys, and with an empty list skips the lock entirely. No verdict moves; the shape matrix, the fuzz target, both interop halves and the local gate probe say so.
  • Numeric as casts are denied crate-wide. Twenty-eight became From or try_from; the forty-two that index a table by a value already proven in range carry an argued allow each.
  • Release binaries are stripped by the build profile rather than by the release workflow, whose strip step turned out to be a no-op for the cross-built aarch64 asset: the v0.9.3 aarch64 archive still carried its symbol tables. Both archives are now stripped the same way.
  • The rest of the simplify pass: the tunnel context holds its members without an inner Arc each, the two tunnel entry points take a stream and a borrowed context, the resolve failure joins the status vocabulary, one place decides what a lapsed response leaves behind, and the three bind-time failures say which step failed.

Testing

  • Tests prove the reset on Linux. The abortive close of a target after a client reset was proven on Linux in Docker, where the kernel does not send RST from FIN_WAIT_2 and macOS does.
  • Targeted mutation over every function this release added or changed: 67 mutants, 58 caught, 8 unviable, the one survivor pinned. The one gap it found, that a lapsed 200 did not stop a UDP session from starting, has a test.
  • Compile-time assertions between constants, the ledgers re-keyed, five gates that read the tree share one reader, three-second sleeps replaced by condition waits, a probe that asserted nothing deleted, a documentation completeness gate over docs/, and a fourth macro spelling in the accounted-log-lines scanner.

Dependencies

  • quinn-proto stays pinned to the upstream 0.11.x branch head 48455d3 under [patch.crates-io], because no 0.11.x release carries the MTU and datagram fixes yet. v1.0.0 will ship with the same pin unless 0.11.18 appears first.

Upgrading: nothing to do. What changed on the wire is on refusal paths only: a request carrying more than two credential values, a :protocol that is not a token, a signed port, a target that is this host's own address, a connection that has spent its unanswered-packet budget, and a peer that closes its QPACK stream are refused where they were served or ignored before. A client that does none of these sees no difference.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v0.9.3...v0.10.0

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 04 Sep 04:20

Changed

  • The SNI gate now refuses a first Initial whose Destination Connection ID is under eight bytes. The gate's own documentation named three replies quinn gives a stranger and promised to take them away, and one of them it never did: an Initial with a short connection ID drew a CONNECTION_CLOSE carrying PROTOCOL_VIOLATION before a frame was read and without any name being involved. That is the reply a port scan came for. The rule sits directly after the packet opens, which is what makes it decidable: opening under the keys its own connection ID derives is what says this is a client's first Initial, and RFC 9000 section 7.2 gives a first Initial a floor of eight bytes. Every later Initial of an admitted handshake is addressed by the eight bytes this server chose, and so is the one a Retry supplies, so no legitimate packet is ever this shape; a packet the gate cannot open keeps passing, which is the v0.9.1 rule this leaves untouched. The refusal is silent like the others and logs one debug line.
  • The pinned quinn-proto moves to the 0.11.x branch head 48455d3, which carries our quinn-rs/quinn#2823. A datagram naming an unsupported QUIC version used to draw a Version Negotiation packet whatever its size, where RFC 9000 section 5.2.2 says servers MUST drop the small ones; that was an amplification vector against exactly the scanner the gate is for, gate on or off. The same branch head brings two unrelated backports, ACK frames bundled into DATAGRAM and STREAM packets and a minimum-RTT path statistic. No 0.11.x release exists yet, so the [patch.crates-io] stanza stays.

Testing

  • A fuzz target for the gate's judgement of a datagram. The long header, the Initial packet protection, the frame walk and the ClientHello reader, on bytes a sender chose every one of, with the module's own promises as oracles: every shape the documentation says passes does pass, a refusal by name implies a real Initial, and changing the connection ID of a refused datagram turns the refusal into a pass. A seed corpus of authenticating Initials is written by the test suite, since a fuzzer cannot spell one by luck.
  • A shape matrix for the gate. Thirty-four legal layouts of one first flight, each built naming a host on the list and one that is not, assert that neither verdict moves; a real quinn first flight and a real aioquic first flight are judged as the bytes they put on the wire; and four on-wire probes record what still answers a stranger with the gate on. Three of those stay #[ignore]d as the documented residual: a decryptable, ack-eliciting Initial that names nobody draws an acknowledgement, and refusing it would refuse the large first flight the gate deliberately lets through.
  • The gate's three Initial-packet toolkits are folded into one in tests/it_gate_shapes.rs, the nameless key source is shared by the judgement seam, the tests and the fuzz target, and the judge itself loses an enum and two copies per packet. No verdict moves.

Dependencies

  • Both lockfiles refreshed: aws-lc-rs 1.18.1, aws-lc-sys 0.45.0, mio 1.2.3, smallvec 1.16.0, toml 1.1.5, tinyvec 1.13.1. The musl cross-build with the new aws-lc-sys was verified on the pull request that carried it.
  • Dependabot now watches fuzz/Cargo.toml too; the interop client moves to quic-go 0.62.0, and three workflow actions are bumped.

Upgrading: nothing to do. With expected_sni set, a scanner sending an Initial with a short connection ID, or a small probe naming a QUIC version this server does not speak, now gets nothing back; nothing changes for clients.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

What's Changed

  • build(deps): quinn-proto pin to 0.11.x HEAD 48455d3, lockfile refresh by @vcarus in #9
  • build(deps): Bump github.com/quic-go/quic-go from 0.61.0 to 0.62.0 in /tests/interop by @dependabot[bot] in #10
  • build(deps): Bump taiki-e/install-action from 2.86.8 to 2.87.3 by @dependabot[bot] in #11
  • build(deps): Bump actions/deploy-pages from 5.0.0 to 5.0.1 by @dependabot[bot] in #12
  • build(deps): Bump softprops/action-gh-release from 3.0.2 to 3.0.3 by @dependabot[bot] in #13

New Contributors

  • @vcarus made their first contribution in #9

Full Changelog: v0.9.2...v0.9.3

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 03 Sep 04:51

Added

  • A warning when expected_sni names a host the certificate does not cover. The SNI gate admits exactly the configured names, and a name the certificate cannot vouch for is one no client that verifies the certificate by it can complete a handshake under: a typo there makes the server silent, and silence is what the gate is for, so nothing else would ever say so. Config::warnings now checks every configured name against the leaf certificate with rustls's own verify_server_name, the check a client makes, at startup, on --check-config and on every reload. It is a warning rather than an error because the list and the certificate are allowed to differ: a client pinning the certificate by fingerprint need not verify its name.

Testing

  • Two unit tests mint a certificate and assert the warning for a misspelt name and its absence for a covered one in any letter case and with a trailing dot; the first goes red when the check is stubbed out.

Upgrading: nothing to do. Servers with expected_sni set may log one new warning at startup if a configured name is not on the certificate; that warning is the point of this release.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v0.9.1...v0.9.2

v0.9.1

Choose a tag to compare

@github-actions github-actions released this 03 Sep 03:33

Fixed

  • The SNI gate no longer costs every admitted handshake a probe timeout. Only a client's first Initial packets are keyed by their own Destination Connection ID; once the server has answered, the client addresses it by the connection ID the server chose (RFC 9000 §7.2) while its Initial keys stay those of the first packet (RFC 9001 §5.2). v0.9.0 refused the client's acknowledgement of the server's Initial as a forgery it could not open and blinded its datagram, and the Handshake packet carrying the client's Finished usually rides in that same datagram, so with expected_sni set every handshake waited for the client to retransmit and the server never took an Initial RTT sample. Such a packet now passes; nothing that is not one of them gets further, because quinn cannot open it either and drops it without a reply. Only servers with expected_sni set were affected; the default empty list was never touched by it.

Testing

  • Two unit tests craft a 1200-byte Initial with quinn's own Initial keys: one keyed by its Destination Connection ID opens and is judged by the name it carries, proving the construction; one addressed to the server's connection ID but keyed by the client's first is passed, and turns red when the refusal comes back. quinn's own client does not coalesce those two packets on loopback, which is why the on-wire suite had stayed green.
  • Cross-implementation interop (masque-go + aioquic) and the full suite were re-run against this tree before tagging, both green with a clean server log.

Upgrading: nothing to do. If you set expected_sni on v0.9.0 and saw handshakes take an extra round trip, or every connection logging rtt_ms equal to initial_rtt_ms, this release is the fix.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v0.9.0...v0.9.1

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 03 Sep 02:11

Added

  • An SNI gate: [security] expected_sni. Empty by default, which is exactly what every release before this one did. With a list of host names configured, each QUIC Initial packet is decrypted at the socket with the keys RFC 9001 derives from its Destination Connection ID, the ClientHello is reassembled from the CRYPTO frames, and a handshake that does not name one of the listed hosts is blinded before the QUIC layer reads it. A port scan of the address then gets nothing back: no Version Negotiation packet, no CONNECTION_CLOSE, no TLS alert, no certificate, no 407. Probes carrying an unsupported QUIC version fall silent the same way.
  • A second gate in the TLS layer. With a list configured, the certificate resolver declines any other name, so a ClientHello deliberately split across Initial packets, which the socket gate passes on purpose, still gets neither the certificate nor a 407.
  • The list reloads on SIGHUP like the certificate and the credentials.

Changed

  • Two SHOULDs are knowingly set aside when the gate is on, recorded with their reasons: RFC 9000 §5.2.2's Version Negotiation answer and RFC 6066 §3's unrecognized_name alert. Nothing changes with the default empty list.

Testing

  • Twelve on-wire tests pin the gate: a wrong name times out rather than being closed, the same 1200-byte unsupported-version probe draws a Version Negotiation packet with the gate off and nothing with it on, a reload swaps the list, a split ClientHello reaches the second gate, and nmap-shaped empty probes are silent either way. Nineteen unit tests cover the Initial reader.
  • Cross-implementation interop (masque-go + aioquic) and the full suite were re-run against this tree before tagging, both green with a clean server log.

Upgrading: nothing changes unless you set expected_sni. If you do, every client must already send that name as SNI (in Surge, the sni= parameter); configure the clients first, then the server, or nobody can connect and the only trace is this server's debug log. See docs/configuration.md.


Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories. Installing and updating is described in docs/deployment.md.

Full Changelog: v0.8.1...v0.9.0

v0.8.1

Choose a tag to compare

@github-actions github-actions released this 02 Sep 05:48

Changed

  • Minimum supported Rust is 1.95 (was 1.88). std renamed Atomic*::fetch_update to try_update and deprecates the old name from 1.99, so a -D warnings build on stable would have gone red in October; the HEADERS buffer budget counter follows the rename, with release() on the infallible update. Zero behaviour change. Release tarballs are unaffected; only a build from source needs the newer toolchain. The msrv CI job pins the new floor, and README.md and docs/deployment.md say 1.95.
  • logfmt uses str::floor_char_boundary and fmt::from_fn in place of a hand-rolled char-boundary search and a wrapper struct that existed only to give an address list a Display. Same cut at the same position, same rendering.

Testing

  • The floor is asserted both ways: cargo +1.95 check --all-targets --locked is green and cargo +1.88 check --ignore-rust-version fails on the two try_update call sites, so the pin moved because the code needs it, not as decoration.
  • The flood test's evidence assertion no longer asks for a rate. a_datagram_flood_does_not_hold_a_session_past_its_idle_timeout wanted more than a hundred dropped payloads inside its one-second window and a busy CI host delivered thirty-two, with the idle close itself on time; it now asks for the two that rule out the shapes it exists for, and sabotaging the flood to a single packet still turns it red.
  • Cross-implementation interop (masque-go + aioquic) and the full suite were re-run against this tree before tagging, both green with a clean server log.

Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories, so the self-signed installer runs straight out of it — and so does the documentation it refers you to:

tar xzf volto-*-x86_64-unknown-linux-musl.tar.gz
cd volto-*-x86_64-unknown-linux-musl
sudo script/install-selfsigned.sh --binary ./volto

Or skip the manual download entirely: script/deploy.sh fetches the newest release itself, verifies it against SHA256SUMS and installs or updates in place — with --enable-timer it keeps the host on the latest release with a daily systemd timer. It bootstraps a bare host in one line:

curl -fsSL https://raw.githubusercontent.com/vcarus/volto/main/script/deploy.sh | sudo bash -s -- --enable-timer

Full Changelog: v0.8.0...v0.8.1

v0.8.0

Choose a tag to compare

@github-actions github-actions released this 02 Sep 02:48

Changed

  • A startup warning when server.alpn does not offer h3. The key stays legal for interop debugging, but a list without h3 starts a server every client fails against with no_application_protocol, so warnings() now names the configured list at startup instead of leaving handshake errors as the only trace.
  • A reload that ignores a changed server.listen now says so. SIGHUP cannot move the listening socket; when the reloaded file names a different address the server logs one warning with the bound and configured addresses rather than staying silent.
  • Release tarballs carry docs/. Both scripts point the operator at docs/configuration.md or docs/deployment.md at the moment they refuse an install, so the pages now travel with the binary; the release workflow also refuses to publish a tag whose commit has no successful CI run.
  • The installer names --binary when the binary is not where it looked, so an operator working from a release tarball is told the flag instead of being sent to cargo build.
  • Release builds use fat LTO with one codegen unit. Measured on Linux against the default profile: the stripped binary is 11.5% smaller and a 64-byte datagram (the shape of a DNS query through the tunnel) costs 13-17% less CPU each way; MTU-sized datagrams and bulk TCP move 2-8%. The release link takes about a minute longer; nothing else changes.
  • Refactor batch, behaviour unchanged. tunnel/mod.rs is split into status.rs (RFC 9209 vocabulary and the response helpers) and quota.rs; the per-connection Context is held in an Arc; the at-capacity admission decision, the closing log line and the GOAWAY handling are named functions instead of select! arms; the buffered HTTP/3 frame types are an enum so the frame parser is exhaustive and its last unreachable! is gone; four copies of the bounded-response match are one helper; MAX_VARINT is VARINT_MAX_LEN.

Fixed

  • The max_streams_bidi ceiling's error message still described the credit as paid at every handshake; since the pre-authentication clamp it is granted once, by a connection's first authenticated request, which is what the message says now.
  • docs/configuration.md documented the compiled-in MTU defaults while every install runs above them (initial_mtu = 1242, mtu_upper_bound = 1464 from the shipped example); the doc now says so, in the same shape as the existing initial_rtt_ms note.
  • docs/deployment.md leads with the release tarball: download URL shape and the two supported targets, sha256sum --ignore-missing -c SHA256SUMS (a plain -c fails because the file lists both architectures), the prerequisites a stock Ubuntu 24.04 image has and lacks, a conditional firewall line, the one-sentence rollback rule for releases before v0.4.5, and the -c / --binary flags that existed only in --help.
  • docs/architecture.md names the QPACK/Huffman differential oracle as the third independent judge of the in-tree codec.

Testing

  • New CI gates: shellcheck over script/*.sh; clippy with --all-features; the mirrored build step in cross.yml and release.yml is asserted equal by a test and editing either file runs the cross-build; the docs/ copy in the tarball is asserted; the fuzz targets are type-checked on stable with --locked; the fuzz and root manifests are asserted to pin the same quinn-proto revision.
  • Operator-only test binaries are behind the operator-runs feature (it_bench, it_resolver_pool): cargo test no longer links them to run nothing, and naming one without the feature is an error rather than an empty pass.
  • The shipped example configuration is pinned to the defaults it documents: every optional key is uncommented and compared field by field, the key set per section is asserted, and the three intended deviations are listed so a fourth cannot slip in.
  • The fuzz lockfile is asserted to record the crate's current version, after the v0.8.0 bump itself tripped the new --locked fuzz job in CI; the installer's port range check is written as an if so the runner's older shellcheck stops reporting SC2015.
  • it_auth_log scopes each credential assertion to its own scenario; the last cross-file test scaffolding is collected into tests/common/; the narrowest timing bound in the suite now has the same 2.5x margin as its neighbours.
  • fuzz/ moves to edition 2024 with a refreshed lockfile (the current crate version, aws-lc-rs, no ring in any shipped target's graph) and the eight targets were run against the corpus: zero crashes.
  • Cross-implementation interop (masque-go + aioquic) and the full suite were re-run against this tree before tagging, both green with a clean server log.

Static musl builds for x86_64 and aarch64. Verify with SHA256SUMS.

Each archive carries the binary, LICENSE, README.md and the script/ and docs/ directories, so the self-signed installer runs straight out of it — and so does the documentation it refers you to:

tar xzf volto-*-x86_64-unknown-linux-musl.tar.gz
cd volto-*-x86_64-unknown-linux-musl
sudo script/install-selfsigned.sh --binary ./volto

Or skip the manual download entirely: script/deploy.sh fetches the newest release itself, verifies it against SHA256SUMS and installs or updates in place — with --enable-timer it keeps the host on the latest release with a daily systemd timer. It bootstraps a bare host in one line:

curl -fsSL https://raw.githubusercontent.com/vcarus/volto/main/script/deploy.sh | sudo bash -s -- --enable-timer

Full Changelog: v0.7.0...v0.8.0