Skip to main content

SNI proxy with TCP multiplexer

Project description

SniTun

End-to-End encryption with SNI proxy on top of a TCP multiplexer

Connection flow

sequenceDiagram
    participant E as Endpoint
    participant C as Client
    participant M as Session Master
    participant S as SniTun
    participant D as Device

    Note over C,M: Trusted connection
    C->>M: Auth / config
    M-->>C: Fernet token

    Note over C,S: Insecure connection
    C->>S: Fernet token
    S->>C: Challenge (AES/CBC)
    C->>S: Response (AES/CBC)

    Note over C,S: Client enters multiplexer mode
    D->>S: External connection (TLS / SNI)
    S->>C: Forward over multiplexer (AES/CBC)
    C->>E: Open local connection

    Note over E,D: End-to-end SSL (trusted connection)

Fernet token

The session master encrypts the client's configuration into a Fernet token. The session master and the SniTun server share the Fernet key(s), so only the SniTun server can decrypt the token; the client just relays it when it connects.

The token payload is a JSON object:

{
  "valid": 1924948800.0,
  "hostname": "myname.ui.nabu.casa",
  "aes_key": "401933e35f9f43d18db1d1de2e5d2e9a9f4c3b2a1d0e9f8c7b6a5d4c3b2a1f0e",
  "aes_iv": "9b2c4d6e8f0a1b3c5d7e9f0a1b2c3d4e",
  "protocol_version": 2,
  "cipher": "aes-gcm",
  "alias": ["www.myname.ui.nabu.casa"]
}
Field Type Description
valid float Expiry as a UTC Unix timestamp in seconds. The SniTun server rejects the token once this time has passed.
hostname string Primary hostname (matched against the TLS SNI) that this peer serves.
aes_key string Hex-encoded 32-byte key (AES-256) used to encrypt the multiplexer header.
aes_iv string Hex-encoded 16-byte initialization vector. Used by aes-cbc; ignored by aes-gcm/aes-gcm-siv (which use a random per-frame nonce).
protocol_version int Multiplexer protocol version the client speaks (see Protocol versioning considerations). Optional; the server assumes 0 when omitted.
cipher string Multiplexer cipher: aes-cbc (default), aes-gcm, or aes-gcm-siv (both authenticated). Optional; the server assumes aes-cbc when omitted. Both ends must be configured the same.
alias string[] Additional hostnames the peer also serves. Optional.

The SniTun server must be able to decrypt this token to validate the client's authenticity. SniTun then initiates a challenge-response handling to validate the AES key and ensure that it is the same client that requested the Fernet token from the session master.

Note: SniTun server does not perform any user authentication!

Challenge/Response

The SniTun server creates a SHA256 hash from a random 40-bit value. This value is encrypted and sent to the client, who then decrypts the value and performs another SHA256 hash with the value and sends it encrypted back to SniTun. If it is valid, the client enters the Multiplexer mode.

Multiplexer Protocol

The header is encrypted using the cipher selected by the token (see Fernet token): aes-cbc (default) or the authenticated aes-gcm/aes-gcm-siv. The payload should be SSL. The ID changes for every TCP connection and is unique for each connection. The size is for the data payload.

With aes-gcm or aes-gcm-siv each encrypted unit (the header, and the encrypted New data) is framed as nonce(12) || ciphertext || tag(16), so the header occupies 60 bytes on the wire instead of 32 and the source IP in a New message carries its own nonce and tag. The tag makes the header — including SIZE — tamper-evident, which AES-CBC cannot detect.

aes-gcm relies on a fresh random nonce per frame and is only safe while the AES key is fresh per session; reusing a key across reconnects risks a nonce collision, which is catastrophic for GCM. aes-gcm-siv (RFC 8452) is nonce-misuse resistant — a repeated nonce only reveals whether two units were identical — so it is the safer choice when an AES key can outlive a single connection. It requires OpenSSL 3.0+; servers without it should keep using aes-gcm.

The extra information could include the caller IP address for a New message on protocol version < 2. From protocol version 2 the caller IP is sent in the (encrypted) data instead — see the New message type below. Otherwise, it is random bits.

The 32-byte header is followed by a variable-length data payload (byte offsets shown):

packet-beta
0-15: "ID (16 bytes)"
16-16: "FLAG (1 byte)"
17-20: "SIZE (4 bytes)"
21-31: "EXTRA (11 bytes)"
32-63: "DATA (variable)"

