Commit Graph

146 Commits

Author SHA1 Message Date
ospab d915efc715 refactor(relay)!: forward transparently instead of re-authenticating clients
The relay authenticated clients itself, with an HMAC handshake and a
background job pulling the access-key list from the target server's management
API. That never worked with a real client and would not have been worth having
if it had.

It could not work: no OSTP client produces those credentials. The TCP path
required an HTTP request (`GET /stream` with `Authorization: Bearer`) and the
UDP path a `timestamp || HMAC` preamble, while the client sends junk frames
followed by length-prefixed frames, and an obfuscated Noise handshake. There
is no HMAC code in ostp-client at all, so every connection was rejected: TCP
answered 404, UDP dropped the datagram.

It was also weak where it applied. The HMAC covered only an 8-byte timestamp,
so a captured signature was a bearer token replayable from any address for the
clock-skew window, with no anti-replay set. And the HTTP handshake put a
literal `GET /stream` on the wire — a greppable signature in a protocol whose
premise is that nothing is recognisable.

Authentication now stays where it is cryptographically meaningful. The target
server already authenticates every session end-to-end via Noise with a PSK
derived from the access key and silently drops what fails; the relay adds
nothing by re-checking, and holding a copy of the key list on a forwarding box
is a liability. The relay makes no security decisions and says so.

What it does need is protection from being a resource sink, so this adds a
session cap, a connection cap, and a token-bucket admission limit on NEW
sessions only. It forwards to one fixed upstream and replies only to the
sender, so it is not a reflector: amplification is one.

Fixes a bug the new end-to-end test caught: upstream sockets were bound as
"[::]:0" and connected to a possibly-IPv4 upstream, which fails wherever
IPV6_V6ONLY defaults on — that is every deployment with an IPv4 target server.
The bind family now follows the resolved upstream.

upstream_api_url/token/sync_interval_secs are accepted and ignored so existing
relay configs keep parsing; the wizard and template no longer emit them, and
`ostp check` flags them as removable.
2026-08-03 18:49:57 +03:00
ospab 4a3fb8b944 fix(client): stop mobile connects from stalling for minutes on dead IPv6
Connecting over a mobile network took ~90s, and under worse conditions did
not complete at all. Three compounding causes, all in the address loop of
perform_handshake_with_id, which walks candidates strictly in order and burns
each one's full retry budget before touching the next:

  - IPv6 was tried FIRST. Carriers routinely hand out IPv6 with no working
    route and blackhole it rather than rejecting, so each AAAA record cost the
    entire 4x1.2s budget with nothing to show; with several of them the
    working IPv4 address was not reached for tens of seconds. The identical
    ordering bug was already fixed on the server's outbound path and in the
    UoT connect - the client's handshake was simply missed.

  - The NAT64 prefix discovery lookup had no timeout. It only ever runs on
    networks that are already misbehaving, exactly where a resolver can hang
    for tens of seconds. Now bounded at 2s, falling back to the well-known
    RFC 6052 prefix, which beats waiting.

  - NAT64 was retried per failing IPv4 address, each time re-running that
    lookup plus another four handshake attempts - for a path that either works
    for the whole network or for none of it. Now attempted once.

Ordering alone is the dominant fix; the other two bound the tail.
2026-07-31 19:26:30 +03:00
ospab f789167a22 feat(congestion): actually pace sends instead of releasing whole windows
pacing_rate had been computed on every ACK since the controller was written
and never read by anything: admission was decided purely by cwnd. But cwnd
bounds how much may be UNACKNOWLEDGED, not how fast it reaches the wire, so a
full window went out back-to-back. On a bottleneck with a deep buffer that
burst is absorbed rather than dropped, and it lands as standing queue — the
mechanism behind the multi-second RTT this protocol has been showing on
mobile. It is also why BBR could not simply be dropped in: BBR's whole model
is "send at the estimated bottleneck rate", which is meaningless without a
pacer underneath it.

