A multi-protocol security tunnel written in Rust, ported from gost (GO Simple Tunnel).
rustun provides a unified command-line interface for proxying, tunneling, and forwarding network traffic across a wide range of protocols. It supports listening on multiple ports simultaneously, multi-level proxy chaining, standard and extended proxy protocols, pluggable transport obfuscation, local and remote port forwarding, transparent proxying, DNS proxying, TUN/TAP device tunneling, authentication, access control, load balancing, and live configuration reload.
- Installation
- Getting Started
- Command Line Reference
- Node Address Format
- Configuration File
- Proxy Protocols
- Transport Types
- Proxy Chaining
- Port Forwarding
- DNS Proxy
- SNI Proxy
- Transparent Proxy
- Relay Protocol
- Shadowsocks
- Authentication
- Access Control
- Load Balancing
- TLS and Encryption
- Obfuscation
- TUN/TAP Device
- Live Reload
- Architecture
- Platform Compatibility
- Docker
- Examples
- Implementation Status
- Module Reference
- Testing
- License
git clone https://github.com/mew-sh/rustun
cd rustun
cargo build --releaseThe compiled binary is located at target/release/rustun (or target/release/rustun.exe on Windows).
rustun -VOutput:
rustun 0.1.0 (rustc windows/x86_64)
The simplest usage is to start a standard proxy server. When no scheme is specified, rustun starts in auto-detection mode and determines the protocol (HTTP, SOCKS4, or SOCKS5) from the first byte of each incoming connection.
Start a standard HTTP/SOCKS5 auto-detecting proxy:
rustun -L :8080Start an explicit HTTP proxy:
rustun -L http://:8080Start an explicit SOCKS5 proxy:
rustun -L socks5://:1080Start a proxy with authentication:
rustun -L admin:123456@localhost:8080Start multiple authentication credentials from a secrets file:
rustun -L "localhost:8080?secrets=secrets.txt"The secrets.txt file format is one username password pair per line. Lines beginning with # are treated as comments and ignored:
# username password
test001 123456
test002 12345678
Listen on multiple ports simultaneously:
rustun -L http2://:443 -L socks5://:1080 -L ss://aes-128-gcm:123456@:8338Route traffic through an upstream proxy server:
rustun -L :8080 -F 192.168.1.1:8081Forward through a proxy with authentication:
rustun -L :8080 -F http://admin:123456@192.168.1.1:8081Create a chain of proxy servers. rustun forwards each request through the chain in the order specified by the -F flags. Each proxy in the chain can use any supported protocol:
rustun -L :8080 -F quic://192.168.1.1:6121 -F socks5+wss://192.168.1.2:1080 -F http2://192.168.1.3:443In this example, the traffic path is:
client -> rustun(:8080) -> quic(192.168.1.1:6121) -> socks5+wss(192.168.1.2:1080) -> http2(192.168.1.3:443) -> target
Usage: rustun [OPTIONS]
Options:
-L <LISTEN> Listen address, can listen on multiple ports (required)
-F <FORWARD> Forward address, can make a forward chain
-M <MARK> Specify out connection mark [default: 0]
-C <CONFIG_FILE> Configure file
-I <INTERFACE> Interface to bind
-D Enable debug log
-V Print version
-P <PPROF_ADDR> Profiling HTTP server address [default: :6060]
-h, --help Print help
Specifies one or more local listening addresses. Each -L flag starts an independent service. The address follows the node address format described in Section 4. At least one -L flag is required unless a configuration file is provided via -C.
rustun -L http://:8080 # HTTP proxy on port 8080
rustun -L socks5://admin:pass@:1080 # SOCKS5 with authentication on port 1080
rustun -L tcp://:2222/192.168.1.1:22 # TCP port forwarding, local 2222 to remote 22
rustun -L http://:8080 -L socks5://:1080 # Two listeners simultaneouslySpecifies one or more upstream proxy nodes to form a forward chain. Each -F flag appends a node to the chain. When multiple -F flags are given, they form a multi-hop chain: the first -F is the entry proxy, the last -F is the exit proxy closest to the target.
rustun -L :8080 -F http://proxy1:3128 # Single-hop HTTP proxy chain
rustun -L :8080 -F http://proxy1:3128 -F socks5://proxy2:1080 # Two-hop chain: HTTP then SOCKS5Sets the SO_MARK socket option on all outgoing connections. This integer value is used by the Linux kernel for policy-based routing (e.g., routing marked packets through specific interfaces or VPN tunnels via ip rule and iptables). Has no effect on platforms that do not support socket marks. Default value is 0 (no mark).
rustun -L :8080 -M 100Loads configuration from a JSON file instead of (or in addition to) command-line flags. The JSON format is described in Section 5. When both -C and -L/-F flags are present, the configuration file takes precedence.
rustun -C config.jsonBinds all outgoing connections to the specified network interface name. This is useful on multi-homed hosts where traffic should exit through a particular interface. The value is an interface name such as eth0, wlan0, or tun0.
rustun -L :8080 -I eth0Enables debug-level logging. Without this flag, only informational messages and above are printed. With this flag, detailed per-connection diagnostics including protocol handshakes, address resolution, and data flow are logged.
rustun -L :8080 -DPrints the version string and exits immediately. The output format is rustun <version> (rustc <os>/<arch>), which mirrors the gost version output format gost <version> (go<version> <os>/<arch>).
rustun -V
# Output: rustun 0.1.0 (rustc windows/x86_64)Specifies the address for a profiling HTTP server. This server is only started when the PROFILING environment variable is set to a non-empty value. Default address is :6060.
PROFILING=1 rustun -L :8080 -P :6060When rustun is invoked with no arguments at all, it prints the usage help text and exits with code 0. This matches the behavior of gost.
All -L (listen) and -F (forward) addresses follow the same URL-based format:
[scheme://][user:password@][host]:port[/remote_address][?parameters]
The scheme defines both the protocol and the transport, separated by +:
protocol+transport://...
If only one component is given (e.g., http://), the transport defaults to tcp. If no scheme is given at all (e.g., :8080), the protocol is auto-detected and the transport is tcp.
Examples:
| Scheme | Protocol | Transport | Meaning |
|---|---|---|---|
http |
HTTP | TCP | HTTP proxy over TCP |
socks5 |
SOCKS5 | TCP | SOCKS5 proxy over TCP |
http+tls |
HTTP | TLS | HTTP proxy over TLS (HTTPS proxy) |
socks5+ws |
SOCKS5 | WebSocket | SOCKS5 proxy over WebSocket |
ss |
Shadowsocks | TCP | Shadowsocks over TCP |
relay+tls |
Relay | TLS | Relay protocol over TLS |
tcp |
TCP forward | TCP | TCP port forwarding |
kcp |
KCP | KCP | KCP transport (UDP-based) |
Inline authentication credentials are specified before the @ sign:
socks5://admin:secret@:1080
For Shadowsocks, the format is method:password:
ss://aes-128-gcm:mypassword@:8338
The host may be omitted to listen on all interfaces (equivalent to 0.0.0.0). IPv6 addresses must be enclosed in brackets:
:8080 # Listen on all interfaces, port 8080
127.0.0.1:8080 # Listen on loopback only
[::1]:8080 # Listen on IPv6 loopback
For forwarding protocols (tcp, udp, rtcp, rudp, dns), the path component specifies the remote target address:
tcp://:2222/192.168.1.1:22 # Forward local port 2222 to 192.168.1.1:22
dns://:5353/8.8.8.8:53 # Forward DNS queries to Google DNS
Multiple remote addresses may be separated by commas for load balancing:
tcp://:8080/10.0.0.1:80,10.0.0.2:80,10.0.0.3:80
Additional configuration is passed as URL query parameters. The table below shows all recognized parameters and their implementation status.
Fully implemented parameters are extracted from the node URL and applied at runtime. Parsed only parameters are stored in the node's value map and available to handler code, but the handler does not yet act on them. Type only parameters have corresponding configuration types defined but no runtime extraction.
| Parameter | Applies To | Description | Status |
|---|---|---|---|
secrets |
HTTP, SOCKS5 | Path to authentication file | Fully implemented |
bypass |
All handlers | Comma-separated bypass rules, prefix ~ for reverse |
Fully implemented |
whitelist |
All handlers | Permission whitelist (action:host:port) |
Fully implemented |
blacklist |
All handlers | Permission blacklist (action:host:port) |
Fully implemented |
hosts |
All handlers | Path to hosts file for static resolution | Fully implemented |
timeout |
All | Connection timeout (e.g., 5s, 30s) |
Fully implemented |
retry |
All handlers | Per-handler retry count | Fully implemented |
method |
Shadowsocks | Cipher method name | Fully implemented |
ip |
Forward | Additional IP addresses (comma-separated) | Fully implemented |
proxyAgent |
HTTP | Custom Proxy-Agent header value | Fully implemented |
host |
SNI | Target host for SNI proxy | Fully implemented |
dns |
All handlers | DNS resolver address(es), comma-separated | Parsed only |
strategy |
Chain nodes | Load balancing strategy: round, random, fifo |
Parsed only |
max_fails |
Chain nodes | Maximum failures before marking node dead | Parsed only |
fail_timeout |
Chain nodes | Duration before a dead node is retried | Parsed only |
fastest_count |
Chain nodes | Number of top fastest nodes to keep | Parsed only |
ttl |
UDP, DNS | UDP connection idle timeout (e.g., 60s) |
Parsed only |
cert |
TLS | Path to TLS certificate PEM file | Parsed only |
key |
TLS | Path to TLS private key PEM file | Parsed only |
ca |
TLS | Path to CA certificate for pinning | Parsed only |
secure |
TLS client | Enable server certificate verification | Parsed only |
path |
WebSocket, H2 | URL path for the WebSocket/H2 endpoint | Parsed only |
ping |
SSH, HTTP/2 | Heartbeat interval in seconds | Parsed only |
c |
KCP | Path to KCP JSON configuration file | Parsed only |
tcp |
KCP | Enable TCP mode for KCP | Parsed only |
keepalive |
QUIC | Enable QUIC keep-alive | Parsed only |
probe_resist |
HTTP | Probe resistance mode (code, web, host, file) | Parsed only |
knock |
HTTP | Knocking host for probe resistance | Parsed only |
backlog |
UDP | UDP listener connection backlog | Parsed only |
queue |
UDP | UDP listener queue size | Parsed only |
name |
TUN/TAP | Device name | Parsed only |
net |
TUN/TAP | Device network address (CIDR notation) | Parsed only |
mtu |
TUN/TAP | Maximum transmission unit | Parsed only |
route |
TUN/TAP | Routing entries (CIDR, comma-separated) | Parsed only |
gw |
TUN/TAP | Default gateway IP | Parsed only |
peer |
TUN (macOS) | Peer address for point-to-point | Parsed only |
nodelay |
Relay, SS | Send address header immediately (no buffering) | Parsed only |
notls |
SOCKS5 | Disable TLS method in SOCKS5 | Parsed only |
httpTunnel |
HTTP | Force HTTP tunnel mode | Parsed only |
All parameters in the "Parsed only" column are stored in node.values and accessible via node.get("key"), node.get_bool("key"), node.get_int("key"), and node.get_duration("key"). They are available for handler implementations to consume but are not yet wired into the default CLI server startup path. Contributions to wire additional parameters are welcome.
The JSON configuration file provides the same capabilities as command-line flags. The top-level object defines the default route, and additional routes are defined in the Routes array.
{
"Debug": true,
"ServeNodes": [
"http://:8080",
"socks5://:1080"
],
"ChainNodes": [
"http://proxy:3128"
],
"Retries": 3,
"Mark": 100,
"Interface": "eth0",
"Routes": [
{
"ServeNodes": ["relay+tls://:8443"],
"ChainNodes": ["http://upstream:8080"],
"Retries": 1,
"Mark": 0,
"Interface": ""
},
{
"ServeNodes": ["ss://aes-256-gcm:password@:8338"],
"ChainNodes": [],
"Retries": 0
}
]
}| Field | Type | Default | Description |
|---|---|---|---|
Debug |
boolean | false |
Enable debug-level logging |
ServeNodes |
string[] | [] |
Listen addresses for the default route. Each string follows the node address format. |
ChainNodes |
string[] | [] |
Proxy chain nodes for the default route. Ordered from entry to exit. |
Retries |
integer | 0 |
Number of times to retry a failed connection through the chain. A value of 0 means no retries (one attempt). |
Mark |
integer | 0 |
SO_MARK value applied to all outgoing connections in this route. Used for Linux policy routing. |
Interface |
string | "" |
Network interface name to bind outgoing connections. Empty string means no binding. |
Routes |
object[] | [] |
Additional independent routes. Each route object has the same fields as the top level (excluding Debug and Routes). |
Each route operates independently: it has its own set of listeners (ServeNodes), its own proxy chain (ChainNodes), and its own connection parameters. This allows a single rustun instance to serve multiple proxy protocols on different ports with different upstream chains.
The HTTP proxy handler supports both the CONNECT method (for HTTPS tunneling) and direct HTTP request forwarding.
When a client sends an HTTP CONNECT request, rustun establishes a TCP tunnel to the target and relays data bidirectionally. When a client sends a plain HTTP request (GET, POST, etc.), rustun forwards the request to the target server and streams the response back.
Server:
rustun -L http://:8080The handler performs the following steps for CONNECT:
- Read the HTTP request line and headers from the client.
- Extract the target host from the CONNECT URI or the Host header.
- Verify the target against bypass rules, whitelist, and blacklist.
- Authenticate the client using the Proxy-Authorization header if an authenticator is configured.
- Connect to the target through the proxy chain (or directly if no chain is configured).
- Send
HTTP/1.1 200 Connection establishedback to the client. - Relay data bidirectionally between client and target until either side closes.
For plain HTTP requests, the handler forwards the request (stripping proxy-specific headers such as Proxy-Connection and Proxy-Authorization) to the target server and relays the response.
The HTTP proxy sets the Proxy-Agent response header to rustun/<version> by default. This can be customized with the proxyAgent query parameter.
The HTTP proxy supports probe resistance to defend against active probing. When authentication is configured and a client fails to authenticate, the proxy can disguise itself rather than returning a 407 Proxy Authentication Required response. Modes:
code:<status>-- Return the specified HTTP status code.web:<url>-- Fetch and return the content from the given URL.host:<addr>-- Forward the request to the given address.file:<path>-- Serve the content of the given file.
rustun -L "http://user:pass@:8080?probe_resist=code:404"
rustun -L "http://user:pass@:8080?probe_resist=web:example.com&knock=secret.example.com"The knock parameter specifies a knocking host. Only requests to this host bypass probe resistance and receive the real 407 response, allowing legitimate clients to discover that authentication is required.
The SOCKS5 handler implements RFC 1928 (SOCKS Protocol Version 5) and RFC 1929 (Username/Password Authentication for SOCKS V5).
Server:
rustun -L socks5://:1080Client (using rustun as a chain node):
rustun -L :8080 -F socks5://server_ip:1080Supported SOCKS5 features:
- Authentication methods: No authentication (
0x00), Username/Password (0x02). - Commands: CONNECT (
0x01), UDP ASSOCIATE (0x03). - Address types: IPv4 (
0x01), Domain name (0x03), IPv6 (0x04).
The handler performs the following steps:
- Read the SOCKS5 greeting (version byte, number of methods, method list).
- Select the appropriate authentication method based on whether an authenticator is configured.
- If username/password is selected, read and verify the credentials.
- Read the SOCKS5 request (command, address type, destination address, port).
- For CONNECT: verify permissions, connect to the target through the chain, send a success reply, and relay data bidirectionally.
- For UDP ASSOCIATE: send a reply with the local address and keep the TCP connection alive.
The SOCKS4 handler implements the original SOCKS4 protocol (IPv4 addresses only) and the SOCKS4a extension (which adds domain name support).
Server:
rustun -L socks4://:1080SOCKS4a is automatically supported: when the client sets the IP address to 0.0.0.x (where x is non-zero) and appends a null-terminated domain name after the user ID, the handler resolves the domain name to connect to the target.
The SOCKS4 handler does not support authentication (as per the SOCKS4 specification). For security, the auto-detection handler (which handles both SOCKS4 and SOCKS5) will reject SOCKS4 connections when credentials are configured on the listener.
When no scheme is specified (e.g., -L :8080), the auto-detection handler examines the first byte of each incoming connection to determine the protocol:
| First Byte | Protocol | Handler |
|---|---|---|
0x05 |
SOCKS5 | Socks5Handler |
0x04 |
SOCKS4/4a | Socks4Handler |
| Any other | HTTP | HttpHandler |
This allows a single port to serve all three proxy protocols simultaneously.
The transport layer determines how data is carried on the wire between rustun and the next proxy node. The transport is specified after the + in the scheme:
rustun -L http+tls://:443 # HTTP proxy over TLS
rustun -L :8080 -F socks5+ws://proxy:8080 # SOCKS5 over WebSocketRaw TCP connection. Used when no transport is specified.
TLS encrypts the TCP connection. rustun supports custom certificates, client certificate authentication (mTLS), and certificate pinning.
Server (with custom certificate):
rustun -L "http+tls://:443?cert=cert.pem&key=key.pem"Client (with server verification):
rustun -L :8080 -F "http+tls://server:443?secure=true"Client (with CA certificate pinning):
rustun -L :8080 -F "http+tls://server:443?ca=ca.pem"If no certificate files are provided, rustun looks for cert.pem and key.pem in the current working directory. If those are not found, a random self-signed certificate is generated.
WebSocket transport frames proxy data as binary WebSocket messages. This is useful for traversing HTTP proxies and firewalls that only allow WebSocket traffic.
Server:
rustun -L "socks5+ws://:8080?path=/ws"Client:
rustun -L :8080 -F "socks5+ws://server:8080?path=/ws"The path parameter sets the WebSocket endpoint URL path. Both ws (plain) and wss (TLS-encrypted) variants are supported. Multiplexed variants mws and mwss use stream multiplexing over a single WebSocket connection.
SSH transport uses the SSH protocol (RFC 4253/4254) for encrypted tunneling with two modes:
Forward Tunnel: Used for local and remote TCP port forwarding through SSH.
Server:
rustun -L forward+ssh://:2222Client (remote port forwarding):
rustun -L rtcp://:1222/:22 -F forward+ssh://server:2222Transport Tunnel: Used as a general-purpose encrypted transport for proxy protocols.
Server:
rustun -L ssh://:2222Client:
rustun -L :8080 -F "ssh://server:2222?ping=60"The ping parameter enables heartbeat detection with the specified interval in seconds.
KCP is a UDP-based reliable transport protocol that provides faster delivery than TCP at the cost of higher bandwidth consumption. It is based on the KCP protocol specification.
Server:
rustun -L kcp://:8388Client:
rustun -L :8080 -F kcp://server:8388KCP configuration is loaded from a JSON file. rustun automatically loads kcp.json from the working directory if it exists, or you can specify a path:
rustun -L "kcp://:8388?c=/path/to/kcp.json"KCP JSON configuration fields:
{
"key": "encryption-key",
"crypt": "aes",
"mode": "fast",
"mtu": 1350,
"sndwnd": 1024,
"rcvwnd": 1024,
"datashard": 10,
"parityshard": 3,
"dscp": 0,
"nocomp": false,
"nodelay": 0,
"interval": 50,
"resend": 0,
"nc": 0,
"sockbuf": 4194304,
"keepalive": 10,
"tcp": false
}Mode presets (mode field) adjust nodelay, interval, resend, and nc to predefined values: normal, fast, fast2, fast3.
KCP nodes can only be used as the first node of a proxy chain.
QUIC is a UDP-based multiplexed transport protocol. It provides built-in encryption (via TLS 1.3) and supports multiple concurrent streams over a single connection.
Server:
rustun -L quic://:6121Client:
rustun -L :8080 -F "quic://server:6121?keepalive=true"QUIC nodes can only be used as the first node of a proxy chain.
HTTP/2 transport supports two modes:
Standard Proxy (http2): Acts as an HTTP/2 proxy using the CONNECT method, and is backwards-compatible with HTTPS proxies.
# Server
rustun -L http2://:443
# Client
rustun -L :8080 -F "http2://server:443?ping=30"Tunnel (h2 / h2c): Uses HTTP/2 as a pure transport tunnel. h2 uses TLS encryption; h2c uses cleartext HTTP/2.
# Server (TLS)
rustun -L h2://:443
# Server (cleartext)
rustun -L h2c://:8080
# Client
rustun -L :8080 -F h2://server:443FakeTCP disguises UDP traffic as TCP packets using raw sockets. This is useful for KCP-based tunnels in environments where UDP traffic is blocked or throttled but TCP is allowed. Requires raw socket capabilities (CAP_NET_RAW on Linux).
VSOCK (Virtual Socket) provides communication between a virtual machine and its host. The address format is contextID:port. Only available on Linux with the vsock kernel module loaded.
Proxy chains route traffic through a sequence of proxy servers. Each -F flag appends a node to the chain. When a client connects, rustun dials through the chain sequentially:
rustun -L :8080 -F http://proxy1:3128 -F socks5://proxy2:1080The connection process:
- Establish a TCP connection to
proxy1:3128. - Send an HTTP CONNECT request to
proxy1requesting connection toproxy2:1080. - Upon receiving
200 Connection established, perform a SOCKS5 handshake withproxy2through the tunnel. - Send a SOCKS5 CONNECT request to
proxy2for the final target address. - Upon success, relay data bidirectionally between the client and the target.
Each node in the chain can use any supported protocol+transport combination. The chain supports:
- Configurable retry count per chain (via
Retriesin config or per-noderetryparameter). - Connection timeout (via
timeoutparameter). - Custom DNS resolution per chain (via
dnsparameter). - Custom hosts file per chain (via
hostsparameter). - Bypass rules that stop chain traversal when a matching address is encountered.
rustun -L :8080 \
-F "http+tls://proxy1:443?secure=true" \
-F "socks5+ws://proxy2:8080?path=/tunnel" \
-F "relay+tls://proxy3:8443"Forward all TCP connections arriving on a local port to a remote target address. The remote address is specified as the path component of the URL:
rustun -L tcp://:2222/192.168.1.1:22This binds to local port 2222 and forwards every incoming connection to 192.168.1.1:22. If a proxy chain is specified via -F, the forwarding goes through the chain:
rustun -L tcp://:2222/192.168.1.1:22 -F http://proxy:8080When the last node of the chain uses SSH forward transport, rustun uses SSH direct port forwarding (RFC 4254 Section 7.2):
rustun -L tcp://:2222/192.168.1.1:22 -F forward+ssh://server:2222Multiple remote addresses may be specified (comma-separated) for load balancing:
rustun -L tcp://:8080/10.0.0.1:80,10.0.0.2:80,10.0.0.3:80Forward all UDP datagrams arriving on a local port to a remote target address:
rustun -L "udp://:5353/192.168.1.1:53?ttl=60"Each UDP forwarding channel has an idle timeout. When no data is exchanged within this period, the channel is closed. The timeout is set via the ttl parameter (default: 60 seconds).
When forwarding UDP data through a proxy chain, the last node in the chain must be a SOCKS5 proxy, which uses UDP-over-TCP to carry the datagrams.
In remote forwarding, rustun listens on a port at the remote end (the last proxy in the chain) and forwards connections back to a local target:
rustun -L rtcp://:2222/192.168.1.1:22 -F socks5://172.24.10.1:1080This causes port 2222 on 172.24.10.1 to forward connections to 192.168.1.1:22. When the last node uses SSH forward transport, rustun uses SSH remote port forwarding (RFC 4254 Section 7.1):
rustun -L rtcp://:2222/192.168.1.1:22 -F forward+ssh://server:2222rustun -L "rudp://:5353/192.168.1.1:53?ttl=60" -F socks5://172.24.10.1:1080The DNS proxy handler forwards DNS queries to an upstream DNS resolver. The upstream address is specified as the path component:
rustun -L dns://:5353/8.8.8.8:53This listens for DNS queries on local port 5353 (TCP) and forwards them to Google Public DNS at 8.8.8.8:53 via UDP.
The DNS resolver module also supports DNS-over-TLS (DoT) and DNS-over-HTTPS (DoH) when used as a chain component.
The SNI (Server Name Indication) proxy inspects the TLS ClientHello message to extract the server name, then routes the connection to the appropriate backend server.
rustun -L sni://:443The handler performs the following steps:
- Peek at the first bytes of the incoming connection.
- If the first byte is
0x16(TLS Handshake), parse the ClientHello to extract the SNI hostname. - Connect to
<hostname>:443(or the port specified by thehostparameter). - Forward the original ClientHello and all subsequent data bidirectionally.
If the connection does not begin with a TLS handshake, it is treated as a plain HTTP request and delegated to the HTTP handler.
Transparent proxying intercepts connections without requiring client-side proxy configuration. This uses iptables REDIRECT or TPROXY rules on Linux.
rustun -L redirect://:12345 -F http2://server:443The transparent proxy handler uses the SO_ORIGINAL_DST socket option to recover the original destination address (set by iptables REDIRECT). It then connects to that address through the proxy chain.
Example iptables rule:
iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 12345UDP transparent proxying is also supported via TPROXY:
rustun -L "redu://:12345?ttl=60"The relay protocol is a lightweight custom protocol designed for efficient tunneling with built-in support for user authentication and target address specification.
Server (with fixed target):
rustun -L relay://:8443/192.168.1.1:80Server (dynamic target from client):
rustun -L relay://user:pass@:8443Client:
rustun -L :8080 -F relay://user:pass@server:8443The relay protocol frame format includes a version byte, flags (including a UDP flag for UDP relay), and feature fields for user authentication and address specification.
Shadowsocks is an encrypted proxy protocol. The cipher method and password are specified in the URL credentials as method:password:
Server:
rustun -L ss://aes-128-gcm:123456@:8338Client:
rustun -L :8080 -F ss://aes-128-gcm:123456@server:8338Supported cipher methods:
| Cipher | Key Size |
|---|---|
aes-128-gcm |
16 bytes |
aes-256-gcm |
32 bytes |
chacha20-ietf-poly1305 |
32 bytes |
plain / none |
0 (no encryption, for testing only) |
Key derivation uses the EVP_BytesToKey algorithm (OpenSSL-compatible) to convert the password string into a fixed-length key.
UDP relay is supported via the ssu scheme:
rustun -L ssu://aes-128-gcm:123456@:8338The simplest authentication method embeds credentials directly in the URL:
rustun -L http://admin:secret@:8080
rustun -L socks5://user:password@:1080When inline credentials are provided on a -L address, the handler requires all incoming clients to authenticate with those exact credentials.
For multiple user accounts, use a secrets file:
rustun -L "http://:8080?secrets=users.txt"The file contains one username password pair per line. Lines starting with # are comments. Blank lines are ignored. A special reload directive sets the live-reload interval:
# Authentication secrets file
reload 30s
admin strongpassword
readonly simplepass
operator 0p3r@t0r!
The authenticator periodically checks the file modification time and reloads automatically.
- HTTP: Uses the
Proxy-Authorization: Basic <base64>header. - SOCKS5: Uses the Username/Password Authentication sub-negotiation (RFC 1929, method
0x02). - SOCKS4: Does not support authentication. When credentials are configured on a listener, the auto-detect handler rejects SOCKS4 connections entirely.
- Relay: Uses a
UserAuthfeature in the relay protocol frame. - Shadowsocks: Authentication is implicit via the shared cipher method and password (any client with the correct password is authenticated).
The bypass system determines which addresses are allowed or denied. A bypass list contains matcher patterns applied to the target address of each connection.
rustun -L "http://:8080?bypass=192.168.0.0/16,*.internal.com,10.0.0.1"Matcher types:
- IP Matcher: Matches a specific IP address exactly. Example:
192.168.1.1. - CIDR Matcher: Matches any IP within a subnet. Example:
10.0.0.0/8. - Domain Matcher: Matches domain names using glob patterns. Example:
*.example.commatchessub.example.com. A leading dot.example.comis equivalent to*.example.comand also matchesexample.comitself.
By default (non-reversed mode), when a target address matches any bypass pattern, the connection is rejected with a 403 Forbidden response.
Reversed mode: Prefix the bypass value with ~ to invert the logic. In reversed mode, only addresses matching the patterns are allowed; all others are rejected.
rustun -L "http://:8080?bypass=~*.allowed.com,10.0.0.0/8"Bypass patterns can also be loaded from a file (one pattern per line) with live-reload support:
# bypass.txt
reload 10s
reverse true
192.168.0.0/16
*.internal.com
10.0.0.0/8
Permissions provide fine-grained access control using the format action:host_pattern:port_range. Multiple permission rules are separated by spaces.
rustun -L "http://:8080?whitelist=tcp:*:80,443&blacklist=tcp:evil.com:*"The access check logic is:
allowed = (whitelist is empty OR whitelist matches) AND (blacklist is empty OR blacklist does NOT match)
Action: The protocol action to match. Currently tcp for TCP connections and udp for UDP.
Host Pattern: A glob pattern for the hostname. * matches any host. *.example.com matches all subdomains.
Port Range: A port specification. Formats: 80 (single port), 80-443 (range), * (all ports), 80,443,8000-9000 (comma-separated mix).
Examples:
# Only allow TCP connections to ports 80 and 443 on any host
whitelist=tcp:*:80,443
# Block all access to evil.com on any port
blacklist=tcp:evil.com:*
# Allow only connections to specific hosts on specific ports
whitelist=tcp:api.example.com,cdn.example.com:80,443
# Combine whitelist and blacklist
whitelist=tcp:*:80,443&blacklist=tcp:malware.com,ads.tracker.com:*When a proxy chain node group contains multiple nodes, rustun selects among them using a configurable strategy.
Multiple nodes are configured either through the ip query parameter or through a peer configuration file:
rustun -L :8080 -F "http://proxy1:8080?ip=proxy2:8080,proxy3:8080&strategy=round&max_fails=3&fail_timeout=30s"| Strategy | Name | Description |
|---|---|---|
| Round Robin | round |
Cycles through nodes in order. This is the default strategy. |
| Random | random |
Selects a node at random for each connection. |
| FIFO | fifo |
Always selects the first available node. Falls back to subsequent nodes only when the first node is marked as dead. |
Nodes are monitored for connection failures. When a node fails, its fail counter is incremented and its fail timestamp is recorded.
max_fails(default: 1): The maximum number of consecutive failures before a node is marked as dead.fail_timeout(default: 30s): The duration after which a dead node is reconsidered.
When all nodes are dead, the strategy operates on the full list regardless of failure state to avoid complete service outage.
The fastest_count parameter enables latency-based filtering. Nodes are periodically probed via TCP connection, sorted by latency, and only the top N fastest nodes are kept in the selection pool:
-F "http://proxy1:8080?ip=proxy2:8080,proxy3:8080&fastest_count=2"rustun includes the ability to generate a random self-signed TLS certificate at startup. This is used when no certificate files are provided.
Place cert.pem (public key) and key.pem (private key) in the current working directory, and rustun loads them automatically. Alternatively, specify paths explicitly:
rustun -L "http+tls://:443?cert=/path/to/cert.pem&key=/path/to/key.pem"By default, clients do not verify the server certificate (matching gost behavior for self-signed certificates). Enable verification with:
rustun -L :8080 -F "http+tls://server:443?secure=true"Specify a CA certificate to restrict which certificates are trusted:
rustun -L :8080 -F "http+tls://server:443?ca=ca.pem"When both client and server are rustun (or gost) instances, SOCKS5 connections negotiate TLS encryption using extended methods 0x80 (TLS) and 0x82 (TLS with authentication). This provides end-to-end encryption without requiring a separate TLS transport layer.
Obfuscation transports disguise proxy traffic as benign protocols to evade deep packet inspection (DPI).
Disguises the connection as an HTTP WebSocket upgrade. The handshake sends a legitimate-looking HTTP GET request with Connection: Upgrade and Upgrade: websocket headers, and the server responds with 101 Switching Protocols.
Server:
rustun -L http+ohttp://:8080Client:
rustun -L :8080 -F http+ohttp://server:8080Disguises the connection as a TLS handshake. The client sends a synthetic TLS ClientHello with a valid SNI extension, and the server responds with a synthetic ServerHello. After the handshake, data flows as raw bytes (not actually TLS-encrypted, but appearing as TLS to passive observers).
Server:
rustun -L http+otls://:8443Client:
rustun -L :8080 -F http+otls://server:8443Obfs4 is a pluggable transport from the Tor Project that provides strong obfuscation against active probing.
Server:
rustun -L obfs4://:443The server prints the client connection string (including the cert parameter). Client:
rustun -L :8080 -F "obfs4://server:443?cert=<base64-cert>&iat-mode=0"TUN (Layer 3) and TAP (Layer 2) virtual network devices allow rustun to operate as a VPN tunnel.
rustun -L "tun://:0?name=tun0&net=10.0.0.1/24&mtu=1350&route=192.168.0.0/16&gw=10.0.0.1"Parameters:
name: Device name (e.g.,tun0).net: Device IP address in CIDR notation (e.g.,10.0.0.1/24).peer: Peer address for point-to-point mode (macOS).mtu: Maximum transmission unit (default: 1350).route: Comma-separated CIDR routes to add.gw: Default gateway IP.
rustun -L "tap://:0?name=tap0&net=10.0.0.1/24&mtu=1500&route=192.168.0.0/16&gw=10.0.0.1"TAP devices operate at the Ethernet frame level (Layer 2), supporting broadcast, ARP, and other Layer 2 protocols.
Configuration files, authenticator secrets files, bypass lists, hosts files, and DNS resolver configurations support automatic live reloading. When the file modification time changes, rustun re-reads and applies the new content without restarting.
The reload period is specified within the file using a reload directive:
# bypass.txt
reload 10s
reverse true
192.168.1.0/24
*.internal.com
# hosts.txt
reload 30s
10.0.0.1 server1
10.0.0.2 server2 alias2
# secrets.txt
reload 1m
admin secretpassword
user userpassword
A reload period of 0 disables reloading. A negative period (set internally by calling stop()) permanently stops reloading.
rustun follows a layered architecture that mirrors the design of gost:
+--------------------------------------------+
| CLI |
| Parses -L, -F, -C, -M, -I, -D flags |
+--------------------+-----------------------+
|
+--------------------v-----------------------+
| Config / Router |
| Creates Server + Handler + Chain per route |
+--------------------+-----------------------+
|
+--------------------v-----------------------+
| Server |
| TCP listener, accepts connections, |
| exponential backoff on accept errors |
+--------------------+-----------------------+
|
+--------------------v-----------------------+
| Handler |
| Protocol-specific connection processing |
| (HTTP, SOCKS4/5, SS, Relay, SNI, DNS, ...) |
+-------+----------------+-------------------+
| |
+-------v------+ +-----v-----------+
| Chain | | Access Control |
| Multi-hop | | Bypass, WL/BL, |
| tunneling | | Authenticator |
+-------+------+ +-----------------+
|
+-------v------+
| NodeGroup |
| Selection, |
| LB strategy, |
| Fail filter |
+-------+------+
|
+-------v------+
| Node |
| Connector + |
| Transporter |
+-------+------+
|
+-------v------+
| Transport |
| TCP, TLS, |
| WS, KCP, ... |
+-------+------+
|
+-------v------+
| Target |
+--------------+
Node: A proxy endpoint parsed from a URL string. Contains protocol, transport, address, credentials, and configuration parameters. The FailMarker tracks connection failures using atomic counters.
NodeGroup: A collection of Nodes used for load balancing. Contains a list of nodes, a selection strategy, and filters. The next() method selects a node by applying filters (FailFilter removes dead nodes, InvalidFilter removes nodes with invalid ports) and then applying the strategy.
Chain: An ordered sequence of NodeGroups forming a proxy chain. The dial() method connects through each node sequentially, establishing tunnels via HTTP CONNECT or SOCKS5 at each hop.
Handler: An async trait with a single method handle(conn: TcpStream) -> Result<(), HandlerError>. Each protocol implements this trait. The AutoHandler delegates to the appropriate protocol handler based on the first byte.
Server: Wraps a TcpListener and a Handler. The serve() method accepts connections in a loop, spawning a new task for each connection. Uses exponential backoff (5ms to 1s) on accept errors.
Transport: The transport() function takes two bidirectional streams and relays data between them using two concurrent copy tasks (one per direction) with 32KB buffers.
Certain features require platform-specific system calls or commands. The table below shows availability by operating system.
| Feature | Linux | macOS | Windows | Other Unix |
|---|---|---|---|---|
Socket mark (-M / SO_MARK) |
Functional | No-op | No-op | No-op |
Interface bind (-I / SO_BINDTODEVICE) |
Functional | No-op | No-op | No-op |
| TCP transparent proxy (redirect) | Functional (SO_ORIGINAL_DST) | Returns error | Returns error | Returns error |
| UDP transparent proxy (tproxy) | Stub (requires tproxy setup) | Returns error | Returns error | Returns error |
| Signal handling (SIGUSR1) | SIGUSR1 dumps diagnostics | SIGUSR1 dumps diagnostics | No-op | SIGUSR1 dumps diagnostics |
| TUN device creation | ip link/ip address/ip route |
ifconfig/route (with peer) |
netsh (requires TAP driver) |
ifconfig/route |
| TAP device creation | ip link/ip address/ip route |
Not supported (returns error) | netsh (requires TAP driver) |
ifconfig/route |
| VSOCK transport | Requires vsock kernel module | Not supported | Not supported | Not supported |
| FakeTCP transport | Requires CAP_NET_RAW | Not supported | Not supported | Not supported |
Platform-specific code is gated using #[cfg(target_os = "...")] attributes. Non-Linux stubs return Ok(()) (for no-ops like socket marks) or Err(...) (for features that cannot function, like transparent proxy).
A multi-stage Dockerfile and a docker-compose.yml are provided for containerized deployment.
docker build -t rustun .# HTTP proxy
docker run -p 8080:8080 rustun -L http://:8080
# SOCKS5 proxy with authentication
docker run -p 1080:1080 rustun -L socks5://admin:pass@:1080
# From a config file
docker run -v ./config.json:/etc/rustun/config.json rustun -C /etc/rustun/config.jsonThe docker-compose.yml defines 10 services covering all major deployment topologies:
docker compose up -d # start all services
docker compose up http-proxy # start a single service
docker compose logs -f # follow logs
docker compose down # stop everythingServices defined: http-proxy (8080), socks5-proxy (1080), auto-proxy (8888), shadowsocks (8338), relay-server (8443), tcp-forward (2222), dns-proxy (5353), sni-proxy (4443), chained-proxy (9080), multi-listener (18080/11080).
Shell scripts in the examples/ directory demonstrate each major feature with real network commands. Each script starts a rustun instance, sends test traffic, and verifies the result.
| Script | Feature Tested |
|---|---|
examples/01_http_proxy.sh |
HTTP and HTTPS CONNECT proxy via curl |
examples/02_socks5_proxy.sh |
SOCKS5 authentication success and rejection |
examples/03_port_forwarding.sh |
TCP forwarding with echo server verification |
examples/04_proxy_chain.sh |
Two-hop chain: HTTP proxy then SOCKS5 proxy |
examples/05_auto_detect.sh |
Single port serving HTTP, SOCKS4, and SOCKS5 simultaneously |
examples/06_config_file.sh |
Multiple services from a single JSON config |
examples/config.json |
Example JSON configuration with multiple routes |
The table below summarizes the implementation depth of each gost feature in rustun.
Full: Handler, connector, and CLI wiring are all implemented with end-to-end integration tests. Handler only: The handler/connector types exist and function, but the transport-level protocol (e.g., the actual KCP/QUIC/SSH wire protocol) delegates to a library crate or is stubbed. Types only: Configuration types, parsing, and platform-specific commands are implemented, but no runtime device creation or wire protocol is present.
| gost Feature | rustun Status | Notes |
|---|---|---|
| HTTP proxy (CONNECT + forward) | Full | Tested end-to-end with auth, bypass, blacklist |
| SOCKS5 proxy | Full | CONNECT, UDP ASSOCIATE, user/pass auth, IPv4/IPv6/domain |
| SOCKS4/4a proxy | Full | IPv4 and domain (4a) tested end-to-end |
| Auto-detect handler | Full | Detects HTTP/SOCKS4/SOCKS5 from first byte |
| Proxy chain (multi-hop) | Full | HTTP CONNECT and SOCKS5 chaining tested |
| TCP direct forwarding | Full | Multi-target load balancing |
| UDP direct forwarding | Full | TCP-to-UDP relay |
| TCP remote forwarding | Full | Reverse tunnel tested end-to-end |
| Relay protocol | Full | Auth features, address features, TCP tested |
| Shadowsocks | Full | Plain cipher tested; AES-GCM/ChaCha20 key derivation implemented |
| SNI proxy | Full | TLS ClientHello parsing, HTTP fallback |
| DNS proxy | Full | TCP-to-UDP query forwarding |
| HTTP/2 transport | Handler only | Delegates to HTTP/1.1 CONNECT; h2 crate types present |
| SSH tunneling | Handler only | Handler and transporter types; russh crate not wired |
| WebSocket transport | Handler only | WS/WSS transporter and handler; binary relay implemented |
| Obfuscation (HTTP) | Full | Client/server handshake tested end-to-end |
| Obfuscation (TLS) | Full | Fake ClientHello/ServerHello tested end-to-end |
| Obfuscation (obfs4) | Types only | Type stubs; requires obfs4 pluggable transport |
| KCP transport | Types only | Full config with JSON parsing and mode presets |
| QUIC transport | Types only | QuicConfig, transporter, listener types; quinn crate present |
| TLS client | Full | Insecure and default connectors |
| TLS server | Handler only | TlsAcceptor wired; handler dispatch pending generalization |
| Transparent proxy (TCP) | Full on Linux | SO_ORIGINAL_DST; error on other platforms |
| Transparent proxy (UDP) | Stub | Requires tproxy integration |
| TUN device | Types + platform commands | Config parsing, route commands (Linux/macOS/Windows/Unix) |
| TAP device | Types + platform commands | Config parsing, route commands (not macOS) |
| FakeTCP | Types only | Requires raw socket access |
| VSOCK | Types only | Address parsing; requires vsock crate on Linux |
| Multiplexing (smux) | Types only | MuxFrame encode/decode; MuxSession lifecycle |
| Live reload | Full | Reloader/Stoppable traits; period_reload file watcher |
| Authentication | Full | Inline credentials, secrets file, LocalAuthenticator reload |
| Bypass | Full | IP/CIDR/Domain matchers, reversed mode, reload |
| Permissions | Full | Whitelist/blacklist with action:host:port rules |
| Load balancing | Full | Round-robin, random, FIFO; FailFilter, InvalidFilter |
| Socket mark | Full on Linux | SO_MARK; no-op on other platforms |
| Interface bind | Full on Linux | SO_BINDTODEVICE; no-op on other platforms |
| Signal handling | Full on Unix | SIGUSR1; no-op on Windows |
| Configuration file | Full | JSON format compatible with gost |
| Module | Source File | Description |
|---|---|---|
| auth | auth.rs | Authenticator trait and LocalAuthenticator with key-value credential store and live-reload support |
| bypass | bypass.rs | Address filtering using IpMatcher, CidrMatcher, and DomainMatcher with glob support; reversed mode |
| chain | chain.rs | Proxy chain with multi-hop HTTP CONNECT and SOCKS5 tunneling, retry logic, and DNS resolution |
| client | client.rs | Client, Connector, and Transporter trait definitions; DialOptions, HandshakeOptions, ConnectOptions |
| config | config.rs | JSON configuration file parser compatible with gost format; RouteConfig and Config types |
| dns_proxy | dns_proxy.rs | DNS proxy handler (TCP) and UDP DNS proxy; forwards queries to upstream resolvers |
| forward | forward.rs | TcpDirectForwardHandler and UdpDirectForwardHandler with multi-target load balancing |
| ftcp | ftcp.rs | FakeTCP transport types and configuration (raw socket UDP-over-TCP framing) |
| handler | handler.rs | Handler async trait, AutoHandler (protocol auto-detection), HandlerOptions, basic_proxy_auth |
| hosts | hosts.rs | Static hostname-to-IP table with aliases and live-reload support |
| http_proxy | http_proxy.rs | HTTP proxy handler (CONNECT tunneling and request forwarding) and HTTP CONNECT connector |
| http2_transport | http2_transport.rs | HTTP/2 connector, transporter (h2/h2c), and handler |
| kcp | kcp.rs | KCP transport configuration with JSON parsing, mode presets, and encryption settings |
| lib | lib.rs | Library crate root; re-exports all public types and constants |
| mux | mux.rs | Stream multiplexing: MuxSession and MuxFrame (encode/decode) for multiplexed transports |
| node | node.rs | Node (URL parsing, parameter access), NodeGroup (load balancing), FailMarker (atomic failure tracking) |
| obfs | obfs.rs | HTTP obfuscation (WebSocket upgrade simulation), TLS obfuscation (ClientHello/ServerHello), obfs4 types |
| permissions | permissions.rs | Permissions (whitelist/blacklist), PortRange, PortSet, StringSet, Can() function |
| quic_transport | quic_transport.rs | QUIC transport and listener using quinn; QuicConfig with keep-alive and encryption settings |
| redirect | redirect.rs | TCP/UDP transparent proxy handlers; SO_ORIGINAL_DST on Linux, error on other platforms |
| relay | relay.rs | Relay protocol connector and handler with user authentication and address features |
| reload | reload.rs | Reloader and Stoppable traits; period_reload file-watching function |
| remote_forward | remote_forward.rs | TcpRemoteForwardHandler and TcpRemoteForwardListener for reverse port forwarding |
| resolver | resolver.rs | DNS resolver with IPv4/IPv6 preference; uses system resolver via tokio::net::lookup_host |
| selector | selector.rs | NodeSelector, Strategy (Round/Random/FIFO), Filter (FailFilter/InvalidFilter/FastestFilter) |
| server | server.rs | TCP server with exponential backoff on accept errors; dispatches to Handler |
| signal | signal.rs | Platform-specific signal handling; SIGUSR1 on Unix, no-op on Windows |
| sni | sni.rs | SNI proxy handler: TLS ClientHello SNI extraction, HTTP fallback |
| sockopts | sockopts.rs | Platform-specific socket options: SO_MARK and SO_BINDTODEVICE (Linux), no-op stubs elsewhere |
| socks4 | socks4.rs | SOCKS4/4a handler (server) and connectors (client); domain resolution for SOCKS4a |
| socks5 | socks5.rs | SOCKS5 handler (server) and connector (client); auth methods, CONNECT, UDP ASSOCIATE, IPv4/IPv6/domain |
| ss | ss.rs | Shadowsocks handler, connector, UDP connector; cipher support (AES-GCM, ChaCha20-Poly1305); EVP_BytesToKey |
| ssh | ssh.rs | SSH tunnel transporter, forward handler, key file parsing, authorized keys parsing |
| tls_listener | tls_listener.rs | TLS server: wraps TCP listener with TLS acceptor using native-tls |
| tls_transport | tls_transport.rs | TLS client: tls_connect, insecure_tls_connector, default_tls_connector |
| transport | transport.rs | Bidirectional async data relay (transport function) with 32KB buffered copy |
| tuntap | tuntap.rs | TUN/TAP configuration, IP route parsing, IPv4 header parsing, platform-specific device creation commands |
| vsock_transport | vsock_transport.rs | VSOCK address parsing, transporter, and listener (Linux VM communication) |
| ws | ws.rs | WebSocket transport (WS/WSS), handler with binary message relay, WsOptions |
Run the full test suite (191 unit tests + 20 integration tests = 211 total):
cargo testRun tests for a specific module:
cargo test chain::tests
cargo test socks5::tests
cargo test http_proxy::tests
cargo test handler::testsRun only the integration tests:
cargo test --test integration_testsRun tests with full output:
cargo test -- --nocaptureRun a single test by name:
cargo test test_chain_dial_through_socks5_proxyThe test suite contains 211 tests organized into unit tests (in each module's #[cfg(test)] block) and integration tests (in tests/integration_tests.rs).
Unit Tests (191): Verify individual functions and data structures in isolation. Examples include node URL parsing, bypass matcher logic, permission rule evaluation, configuration JSON parsing, KCP config mode presets, MuxFrame encode/decode, IPv4 header parsing, VSOCK address parsing, Shadowsocks key derivation, obfuscation handshake building, and platform-specific signal handler creation.
Integration Tests (20): Start real TCP listeners and verify end-to-end protocol behavior. These tests create actual server/client pairs communicating over loopback:
- HTTP proxy CONNECT tunnel with data relay
- HTTP proxy blacklist rejection
- SOCKS5 CONNECT via IPv4
- SOCKS5 authentication (success path and rejection path)
- SOCKS4 CONNECT via IPv4
- SOCKS4a CONNECT via domain name
- TCP direct forwarding with echo verification
- TCP remote forwarding with echo verification
- Relay protocol with fixed target
- Shadowsocks plain cipher end-to-end
- Proxy chain through HTTP proxy
- Proxy chain through SOCKS5 proxy
- AutoHandler HTTP detection
- AutoHandler SOCKS5 detection
- Bypass blocks matched address via SOCKS5
- HTTP obfuscation client-server roundtrip
- TLS obfuscation client-server roundtrip
- Configuration file parsing with all fields
- Complex node URL parsing
- Server handling 10 concurrent connections
Configuration Tests: Verify JSON parsing, field defaults, missing files, malformed input, and field compatibility (Mark, Interface, Retries, Routes).
Timeout and Error Tests: Verify behavior under adverse conditions including connection timeouts to non-routable addresses, DNS resolution failures, and retry exhaustion.
This project is a Rust port of gost, which is licensed under the MIT License.