Message Flags/Types:

  • 0x01: New | Carries the caller IP address.
    • Protocol version < 2: the EXTRA field holds the first byte as an ASCII 4 (IPv6 is not supported), followed by the 4-byte IPv4 address; the data payload is empty.
    • Protocol version >= 2: the address is sent in the encrypted data payload (its own encrypted unit, following the header) as a one-byte family marker (4 or 6) followed by the packed address (4 bytes for IPv4, 16 for IPv6), padded with random bytes to an AES block boundary. This keeps the caller IP off the wire in clear text and allows IPv6 to be carried.
  • 0x02: DATA
  • 0x04: Close
  • 0x08: Ping | The extra data is a ping or pong response to a ping.
  • 0x16: Pause the remote reader (added in protocol version 1)
  • 0x32: Resume the remote reader (added in protocol version 1)

Configuration via environment variables

The following environment variables, which, to be effective, must be set before importing this package, are available to override internal defaults:

  • MULTIPLEXER_INCOMING_QUEUE_MAX_BYTES_CHANNEL - The maximum number of bytes allowed in the incoming queue for each multiplexer channel.
  • MULTIPLEXER_INCOMING_QUEUE_MAX_BYTES_CHANNEL_V0 - The maximum number of bytes allowed in the incoming queue for protocol version 0 channels (default: 256MB, larger than standard channels since v0 lacks flow control).
  • MULTIPLEXER_INCOMING_QUEUE_LOW_WATERMARK - The low watermark threshold, in bytes, for the incoming queue for each multiplexer channel.
  • MULTIPLEXER_INCOMING_QUEUE_HIGH_WATERMARK - The high watermark threshold, in bytes, for the incoming queue for each multiplexer channel.
  • MULTIPLEXER_OUTGOING_QUEUE_MAX_BYTES_CHANNEL - The maximum number of bytes allowed in the outgoing queue for the multiplexer channel.
  • MULTIPLEXER_OUTGOING_QUEUE_LOW_WATERMARK - The low watermark threshold, in bytes, for the outgoing queue for each multiplexer channel.
  • MULTIPLEXER_OUTGOING_QUEUE_HIGH_WATERMARK - The high watermark threshold, in bytes, for the outgoing queue for each multiplexer channel.

Protocol versioning considerations

  • The client is responsible for setting the protocol_version key in the token. If no protocol_version is provided, the server must assume protocol version 0.
  • The server side must always be updated first when incrementing the protocol version as the client assumes that the server is always running a protocol version that it supports.
  • When new message types are added to the Multiplexer, the protocol version must be incremented.
  • Both ends of a connection must agree on the protocol version, since some versions change the wire format (see below). The negotiated version is symmetric.

Versions:

  • 0: Base protocol. No flow control. Caller IPv4 in the header EXTRA field.
  • 1: Adds reader pause/resume (0x16/0x32) for flow control.
  • 2: Sends the caller IP in the encrypted New message data instead of EXTRA, which keeps it off the wire in clear and adds IPv6 support.

Project details


Download files

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

Source Distribution

snitun-0.46.0.tar.gz (63.2 kB view details)

Uploaded Source

Built Distribution

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

snitun-0.46.0-py3-none-any.whl (71.5 kB view details)

Uploaded Python 3

File details

Details for the file snitun-0.46.0.tar.gz.

File metadata

  • Download URL: snitun-0.46.0.tar.gz
  • Upload date:
  • Size: 63.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for snitun-0.46.0.tar.gz
Algorithm Hash digest
SHA256 0bb8523aa6f1a7a441649189d3b0799f8f8bb34a132e578de7a03200e7309ffd
MD5 ea209dfd3132c6f77249a8cb7b175b07
BLAKE2b-256 4393a856f88eb0efebcdcd0ff410eeae593d755a8171f35cfbed7784f6e60c9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for snitun-0.46.0.tar.gz:

Publisher: release.yml on NabuCasa/snitun

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file snitun-0.46.0-py3-none-any.whl.

File metadata

  • Download URL: snitun-0.46.0-py3-none-any.whl
  • Upload date:
  • Size: 71.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for snitun-0.46.0-py3-none-any.whl
Algorithm Hash digest
SHA256 85ef18d3a489cba3c330daf6f757d984dc7d67dfed95b7ed0e880f01d8335728
MD5 56b93f8a7a0be45034bfc17a762d125b
BLAKE2b-256 fda2558977fb9b5c586f571d97519910550a0eb4ec2bee415cd78cd22b8e2f00

See more details on using hashes here.

Provenance

The following attestation bundles were made for snitun-0.46.0-py3-none-any.whl:

Publisher: release.yml on NabuCasa/snitun

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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