Adds a token bucket to CongestionController, charged in on_send so every byte
that reaches the wire pays exactly once — retransmits included, since those
are precisely what must not bypass the limit and pile into a full queue.

Burst allowance is 10ms-at-rate rather than one packet. Pacing intervals here
are fractions of a millisecond, so strict per-packet release would need a
sub-millisecond timer per packet; sizing the burst to the loop's existing
~10ms wakeup lets the configured rate still be saturated. There is a floor of
4 MTU so a cold or collapsed rate estimate can never wedge sending entirely.

Wired into both directions. The client gates its proxy-event branch on it,
and the server's per-session backpressure snapshot reports zero headroom when
the bucket is empty. The download path matters most here — that is the one
carrying video — and it was also still clamped to the old 16384-packet
ceiling (~20 MB outstanding), now aligned with MAX_CWND_PACKETS.

Tested that the bucket denies once drained and refills over time; the second
is what keeps a stalled bucket from wedging sending permanently.
2026-07-31 19:18:42 +03:00
ospab 108bab6a90 fix(client): retry resume reconnects; add a way to hash the panel password
Two unrelated user-reported blockers.

Resume on desktop. The suspend/resume detector fired a single reconnect
attempt roughly half a second after waking — which is exactly when the NIC
has not reassociated yet, so it almost always failed. Failure then fell back
to the ordinary 25s stall heuristic, which keys off a monotonic clock that
does not advance while the machine is asleep, so it could take another 25s of
real uptime to fire, or never fire. The forced reconnect is now sticky:
retried every 3s until a session is actually established, verified via
last_valid_recv rather than by "an attempt was made".

Panel password. `api.password_hash` wants a hash, and nothing in the CLI
could produce one: `ostp init server` emits password_hash: "" and the only
generator was inline in the Server+Panel wizard branch, which is Unix-only —
so on a plain server there was no supported path to working API auth at all.
Adds `ostp hash-password [PASSWORD]`, prompting when the argument is omitted
so the password stays out of shell history. Output verified to match both
handle_login's comparison and a reference SHA-256.
2026-07-31 19:02:03 +03:00
ospab f7e9215331 fix(congestion): stop the bufferbloat spiral behind multi-second RTT stalls
Reported symptom: on mobile the reported RTT jumps to 15-20s (worst case
~150s), video stops loading, and it takes ~5 minutes to recover — or doesn't,
until the user reconnects.

Nothing on the network takes 150 seconds. That delay was our own queue. The
controller only ever treated LOSS as congestion, and mobile carrier buffers
are deep enough to absorb a burst rather than drop it, so the loss signal
never arrived and three things compounded:

  - slow start grew cwnd unbounded, with the client permitting up to 16384
    packets in flight (~20 MB — minutes of queue on a mobile uplink);
  - the resulting standing queue inflated RTT samples, which raised SRTT,
    which raised the adaptive RTO, so retransmits piled into the same queue;
  - backoff multiplied that already-inflated RTO by up to 64x. With RTO_MAX
    at 16s a frame could sit unretransmitted for ~17 minutes.

Reconnecting "fixed" it only because a fresh session resets cwnd to 32.

Three bounds, matching how delay-based controllers handle this:
  - Treat sustained RTT inflation as congestion: leave slow start at 2x the
    observed path floor, actively halve cwnd at 4x. This is the part that
    works where loss never comes.
  - Hard cwnd ceiling of 1024 packets (~1.2 MB), well above any real BDP here
    but far below a queue measured in seconds. The client's in-flight gate is
    lowered to match.
  - Cap the post-backoff retransmit timer at 8s.

Tested for the no-loss inflation case and the ceiling. Both encode the bug
directly, since neither is observable from loss-driven tests.

Not addressed here: pacing is still computed and unused, so sending remains
bursty. That is the next lever and wants real-link validation.
2026-07-31 18:47:14 +03:00
ospab 88e0634f09 fix: two independent causes of the tunnel freezing at 0 b/s
Both produce the same reported symptom - traffic stops dead, the session
itself looks fine, and only a manual reconnect recovers it.

1. protocol.rs: a retry could be charged to a frame that was never sent.
   The retransmit loop is budget-limited per tick, but it bumped `retries`
   and reset `last_sent` for every due frame regardless of whether the
   budget actually allowed a send. The budget is smallest exactly when loss
   is heaviest (it is derived from cwnd, which collapses under loss), so
   under real packet loss frames accumulated "phantom retries" they never
   received - measured at 40 retries charged for 8 frames actually sent in
   one tick. After max_retries+2 such rounds the zombie eviction dropped
   them as dead. That data was never delivered and never would be: the
   stream stalls permanently while pings keep flowing, so nothing upstream
   notices anything is wrong. Retries/timers are now only charged on an
   actual transmit, and the loop stops scanning once the budget is spent
   (sent_history is in send order, so this also keeps retransmit priority
   oldest-first). Covered by a new test that asserts retries charged ==
   datagrams emitted; verified it fails against the old code.

2. bridge.rs: the stall detector was reset by datagrams that never
   validated. `last_valid_recv` - "last VALID recv" - was assigned before
   decryption, so a datagram that failed to decrypt still refreshed it on
   its way to the error return. Anything landing on that port kept the
   client convinced the tunnel was healthy: frames from a session the
   server had already evicted, stale retransmits, or plain garbage from an
   off-path source that knows the ip:port. The 25s background reconnect in
   handle_keepalive therefore never fired. It also made the UI health
   indicator report a dead tunnel as fine, and gave any off-path sender a
   trivial way to pin a client in a dead session indefinitely. Now set only
   after the datagram authenticates and decrypts.
2026-07-30 00:36:17 +03:00
ospab 7473278cc2 fix(client): bound the UoT connect; green aura + self-updating ping on mobile
UoT took 20-30s (sometimes 1-2 min) to come up on mobile. The TCP connect
had no timeout, so it inherited the kernel's SYN retry budget. Callers
resolve every address for the server and deliberately try IPv6 first
(perform_handshake_with_id sorts is_ipv6 to the front); a mobile network
that advertises IPv6 without a working route blackholes the SYN instead of
rejecting it, so the client sat through that entire budget before reaching
the IPv4 address that would have connected immediately. UDP never showed
this because connect() on a UDP socket just sets the default peer and
returns.

Capped at 4s per address, so a blackholed candidate costs seconds and the
next one is tried. Left the IPv6-first ordering alone: it is what makes
IPv6-only and NAT64 networks work, and with the cap its worst case is now
bounded. (A further win would be remembering which family last succeeded
and trying that first, removing even those 4s — not done here.)

Also, per the earlier UI requests:
- The connected state drew its aura, ring, icon and status dot from the
  theme's `secondary`, which is #AAAAAA and reads as plain white, giving no
  confirmation the tunnel was actually up. Now green, reusing the green
  already used for a healthy ping so "green = good" stays consistent.
  Applied at the call sites rather than to the theme, since `secondary`
  also paints routing toggles, the download metric and settings switches.
- Ping now updates itself from the metrics stream that was already
  arriving, instead of needing the "Test Ping" button, and is rendered as a
  compact icon + value.
2026-07-29 20:06:07 +03:00
ospab e46c863ef0 fix(client): stop leaking a socket+task per direct-bypassed SOCKS5 UDP flow
Same class of bug as dbf923f (which fixed the TUN-mode UDP NAT path):
handle_udp_associate's direct-bypass branch spawned spawn_direct_udp_reader
holding its own Arc<UdpSocket> clone with no way to know when the
UDP-associate session it belonged to had ended. Every SOCKS5 UDP session
that ever bypassed traffic direct (an excluded IP/domain) leaked one
socket + one reader task for the rest of the process's life.

