Brings the useful work from the old master/pre-release lineage (5c2b5a0)
onto the clean rebuild, since that lineage never had the multi-server/WSS/
Reality removal or any of the 0.4.0 stability work. This is a manual port,
not a cherry-pick — this file's Args/ClientConfig shape had already
diverged too much for the patch to apply mechanically.
- ostp/src/main.rs: flat Args -> clap subcommands (setup, init, generate-key,
links, check, connect, uninstall, update, import, proxy-env,
proxy-env-clear), bridged onto the existing ~500 lines of flag-driven
dispatch via a LegacyArgs struct so none of that logic had to change.
- Fixed a real bug found while porting: GenerateKey's `count` used
short='c', colliding with the global `--config` short (also 'c'), which
clap validates across the whole command tree on first parse() -- a
duplicate short flag there could break every subcommand's parsing, not
just generate-key's.
- `update` now takes `-b/--branch` and `-v/--version` explicitly (was a
bare flag with no way to target a channel or exact version).
- Added the UAC elevation step for TUN mode that this lineage's CLI was
completely missing (`run_client_directly` went straight to creating the
TUN adapter unelevated). Also fixed the elevation check itself: it only
tested `ret <= 32`, but ShellExecuteW returns ERROR_CANCELLED (1223) when
the user clicks "No" on the UAC prompt -- > 32, so a denied prompt was
read as success and the process exited silently without starting the
tunnel. Now ret==1223 is reported explicitly, and a genuine failure logs
GetLastError() so the real Win32 cause is visible next time.
- scripts/install.sh: added -b/--branch alongside the existing -v/--version,
and channel-aware release resolution (nightly/pre-release use their own
rolling tag; stable resolves via the GitHub API's "latest").
- release.yml: added nightly/pre-release branch-push triggers for rolling
prereleases. Fixed the tag_name logic from 5c2b5a0, which mapped
`master` pushes to a release tagged "nightly" -- backwards from master
being the most stable channel. github.ref_name already equals the
branch or tag name that triggered the run, so no per-branch remapping is
needed at all; master is intentionally left off the branch-push list --
it only ever gets real version tags.
Verified: `cargo build -p ostp` succeeds, and `ostp update --help` /
`ostp generate-key --help` / `ostp --help` show the expected flags with no
clap panic.
Co-Authored-By: Claude <noreply@anthropic.com>
security / protocol:
- Derive a PER-KEY junk marker (obfuscation.rs, info byte 0x04) instead of the
global constant [0x88,0x1A,0x93,0x5D]. A fixed marker was a universal DPI
signature identifying ALL OSTP users at once — exactly what the HKDF version
gate avoids for the handshake. Server drops junk via a new DispatchOutcome::Junk
inside the existing key-trial loop (secrets already derived → zero extra cost);
client stamps its own key's marker.
- §E: configurable junk/fragmentation params (junk_pc / junk_ps / frag_chunk / frag_sleep).
GUI (desktop):
- Light theme + toggle, GUI version footer in Settings.
- Fix mouse-wheel scroll on Settings (flex child needed min-height: 0).
- Drop the false "process exclusions unsupported in TUN mode" warning — they DO
work (native_handler maps port->process via GetExtendedTcpTable).
release / infra:
- build.ps1: add -PreRelease (tag CURRENT version as v<ver>-beta.N, no bump, no
master commit); guard the panel build when ostp-control ships no source; bump
the real ostp-gui/package.json instead of the nonexistent ostp-control one.
- release.yml: mark hyphenated tags as GitHub pre-releases; don't hard-fail the
web-panel step when there is no source (use committed dist/).
- Versions aligned to 0.4.1; README license badge BSL 1.1 -> AGPL v3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
In the Closing state the old code force-transitioned to Closed after a
SINGLE inbound packet, so any data/ACKs the peer still had in flight when we
initiated Close were dropped (Closed returns Noop for everything). Stay in
Closing and process inbound normally; handle_inbound already owns the
Close->Closed transition when it actually receives the peer's Close frame.
Also handle Tick in Closing so our own Close frame is retransmitted until
acknowledged.
Ported surgically from 47d44fa — only the Closing-state correctness fix, NOT
that commit's bundled RFC-6298 RTO / congestion rewrite (a behavioural change
to the working base) or the sent_history BTreeMap perf swap (broad hot-path
change for a perf-only gain). cargo test -p ostp-core: 36/36 incl.
test_close_sequence.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Previously start_tunnel returned early if a tunnel was already running, so
changing the server while connected silently kept the OLD connection. Per
the plan ("server change = full stop+start, not hot-reload"), tear down any
existing InProcess/Helper tunnel before starting a fresh one. For the
elevated helper, wait ~1.2s after sending stop so it releases the ostp_tun
adapter before a new helper recreates it (avoids name clashes). start_tunnel
is only invoked on an explicit connect, so restarting here is safe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The plan's share feature for the (single-config) desktop GUI. The QR is
rendered locally so the access key never leaves the device.
- src-tauri: add `qrcode = "0.14"` (features=["svg"]) + `generate_qr`
command (string -> SVG), registered in the invoke handler. Ported from
the current ostp-gui. (cargo check on src-tauri passes.)
- Frontend: "Share" button next to Import builds `ostp://KEY@HOST?sni&type`
from the current config fields, calls generate_qr, and shows a modal with
the QR + a read-only link + Copy. Added i18n keys (en/ru) so the new
data-i18n labels resolve (missing keys would render as the raw key).
Note: Rust side verified via cargo check; the frontend is syntax-checked
(node --check) but not runtime-verified — needs a Tauri build to confirm
visually.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Brand identity per the plan (dark theme + eagle). Ported faithfully from the
current ostp-gui:
- assets/logo.svg + logo.png (the eagle vector/raster).
- .watermark layer: centered, 80%/max-600px, opacity 0.05, behind the
screens (z-index 0, pointer-events none), with a light-theme invert.
- Watermark div added after the ambient blobs in index.html.
Dark theme already existed in styles.css; this adds the eagle the plan calls
for. Backend-independent (pure HTML/CSS/asset).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Anti-DPI obfuscation the project wants to keep, ported from 0.3.x with the
harmful UDP behaviour designed out from the start.
- Junk: before the handshake on a UoT/TCP connection, send 2-5 random
length-prefixed frames (100-1000 B). The server reads each as a frame,
fails to authenticate it, drops it and keeps reading (drop-and-continue),
so junk perturbs DPI flow analysis without breaking the connection. Junk
is NEVER sent over UDP — there each junk would be a lone datagram
indistinguishable from a port scan (probe-flood / wasted CPU / the very
"self-ban" risk the plan calls out). Verified the server has no
probe-based ban, and the unauthorized-probe log is already rate-limited
(§B), so junk-over-UoT produces one debug line, not a flood.
- TCP fragmentation: new `transport.tcp_fragmentation` flag (default off).
When set, the writer splits the first real frame (the handshake) — length
header byte-by-byte then payload in 2-byte chunks with short gaps — so DPI
can't classify the handshake from a single read.
- Ranges are hardcoded for now; §E fine-tuning (configurable Jc/Jmin/Jmax,
S1/S2, H1..H4) is deferred.
Verified by loopback E2E: a UoT client with tcp_fragmentation=true connects
(junk logged as one rate-limited probe, then real handshake accepted) and
curl via SOCKS5 tunnels HTTPS successfully.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The base already derives all secrets from the access key, so derived
secrets were never the gap — the gap was that nothing distinguished a
current handshake from an older-format one, so an old client could still
connect to a new server.
Rather than the plan's literal "plaintext version byte before the crypto
layer" (which would add a constant, DPI-visible marker and defeat the
project's stealth north-star), fold the version INTO the HKDF derivation:
- Add PROTOCOL_VERSION (= 4 for 0.4.0), mixed into the IKM of
derive_all_secrets so a different version yields a completely different
obfuscation key / psk / padding. No marker ever appears on the wire —
the output stays indistinguishable from random.
- A pre-0.4.0 peer derives a different obfuscation key, so the 0.4.0
server cannot recover its handshake header and drops it as an
unauthorized probe. Bump PROTOCOL_VERSION on any future wire break.
Verified:
- cargo test -p ostp-core: 36/36 incl. new test_protocol_version_gates_
old_clients (old-version obf key does NOT recover the session_id).
- Loopback E2E: new client <-> new server connects and tunnels HTTPS
(curl via SOCKS5 returns egress IP).
- Old v0.2.98 client vs new server: handshake times out / aborts, server
accepts 0 clients — exactly the plan's §C criterion.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ported only the fixes that actually apply to the pre-refactor base
(the handshake fixes 6eb7b36/d65af35 fix bugs the 0.3.1 multi-server
refactor introduced into the new outbounds/ostp.rs; the base bridge.rs
already waits for the handshake response with retransmit + NAT64
fallback, so they are intentionally skipped).
- EMFILE (922cf0b): rlimit::increase_nofile_limit at CLI startup.
- Logs (1151726): UoT connect/disconnect → debug; rate-limit the
unauthorized-probe log to one line / ~30s so a junk/probe flood can't
spam the log (and a client running junk-over-UDP can't self-ban).
- Helper lifecycle + bypass routes (b6e78c1):
* ostp-tun-helper forces std::process::exit(0) after run_server so the
WinTun adapter and its metric-0 default route are reclaimed instead
of lingering as a zombie that breaks the next connect.
* windows_route: delete_routes_for_dest() purges stale /32s, dedupe
bypass IPs, and log add failures at warn!.
* windows: retry tun::create through the transient ERROR_INVALID_
PARAMETER window and widen the adapter-index lookup to ~15s.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The "system" TUN stack that shelled out to a bundled tun2socks binary is
long-dead — Flutter already hardcodes the native OSTP stack — and only
bloats the build.
- ostp-jni: drop the tun2socks spawn branch and the tun_child handle;
the native TUN (run_native_tunnel_from_fd) is now unconditional. The
JNI signature is kept ABI-stable (t2sBinPath/localProxy retained but
ignored) to avoid breaking the Kotlin linkage without an Android build.
- Delete the committed 10 MB tun2socks-arm64 asset; drop the tun2socks
download steps from the Android build scripts.
- Remove the dead tun2socks.exe entry from the desktop build_dist.js
(it required a file nothing downloads, breaking the GUI dist build).
- Reword stale tun2socks references in proxy.rs, the GUI config comment,
install.ps1, the release workflow matrix, and CONTRIBUTING.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Clean-rebuild on the stably-working v0.2.98 base. The project's stealth
path is zapret-like (packet obfuscation / junk / fragmentation), NOT
TLS-mimicry, so WSS and Reality are dropped entirely.
- Delete dead orphan files: ostp-client/src/transport/xhttp.rs and
ostp-core/src/crypto/reality.rs (never declared as modules → not even
compiled), plus ostp-core/src/framing/wss.rs.
- Scrub the `wss` transport field from client config/bridge, the unified
CLI (ostp/src/main.rs), the Tauri GUI backend, the GUI frontend
(index.html/main.js), and the Flutter UI; also drop the Reality
pbk/sid plumbing and XTLS auto-search modes from both frontends.
- Drop now-unused client deps (x25519-dalek, chacha20poly1305, hex).
- Bump workspace to version 0.4.0 and license AGPL-3.0; make ostp's
ostp-core dep path-only so the version bump resolves.
- gitignore ostp-control/ (panel assets built separately; a dummy dist
is created for the rust-embed build).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>