SNI Proxy with TCP Multiplexer
Snitun is a Python library that provides a Server Name Indication (SNI) proxy with TCP multiplexing capabilities, useful for routing traffic based on the SNI header. It is actively maintained by NabuCasa (the developers behind Home Assistant), currently at version 0.45.2, with a steady release cadence.
Common errors
-
FileNotFoundError: [Errno 2] No such file or directory: '/path/to/server.pem' (or similar SSL error)
cause The `server_certs` or `server_key` paths specified in the `Config` object do not point to existing, readable files containing valid TLS certificates.fixProvide valid absolute or relative paths to your TLS server certificate and private key files in the `Config` object. Ensure the files exist and `snitun` has read permissions to them. -
TypeError: SnitunServer.__init__() got an unexpected keyword argument 'listen_host' (or similar for other config params)
cause Attempting to pass configuration parameters directly to `SnitunServer` when using version 0.37.0 or newer, which expects a `Config` object.fixAll configuration parameters must now be encapsulated within a `snitun.config.Config` object, which is then passed as the sole argument to `SnitunServer`. For example: `config = Config(listen_host='...', ...); server = SnitunServer(config)`. -
RuntimeWarning: Enable tracemalloc to get the object allocation traceback (often followed by server not starting) OR Task was destroyed but it was pending!
cause `SnitunServer.start()` or `SnitunServer.stop()` were called without `await`, or the `asyncio` event loop was not properly managed to keep the server task alive.fixAlways `await` calls to `SnitunServer.start()` and `SnitunServer.stop()`. Ensure the `asyncio` event loop is running and use `await asyncio.Future()` or `asyncio.sleep()` in an infinite loop to keep the server running if it's the main task.
Warnings
- breaking Prior to version 0.37.0, configuration parameters were often passed directly to `SnitunServer` or related functions. As of 0.37.0, all configuration must be provided via a `snitun.config.Config` object, which is then passed to `SnitunServer`.
- gotcha Snitun operates as a TLS proxy, requiring valid `server_certs` and `server_key` paths in the `Config` object. Without correctly configured certificates, the server will fail to start or operate securely, leading to connection errors for clients.
- gotcha Snitun is built entirely on `asyncio`. Users must be familiar with `async`/`await` syntax and the `asyncio` event loop for proper integration and management of the server, including starting, stopping, and handling long-running operations.
- gotcha Snitun routes traffic based on the Server Name Indication (SNI) header in client TLS handshakes. If client traffic does not include an SNI header (e.g., older clients, direct IP connections), routing based on `config.routes` will not occur, and the connection might be dropped or fallback to a default if configured.
Install
-
pip install snitun
Imports
- Config
from snitun.config import Config
- SnitunServer
from snitun.server import SnitunServer
Quickstart
import asyncio
import os
from snitun.config import Config
from snitun.server import SnitunServer
# In a real-world scenario, you would provide paths to your
# actual TLS server certificate and key files.
# For this quickstart, we use placeholder paths.
# Running this code as-is will likely fail unless these files exist
# and contain valid cert/key pairs for a TLS server.
# You can generate dummy ones or provide real paths via environment variables.
# Example: SNITUN_SERVER_CERT=./server.pem SNITUN_SERVER_KEY=./server.key python your_script.py
DUMMY_CERT = os.environ.get("SNITUN_SERVER_CERT", "/path/to/server.pem")
DUMMY_KEY = os.environ.get("SNITUN_SERVER_KEY", "/path/to/server.key")
async def main():
# Define the Snitun configuration
config = Config(
listen_host="127.0.0.1",
listen_port=8443,
server_certs=DUMMY_CERT, # Required for TLS
server_key=DUMMY_KEY, # Required for TLS
routes={
"example.com": { # SNI hostname to route
"host": "192.168.1.100", # Target host
"port": 443, # Target port
"no_verify_ssl": False # Verify upstream SSL certs
},
"another.example.org": {
"host": "127.0.0.1",
"port": 8080,
"no_verify_ssl": True
}
}
)
# Create an instance of the Snitun server
server = SnitunServer(config)
print(f"Snitun server configured to listen on {config.listen_host}:{config.listen_port}")
print(f"Routes defined: {list(config.routes.keys())}")
print("\nNOTE: To actually run and test this server, you must ensure valid TLS certificate and key files ")
print(" are accessible at the configured `server_certs` and `server_key` paths.")
print(" See Snitun documentation for proper setup.")
print("\nTo start the server (after ensuring valid certs/keys):")
print(" await server.start()")
print(" await asyncio.Future() # Keep running indefinitely")
print(" await server.stop()")
if __name__ == "__main__":
asyncio.run(main())