Wired a oneshot cancellation channel per spawned reader, held by
handle_udp_associate itself: the channel closes automatically (no explicit
signal needed) the instant that function returns, on every exit path,
telling the reader loop to stop via tokio::select! against the cancel
future.
2026-07-21 18:07:49 +03:00
ospab cddd623ad0 fix(client): coalesce bursty NetworkChanged events on mobile handoff
Root cause of "constantly disconnects on mobile, have to reconnect
manually": Android's ConnectivityManager fires onLost(old) + onAvailable(new)
within milliseconds of each other during a real Wi-Fi<->cellular handoff,
and each one queues its own BridgeCommand::NetworkChanged. Each reconnect is
a full sequential handshake (up to ~1.2s x 4 attempts x mux_sessions) run
synchronously inside the bridge's select-loop iteration - so without
coalescing, the FIRST queued NetworkChanged often starts reconnecting before
the OS has actually finished switching networks, races the dying interface,
and only fails after burning its full attempt budget. Only THEN does the
SECOND (correct) NetworkChanged get to run its own reconnect. A sub-second
handoff was turning into several extra seconds of outage on every
occurrence, and multiple back-to-back handoffs (common walking in/out of
Wi-Fi range) compounded this every time.

Fix: on NetworkChanged, drain any additional same-kind events already
queued before starting the reconnect, so a burst collapses into one attempt
using the freshest signal. A different command found while draining isn't
dropped - it's dispatched immediately (recursing into handle_bridge_cmd)
so nothing queued behind the burst gets lost or reordered incorrectly.
2026-07-21 17:57:39 +03:00
ospab dbf923fb16 fix(client): stop leaking a socket+task per bypassed UDP flow
start_udp_bypass_session (the TUN-mode path for UDP from apps/IPs the user
has excluded from the tunnel) spawned a separate task to read from the
physical-interface-bound socket, holding its own Arc<UdpSocket> clone.
Nothing ever cancelled that task when the outer function returned (e.g.
once session_rx closed) - it just kept running, and its socket clone kept
the OS fd alive, for the lifetime of the process. Every distinct bypassed
UDP flow (any excluded app's DNS query, game session, etc.) leaked one
socket and one task permanently.

The sibling function right below it, start_udp_session, already does this
correctly: one tokio::select! loop combining both directions in a single
task that exits (and drops the socket) as soon as either side closes.
Rewrote start_udp_bypass_session to match that pattern instead of
spawning a detached reader task.
2026-07-18 17:46:14 +03:00
ospab 7e3ada8d4d fix(client): robust reconnection across sleep/resume + no zombie tasks
Addresses the PC-after-sleep failure (app either fully disconnects or gets
stuck "Connecting") and the mobile "must reconnect manually" symptom.

- Zombie receiver tasks: each session spawns a task that loops on recv().
  On a dead connection recv() never returns, so the task (and the socket it
  holds) leaked on every reconnect, piling up across sleep/resume cycles.
  SessionState now owns the task's AbortHandle and aborts it on Drop, so
  replacing sessions tears the old task down. The three duplicated inline
  receiver loops are consolidated into spawn_session_receiver().

(Builds on the tick-storm and resume-detection changes already in tree:
MissedTickBehavior::Skip on all intervals so a post-sleep wake doesn't fire
tens of thousands of catch-up ticks — the 10ms retransmit tick was the
worst — and a wall-clock-gap check that forces one clean reconnect on wake
via handle_keepalive(force=true).)
2026-07-12 01:21:58 +03:00
ospab a69ffae750 docs: update architecture diagram to be more understandable 2026-07-12 00:34:32 +03:00
ospab b7bd8c20a5 docs: update CLI arguments to subcommands 2026-07-10 03:05:30 +03:00
ospab 1d1a1ea5af refactor: remove dead stealth_sni config field across the whole stack
stealth_sni was never actually consumed to construct any wire bytes —
verified dead in bridge.rs (only stored, never read). It implied
TLS/HTTP SNI mimicry that this project deliberately does not do
(zapret-like: packet-level DPI obfuscation only, no protocol
mimicry). Removed from the runtime schema (config.rs, bridge.rs),
both CLI/GUI local config shapes and their JSON templates, the
Flutter profile model/UI/share-link logic, and README feature docs.
migrate.rs now drops the field from legacy configs with a note
instead of carrying it forward.
2026-07-10 01:04:56 +03:00
ospab 5ab6833eab feat(core): time-rotating junk marker — kill the static per-user fingerprint
The junk marker was a per-key CONSTANT sent in plaintext at a fixed offset in
junk frames. Junk is meant to look like random noise (zapret-style), but a
constant prefix is a recognizable per-user structure: an on-path observer
watching one user sees the same 4 bytes on every junk packet, i.e. an OSTP
fingerprint. (The earlier fix only removed the GLOBAL constant.)

Now the marker rotates every 60s window: junk_marker = HKDF(key, ver, 0x04 ||
window). To an observer the prefix changes each window (no fixed signature),
and a captured marker is only valid for ~1 window — the "bit of protection"
against a leaked marker. Only a key holder can compute it, so an outsider still
can't forge a silently-dropped junk packet (and silent-drop is cheaper than
normal processing anyway, so junk spam was never a DoS lever to begin with).

