Skip to content

Add webtunnel pluggable transport crate#111

Open
PHPCraftdream wants to merge 1 commit into
jmwample:mainfrom
PHPCraftdream:feat/webtunnel-transport
Open

Add webtunnel pluggable transport crate#111
PHPCraftdream wants to merge 1 commit into
jmwample:mainfrom
PHPCraftdream:feat/webtunnel-transport

Conversation

@PHPCraftdream
Copy link
Copy Markdown

Summary

Adds a new webtunnel crate implementing the WebTunnel pluggable transport: TLS + HTTP/1.1 Upgrade: websocket handshake, after which the TCP stream carries raw Tor cell bytes. No WebSocket framing — the Upgrade dance is purely traffic-shape camouflage.

The crate slots in alongside obfs4/o5 as another ptrs::ClientBuilder / ptrs::ClientTransport implementation; nothing in lyrebird, ptrs, or any other existing crate is touched. Only Cargo.toml gains one new workspace member.

What the crate provides

  • WebTunnelBuilder / WebTunnelClient over tokio::net::TcpStream.
  • WebTunnelConfig: parser for the standard webtunnel bridge-line params (url, ver, servername, addr). utls= is accepted but ignored — TLS fingerprint emulation is deferred (would need boring or a custom ClientHello).
  • TLS via tokio-rustls with webpki-roots trust anchors. No ALPN — matches the Go client (NextProtos is nil).
  • HTTP Upgrade request built without obs-fold whitespace (RFC 7230 §3.2.4). Sec-WebSocket-Key is 16 cryptographically random bytes from getrandom.
  • 101 response parser via httparse, lenient about the optional Sec-WebSocket-Accept header (the Go server doesn't return it; NGINX fronts that add it must not break clients).
  • PrefixStream<S> wrapper preserves any bytes the server fused into the same read as the 101 response (otherwise the first Tor cell can silently disappear).

Tests

32 unit tests covering:

  • Config parsing: url/ver/servername/addr extraction, https vs http scheme, missing url error, utls ignored, query strings.
  • HTTP request shape: path from URL, Host header, WebSocket headers, no obs-fold whitespace, Sec-WebSocket-Key length + base64 validity + randomness.
  • 101 response parsing: bare 101, 101 with trailing bytes, 101 with Sec-WebSocket-Accept, non-101 rejection, empty input.
  • PrefixStream: trailing bytes preserved, prefix→inner stream transition, empty-prefix transparent, write passthrough.
  • Builder trait: method_name, options validation, missing-url error.

Cross-referenced sources

Protocol details verified against the Go reference implementation:

  • gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/webtunnel (via pkg.go.dev)
  • github.com/blackyblack/webtunnel mirror — transport/httpupgrade/httpupgrade.go, transport/tls/tls.go

Confirmed: server returns no Sec-WebSocket-Accept, no ALPN, no Sec-WebSocket-Key validation, TCP target is url= (or addr= override) — the bridge-line <addr>:<port> is cosmetic.

Dependencies added (workspace-relative)

tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12"] }
rustls       = { version = "0.23", default-features = false, features = ["ring", "std", "logging", "tls12"] }
webpki-roots = "0.26"
httparse     = "1"
url          = "2"
base64       = "0.22"
getrandom    = "0.2"
thiserror    = "2"

Known follow-ups (out of scope for this PR)

  • utls= TLS fingerprint emulation (requires boring or hand-rolled ClientHello).
  • Live-server integration test (requires a running webtunnel bridge).
  • http:// URL support is in code but untested against a real server.

🤖 Generated with Claude Code

WebTunnel is a Tor pluggable transport that disguises Tor connections
as legitimate HTTPS/WebSocket traffic. The client opens a TLS
connection to the bridge, issues an HTTP/1.1 `Upgrade: websocket`
handshake, and after a `101 Switching Protocols` response the
underlying TCP stream carries raw Tor cell bytes (no WebSocket
framing — the Upgrade dance is purely camouflage).

Crate contents (`crates/webtunnel/`):

- `WebTunnelBuilder` / `WebTunnelClient` implementing `ptrs::ClientBuilder`
  and `ptrs::ClientTransport` over `tokio::net::TcpStream`.
- `WebTunnelConfig` parsing the standard webtunnel bridge-line
  parameters (`url`, `ver`, `servername`, `addr`, with `utls` accepted
  and ignored — TLS fingerprint emulation deferred).
- TLS via `tokio-rustls` (`webpki-roots` for trust anchors, no ALPN
  to match the Go reference implementation).
- HTTP Upgrade request built without `obs-fold` whitespace (RFC 7230
  §3.2.4). `Sec-WebSocket-Key` is 16 cryptographically random bytes
  from `getrandom`.
- 101 response parser via `httparse`, lenient about the optional
  `Sec-WebSocket-Accept` header.
- A `PrefixStream<S>` wrapper preserves any bytes the server fused
  into the same read as the 101 response (previously a silent data
  loss vector — the first Tor cell could be discarded).

32 unit tests cover the config parser, HTTP request shape (path,
Host, headers, no obs-fold), key generation (length, base64 validity,
randomness), 101 response parsing (with and without trailing bytes,
non-101 rejection), the prefix stream (transparent on empty prefix,
write passthrough), and edge cases (HTTP vs HTTPS scheme, query
strings, addr= overriding URL host).

Dependencies: tokio-rustls 0.26, webpki-roots 0.26, httparse 1,
url 2, base64 0.22, getrandom 0.2. Workspace Cargo.toml updated to
include the new crate.

Protocol details cross-checked against the Go reference implementation
at gitlab.torproject.org/tpo/anti-censorship/pluggable-transports/webtunnel
(via pkg.go.dev and the github mirror at blackyblack/webtunnel).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant