ws2tcp-local-core
Core Rust library for ws2tcp-local.
This crate contains the proxy service, settings resolution, routing rules, gateway handling, TLS setup, and TCP/WebSocket tunnel implementation used by the CLI, FFI, and GUI frontends.
Usage
use ;
# async
run_proxy first checks the gateway (a websocket handshake on the gateway root,
answered by ws2tcp-router's health check) before loading routing rules or
binding any port. When the check fails, the returned anyhow::Error wraps a
GatewayCheckError:
# async
Gateway authentication
The client authenticates to the gateway with exactly one method at a time, chosen with
Settings::auth_mode. The default is Token. Basic (a health check, then Basic Auth on every
connection) is kept only for compatibility with gateways that have no token authentication; it
logs a warning on startup and is to be phased out. Without credentials authentication is not
enabled: nothing is sent at startup, in either mode, and the proxy starts right away.
At startup:
flowchart TD
S["run_proxy starts"] --> C{"Credentials configured?"}
C -- no --> N["GatewayAuth::None<br/>nothing is sent, the proxy starts right away"]
C -- yes --> M{"Settings::auth_mode"}
M -- "Token (default)" --> L["POST /auth/token with Basic Auth<br/>no health check: the login is the check"]
L -- "200" --> T["GatewayAuth::Token<br/>Bearer access token, renewed as needed"]
L -- "401" --> E1["Startup fails:<br/>GatewayCheckError::Unauthorized"]
L -- "404, 403, connection closed, anything else" --> E4["Startup fails:<br/>GatewayCheckError::LoginFailed"]
M -- "Basic (compatibility only)" --> H["Health check with the Basic Auth credentials"]
H -- "passes" --> B["GatewayAuth::Basic<br/>Basic Auth on every tunnel<br/>a warning says it will be phased out"]
H -- "401" --> E1
H -- "unreachable, not a ws2tcp-router, ..." --> E2["Startup fails:<br/>GatewayCheckError::Failed"]
In token mode there is no fallback to Basic Auth. A background task renews the access token on its own once 80% of its lifetime is used, so that neither the application nor the tunnels have to wait for it. It ends with the session, never asks more often than once a second, and after a failure tries again after 5 seconds, doubling up to 5 minutes:
flowchart TD
A["Wait until 80% of the access token's lifetime is used"] --> B{"Renewed meanwhile,<br/>for example by a tunnel?"}
B -- yes --> A
B -- no --> C["POST /auth/refresh,<br/>or log in again when the refresh token is refused or expired"]
C -- ok --> A
C -- error --> D["Warn, wait 5 s, doubling up to 5 min"]
D --> A
Every tunnel still checks the token when it opens (GatewayAuth::authorization), as a safety
net for a renewal that failed or has not happened yet. It renews first when the token is due,
and concurrent tunnels wait for one renewal, because a refresh token can be used only once:
flowchart TD
A["Tunnel request"] --> B{"Access token due?"}
B -- no --> U["Authorization: Bearer access token"]
B -- yes --> C{"Refresh token still valid?"}
C -- yes --> D["POST /auth/refresh"]
C -- no --> L["POST /auth/token with Basic Auth"]
D -- "200" --> U
D -- "401" --> L
D -- "other error" --> K{"Access token still valid?"}
L -- "200" --> U
L -- "error" --> K
K -- yes --> R["Use it, try to renew again in 5 s"]
R --> U
K -- no --> X["The tunnel request fails"]
U --> W["WebSocket handshake with the gateway"]
W -- "101" --> OK["Tunnel open"]
W -- "401, first time" --> M["Mark the token as rejected"]
M --> A
Tokens are never logged, and the Authorization header values are marked sensitive.
License
MIT. See LICENSE.