- core: derive_junk_marker(key, window) + current_junk_window() (60s window),
  same version-gated HKDF scheme; junk_marker dropped from DerivedSecrets.
- client: stamps junk with the current window's marker.
- server: checks current AND previous window per key (absorbs ~1 window of
  clock skew) before falling through to unauthorized-probe handling.
- Not a wire break: only junk framing changes; real handshake/data untouched.
  During mixed rollout, unmatched junk merely logs as a probe (cosmetic).
2026-07-09 14:43:40 +03:00
ospab 5dc3a60017 refactor(logging): consolidate all logs into one ostp.log, Windows clears on start
Every process (CLI daemon, GUI, TUN helper) and every subsystem (tracing, the
core event logger, the helper IPC, panic hook) wrote its own file: ostp-cli.log
+ ostp-core.log + ostp-helper.log + ostp-crash.log — a pile per run. Now they
all funnel into a single ostp.log next to the exe.

- logging: LOG_FILE_NAME/log_file_path() as the one source of truth; init_tracing
  gains a `truncate` arg. Truncation is gated twice: Windows-only (cfg!(windows))
  AND daemon-only. One-shot commands (gk/check/init/-V/...) and the elevated TUN
  helper pass truncate=false so they can never wipe a running daemon's log;
  invocation_is_daemon() detects the daemon from argv. On Linux the server always
  appends (history kept, OS-rotated) as requested.
- runner/helper manual writers + panic hook now target log_file_path(), so their
  output lands in the same ostp.log instead of separate files.
2026-07-09 14:32:08 +03:00
ospab d822f48891 refactor(config): one canonical config.json schema for client/server/relay
All three on-disk config.json shapes (client, server, relay) used to be
declared locally inside ostp/src/main.rs, invisible to any other consumer —
which is exactly how ostp_client::migrate ended up matching against loosely
typed serde_json::Value instead of a real schema, with no guarantee its
hand-built output actually matched what the CLI parser expected.

Moved every one of those definitions (AppMode, UnifiedConfig, ServerConfig,
RelayServerConfig, ClientFileConfig, TunConfig, ExcludeConfig, MuxConfig,
TransportConfigRaw, ApiConfig, FallbackCfg, ListenConfig, UserConfig) into
ostp_client::config — the same file that already held the runtime
ClientConfig/OstpConfig/etc. main.rs now imports them instead of
re-declaring them (`ClientFileConfig as ClientConfig` to avoid colliding
with the runtime ClientConfig, which stays separate on purpose: it's the
engine's internal shape — handshake/io timeouts and the like a user never
sets in config.json — built FROM one of these via the mapping in
run_client_directly, not the same thing).

ServerConfig.dns is now Option<serde_json::Value> rather than
Option<ostp_server::dns::DnsConfig> — ostp-client doesn't (and shouldn't)
depend on ostp-server just to name that type. main.rs, which already depends
on both crates, deserializes it right before handing it to run_server().

