Repository navigation
Session Router 1.1.0 adds forward-secret, post-quantum session encryption; makes path builds cheaper for relays; uses noticeably less memory on relays; and fixes several problems that caused clients to lose track of the network or repeatedly try relays they could never reach.
This release is required for Session Nodes running oxend 11.7.0. oxend 11.7.0 rejects pings from Session Router versions older than 1.1.0, so a node running 11.7.0 alongside an older Session Router stops submitting uptime proofs. oxend 11.7.0 is itself an optional, but recommended, update for now. Upgrade oxend, Session Router and Storage Server (2.12.0) together; if you use the deb packages, a normal apt upgrade handles this.
1.1.0 is fully compatible with 1.0.x relays and clients, so nothing needs to be upgraded network-wide at a fixed moment. The new handshake and path-build formats are used with each peer only when that peer also runs 1.1.0 or later. Against older peers, 1.1.0 falls back to the 1.0 formats.
Upgrading
As usual, upgrades are available from our APT repository (the old deb.oxen.io address continues to work). Static binaries for operators who don't use the deb packages are available below.
Debian 11 ("bullseye") and Ubuntu 20.04 ("focal") are no longer supported, as they has reached end of life and no longer receive security updates. Packages are now provided for Debian 12+ and Ubuntu 22.04+.
Relay-affecting changes
- Forward-secret, post-quantum sessions. Sessions between 1.1.0+ endpoints now use a new handshake. Each side contributes ephemeral X25519 keys, and a ML-KEM-768 key encapsulation is mixed in. The resulting session keys cannot be recovered later from either party's long-term keys, and they are protected against "harvest now, decrypt later" attacks by a future quantum computer. Each direction of a session now gets its own encryption key. Previously one shared secret, derived from one ephemeral key and the remote's long-term identity key, was used in both directions. The handshake adds no round trips: a session becomes usable at the same moment it did before.
- For sessions to a relay (for example, a client reaching a storage server), the new handshake is used when the relay's published router contact says it runs 1.1.0 or later. For client-to-client sessions, the remote's client contact must advertise support. Otherwise the 1.0 handshake is used. (Once 1.1.0+ is mandatory across all service nodes the old handshake support will be removed).
- Pivot relays no longer reject session messages of types they don't recognise. They only forward these messages. Future session message types can therefore be introduced without first waiting for every relay to upgrade.
- Cheaper path builds. Path builds now use a new "v1" per-hop record format. The relay does a single X25519 operation per hop, where it previously did an Ed25519 scalar multiplication plus an identity-key conversion on every build. The record is also smaller, and it is authenticated with an AEAD tag that is checked before any field is used. Clients use v1 for each hop running 1.1.0+ and the old format for older hops. A single path can mix the two, so this works across a partially upgraded network.
- Much lower memory use per connection. Session Router now requires libquic 1.12, which cuts the memory used by each relay-to-relay or client-to-relay link connection by more than half. Relays hold many such connections, so the saving adds up. Two changes account for most of it:
- Every connection used to hold its own 35kB outgoing packet buffer. There is now a single buffer shared by all of an endpoint's connections.
- Split-datagram reassembly no longer allocates a 32kB table for every connection, and stores waiting pieces at their actual size instead of in fixed 1.5kB slots. On its own, this reduces each connection's heap use from about 85kB to 54kB. The rework also fixes a bug where a piece could be paired with half of an unrelated datagram.
- More robust link connections. With libquic 1.11+, a temporary send failure (
EMSGSIZE,ENOBUFS, or a failed GSO send) drops the affected packets, or retries them without GSO, instead of closing the whole connection. UDP packets are now sent with the don't-fragment bit set, as QUIC requires, so that path MTU discovery measures what the path actually carries. Discovery can now also reach full 1472-byte payloads on IPv4. - Fixed relays re-sending the entire network's contact list. When a relay's router contact changed without its address changing (most commonly a version bump during an upgrade), the bucket hashes that relays use to decide what to send were not updated. Removing a router contact also failed to clear its hash. A relay that stayed running through a network-wide upgrade could end up with every bucket permanently mismatched. Clients were then receiving the full list of ~840 relay contacts every five minutes, identical to what they already had. Bucket hashes are now kept correct, which should noticeably reduce relay bandwidth spent on contact fetches.
- More reliable startup when running several instances. The tun interface address range is now auto-selected when the interface is created, retrying up to 50 times if another process claims the range first. Previously the range was chosen during config loading and used much later. This raced with other Session Router processes starting at the same time (as on multi-sn hosts), causing startup failures and systemd restarts.
- New
[bind]optionmax-udp-payload. This option caps the UDP payload size that path MTU discovery may settle on for link connections.-1(the default for relays and full clients) means no cap; otherwise the value must be at least 1200. Relays don't normally need to set this. - Logging changes.
- A
[logging] levelsetting that names only categories (e.g.level=tcp=debug) now adjusts the default levels instead of replacing them. Previously such a setting also changed every other category's level toinfo. A setting that names a global level (e.g.debug) still replaces the defaults. - New
-l/--log-levelscommand-line option, for examplesession-router -l 'path=debug' .... It applies log levels on top of the config file's, which is useful when running manually for debugging.
- A
session-router --versionworks without a config file. Previously it failed with--config is required.- Security hardening of session handshakes. A forged, malformed session-accept message from someone who knew a session's tag could knock an established session back into the unestablished state, stalling its traffic until it re-established. A session's state is now left untouched until the accept's signature verifies.
- Crash and shutdown fixes:
- Fixed a use-after-free when a path build response (or timeout) arrived after the session that started it had been closed or replaced, and a crash when a path build could not be attempted at all.
- Fixed a crash from removing a session from the session list while that list was being iterated.
- Router objects now run their jobs on a dedicated libquic job queue, so no queued job can outlive the router (a source of crashes and hangs on shutdown).
- Replaced a mainnet bootstrap relay that is no longer running.
Client-specific changes
- Clients no longer get stuck after losing the network. A flag meant to prevent overlapping bootstraps was set on the first bootstrap and never cleared, so a client that later lost its relays could never bootstrap again without being restarted. Bootstrapping now works for the life of the process.
- Reworked RouterID list fetching. Each fetch asks several sources for the list of active RouterIDs and requires a majority to agree.
- If a round fails to reach a majority, whether because sources disagreed or because they didn't answer, the client keeps its previous list and retries sooner. Previously the result was installed anyway, and two timeouts out of four could leave the client with no known relays at all. The client then discarded every router contact it was sent.
- When there aren't enough inbound paths to supply the sources, the client also asks over paths its outbound sessions are already using. A majority must still include at least one of the client's own inbound paths, so a remote the client connected to cannot decide the result alone.
- Clients no longer log a warning every few minutes about wanting more sources than they have.
- Fast failure for unreachable relays. A relay that doesn't publish a router contact (such as a node whose operator hasn't installed Session Router, still roughly a sixth of mainnet at the time of writing) is now reported as unreachable as soon as that is known. Previously the caller got a 10-second timeout, and a session could keep retrying path builds to that relay every 250 ms for half a minute. Embedded callers choosing among several swarm members can now skip such nodes immediately.
- Removed the single-router-contact lookup that 1.0.2+ relays don't answer. Lookups made before the first bucketed contact refresh completes are now deferred until it does.
- Embedded library (libsession) changes:
- TCP tunnels from an embedded client to a full Session Router client.
- Path statistics (expiry, round-trip latency, jitter, and ping response/timeout counts) are now available through the embedded API alongside each path's hops. This is a breaking API change:
get_path_for_session()/get_all_session_paths()now returnpath_infostructs. - UDP tunnels are now handed out as claims that release the mapping when destroyed, replacing
close_udp(). Failure callbacks now distinguish "relay is unreachable" from "timed out". establish_udpreturns no tunnel at all for a relay already known to be unreachable.- Embedded clients cap link UDP payloads at 1372 bytes by default (see
max-udp-payloadabove), the smallest size that carries tunnelled 1200-byte QUIC packets without splitting them.tunnel_info::suggested_mtuhas been removed. - The library is now split into
libsessionrouter-core(embedded use, with no libunbound or oxen-mq dependency) andlibsessionrouter(full client/relay). TheSROUTER_EMBEDDED_ONLYcompile-time mode is gone. - ML-KEM-768 functions are exposed in the public API, so libsession can use them without building its own copy.
- Builds for macOS on Apple silicon (arm64), plus fixes for several linking, symbol-conflict and shutdown-hang problems found while embedding Session Router in libsession.
- DNS fixes:
- PTR responses now have the correct record type (they were being returned as
A). - Negative responses (an unmapped IP, a non-existent ONS name) now include an SOA record, so they can be cached and are no longer passed on to the upstream resolver.
- Direct CNAME queries now get a response.
- Unparseable requests get
FORMERR. - Corrected the response flags on
A/AAAAand emptySRVresponses. - Fixed the IPv6 range's zero (subnet anycast) address being handed out for addresses beginning with many zero bits.
- PTR responses now have the correct record type (they were being returned as
[network] mapaddrentries now require an explicit[network] ifaddrrange, and the mapped addresses must fall inside it. This follows from the tun auto-selection change above: the range is no longer known while the config is loaded. Configs that violate this now fail the config check at startup rather than misbehaving later.- The default full-client log level is now
*=info, quic=warn, because QUIC's per-streaminfomessages were very noisy for clients tunnelling TCP.
Build and packaging
- Static release builds now target a generic CPU baseline. Because a build option had been renamed, the 1.0.x static binaries were compiled for the CPU of the CI machine that built them, and could fail with "illegal instruction" on older CPUs. (The deb packages were not affected.)
- The static arm64 build is now properly uploaded as
linux-arm64. It had been uploaded under the same name as the armhf build, so each build overwrote the other. - Dependencies are now fetched through the shared session-deps submodule. CI moved from Drone to Woodpecker. Fixes for clang/LLVM 21 builds.
Full Changelog: v1.0.2...v1.1.0