cmd_migrate now proves its output against this schema before ever writing
to disk (serde_json::from_value::<UnifiedConfig>(migrated)) — a migrator/
schema drift is now a hard error at migrate time, not a corrupted
config.json discovered later. Added a matching unit test
(every_migrated_output_matches_the_canonical_schema) that exercises this
same check on all three migration paths (modular, legacy-flat, server).
2026-07-08 18:58:34 +03:00
ospab 26665a826f feat(client): one authoritative config migrator, manual-only
- fix(cli): stop printing the startup banner ("ostp-cli vX.Y.Z | OS: ...")
  to stderr on every single command invocation. init_tracing() ran
  unconditionally before command dispatch, so `ostp -V`, `ostp gk`, etc. all
  showed it. It's still written to the log file (useful there), just no
  longer echoed via the stderr tracing layer for one-shot commands.

- feat(client): add ostp-client::migrate, the ONE place config migration
  runs. Previously there were three uncoordinated migration paths: a Python
  snippet embedded in scripts/install.sh (only touched server api.* fields,
  ran on every update), the old 0.3.x line's auto-migration on every hot
  reload (silent besides a log warning), and nothing at all for the current
  rebuild. Consolidated into one module covering every config shape that's
  actually existed:
    - v0.3.1-v0.3.21 modular (inbounds/outbounds/routing) -> current flat
      schema, including correctly resolving routing.default_outbound through
      a urltest/selector group to the real server, and reporting (not
      silently dropping) every additional server a multi-server config had.
    - pre-0.3.1 flat configs carrying now-dead fields (tun.wintun_path,
      tun.ipv4_address, transport.wss) -> dropped with an explicit reason,
      everything else passes through untouched.
    - server configs -> backfills api.* defaults and drops legacy api.token
      (ported straight from the install.sh Python, same behavior, correct
      place).
  6 unit tests cover all of the above against realistic fixtures. Wired up
  as `ostp migrate` (was missing from Commands entirely) — no other code
  path calls into this module, so a config's shape only ever changes when
  explicitly asked.

- feat(cli): `ostp import <url>` now asks the same TUN/mux/debug questions
  `ostp connect <url>` always did. Previously import just wrote flat
  defaults to disk with no way to turn any of that on short of hand-editing
  the resulting config.json afterward. Extracted the shared prompt into
  prompt_client_options() so both paths stay in sync.

- chore(install): remove the embedded Python config-migration snippet from
  install.sh; schema migration must never happen implicitly during an
  install/update. Points users at `ostp migrate` instead.
2026-07-08 18:45:04 +03:00
ospab f96daaf57d feat(client): auto-reconnect on network change or any subsystem drop
run_client_core previously ran once: if the OSTP protocol connection, the
TUN device, or the local proxy listener ended for any reason (network
change stranding the socket/adapter on a dead interface, a transient
crash, a drop the inner Bridge-level "TunnelStopped" retry couldn't
recover from), the whole client returned/errored and just stayed down.

Wrapped the existing body (now run_client_once) in an outer supervising
loop: any non-shutdown-requested exit triggers a full clean restart —
fresh DNS resolution, fresh Bridge, fresh TUN/proxy — with backoff
(1/2/5/10/20/30s, resetting once a run has been stable for 60s). Only an
explicit shutdown request stops the loop. connection_state reports
"connecting" during the retry wait so the UI shows reconnecting, not
disconnected.
2026-07-08 17:28:05 +03:00
ospab dbd4ebc4e3 Fix linux build for beta 0.4.1 2026-07-07 17:10:25 +03:00
ospab acab38c551 0.4.1: per-key junk marker, GUI polish, pre-release pipeline
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>
2026-07-07 16:33:57 +03:00
ospab e1bf18e653 core_fixes 2026-06-28 17:11:27 +03:00
ospab db65e3367f §E (base): port junk packets + TCP fragmentation (stream-only)
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>
2026-06-27 17:11:12 +03:00
ospab 7704e0bdb1 §A.3: remove dead tun2socks code (native OSTP TUN is the only path)
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>
2026-06-27 16:29:55 +03:00
ospab 92d6b06d75 §A: remove WSS + Reality (TLS-mimicry); bump to 0.4.0 / AGPL-3.0
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>
2026-06-27 16:29:42 +03:00
ospab 04761fb6a3 Fix memory leaks, hang issues, gui helper token vulns, and log spam 2026-06-16 14:11:37 +03:00
ospab b841053628 fix(jni): add exclusions_rx param to run_native_tunnel_from_fd stub to fix non-Android builds 2026-06-14 01:49:06 +03:00
ospab e0a13702ea fix(tun): resolve OS error 10049 for TCP bypass on Windows and fix 16GB memory leak by bounding smoltcp channels 2026-06-14 01:44:56 +03:00
ospab c36e7373e8 fix(tun): hide verbose split tunneling logs behind debug flag 2026-06-14 01:34:34 +03:00
ospab 3671a83971 chore(tun): add verbose logging for TCP and UDP split tunneling bypass 2026-06-14 01:26:34 +03:00
ospab c7bca41616 chore: fix JNI UoT handler args, center Flutter home screen metrics, update READMEs 2026-06-14 01:04:50 +03:00
ospab 486d745d47 feat(tun): implement process bypass for TCP/UDP and IP bypass for UDP using existing Extended tables 2026-06-14 00:02:08 +03:00
ospab 74b6648db1 fix(tun): fix bypass loop by capturing physical iface before tun route overrides 2026-06-13 23:09:33 +03:00
ospab 55215567dd Fix all compilation errors and suppress all warnings across workspace 2026-06-13 02:30:57 +03:00
ospab a9e4511190 Fix CLI setup permissions, enforce global debug tracing, and fix GUI silent startup crash 2026-06-13 01:25:54 +03:00
ospab 9f35caf4ca Remove built-in DNS server and owndns features 2026-06-10 22:52:35 +03:00
ospab 7bb7d211fa Remove stealth_port entirely and integrate fallback into UoT HTTP handler 2026-06-10 02:26:13 +03:00
ospab 04c31c7f53 feat: implement wintun dynamic downloading, add missing driver frontend modal, fix background logging and UAC helper issues 2026-06-09 01:01:36 +03:00
ospab da238fad5c fix(client): fix compilation error on linux due to server_ip_str 2026-06-07 21:03:52 +03:00
ospab 730eab8553 feat: implement built-in DNS server, adblock and dns leak prevention 2026-06-07 19:55:42 +03:00
ospab 29e9ef739c Refactor: Phase 1 and 2 - Async architecture, JNI fixes, SmolTCP data races, and Tunnel optimizations 2026-06-03 02:06:06 +03:00
ospab 5952fbe3cc fix: rename WindowsProxyGuard to SystemProxyGuard in bridge.rs 2026-06-02 23:01:45 +03:00
ospab c2bc764613 feat: linux auto-sudo and tauri system tray background mode 2026-06-02 22:58:04 +03:00
ospab 0951afa499 feat(linux): implement SystemProxyGuard with GNOME/KDE support and headless proxy prompt 2026-05-31 21:01:28 +03:00
ospab ba5fe72873 feat(cli): add --import, --proxy-env, interactive link prompt, and TUN safety guard for Linux 2026-05-31 20:53:54 +03:00
ospab 472fb8dc11 feat: user configured MTU automatically subtracts 48 for overhead compensation 2026-05-30 22:24:08 +03:00
ospab 8825cf0838 fix: resolve deadlock, multiplexing backpressure, and LTE fragmentation issues 2026-05-30 22:21:12 +03:00
ospab 0fdea7ee21 fix(client): resolve borrow after move error in bridge.rs and clean up warnings 2026-05-30 22:09:23 +03:00
ospab 9f143f730a fix(client): send immediate Ping on connection to avoid 60s delay in UI 2026-05-30 22:07:22 +03:00
ospab 53132036c5 fix(client): flush stale proxy_rx messages on background reconnect to prevent UDP burst drops on mobile networks 2026-05-30 21:55:33 +03:00