Mult Port + TCP Calibration (SSH DEAD)
This commit is contained in:
@@ -4,12 +4,23 @@ DragonTCP carries ordinary TCP traffic inside a compact binary record protocol,
|
||||
normally over TCP port 53. It has three parts:
|
||||
|
||||
* a **Go server** on a Linux host that relays streams to their real destinations,
|
||||
* a **Go client** that exposes a local HTTP/HTTPS proxy,
|
||||
* a **Go client** that exposes a local HTTP/HTTPS proxy and, optionally, an
|
||||
authenticated tunnel-only SSH/SOCKS5 VPN path,
|
||||
* an **Android app** that captures all device traffic with `VpnService`, runs a
|
||||
userspace TCP/IP stack, and feeds everything into that local proxy.
|
||||
|
||||
This document is a complete reference for how all of it works.
|
||||
|
||||
> **Current Android build:** SSH support is retained in the Go client/server source,
|
||||
> but the Android app intentionally runs direct DragonTCP only. Startup performs
|
||||
> server-backed fake-iperf calibration for B, BP, and X: it starts at 32 bytes, grows
|
||||
> candidate UP/DW chunks by 4x toward the fixed 1 MiB ceiling, then refines the first
|
||||
> good/bad boundary to 32-byte precision. For X, the calibrated UP/DW values are
|
||||
> locked for the lifetime of the tunnel: runtime successes do not grow them and
|
||||
> transport failures do not shrink them. The Android main screen shows live UP and
|
||||
> DW chunk candidates/active sizes without requiring the log screen. Android always
|
||||
> uses SHA-256-masked payload framing; the clear-payload control is not exposed.
|
||||
|
||||
---
|
||||
|
||||
## Table of contents
|
||||
@@ -59,11 +70,37 @@ This document is a complete reference for how all of it works.
|
||||
destination server
|
||||
```
|
||||
|
||||
When SSH mode is enabled, the path becomes:
|
||||
|
||||
```text
|
||||
Android apps
|
||||
│ IP packets
|
||||
▼
|
||||
VpnService + TunnelEngine
|
||||
│ SOCKS5 TCP / UDP ASSOCIATE
|
||||
▼
|
||||
127.0.0.1:1080 (dragontcp-client)
|
||||
│
|
||||
│ one persistent SSH connection carried by one DragonTCP stream
|
||||
▼
|
||||
dragontcp-server
|
||||
│
|
||||
├─ SSH direct-tcpip channels ───────────────► TCP destinations
|
||||
│
|
||||
└─ SSH-only dragontcp-udpgw.internal:7400 ─► embedded UDPGW ─► UDP
|
||||
```
|
||||
|
||||
The SSH service is deliberately **tunnel-only**. It supports password
|
||||
authentication and `direct-tcpip`; a `session` channel is only a dummy channel
|
||||
whose shell/exec/PTY requests are refused. There is no usable interactive shell,
|
||||
SCP, or command execution path.
|
||||
|
||||
### 1.2 Design decisions
|
||||
|
||||
**The server is only a relay.** It creates no TUN device, performs no NAT, and
|
||||
needs no `iptables` rules. All packet-level work happens on the phone in
|
||||
userspace. The server is a single static binary with no dependencies.
|
||||
userspace. SSH and UDPGW are embedded in the same Go server binary; no separate
|
||||
OpenSSH or BadVPN daemon is required.
|
||||
|
||||
**Each direction is split into records, not a byte stream.** Every record is
|
||||
carried by its own request/response exchange. That costs some efficiency and buys
|
||||
@@ -87,8 +124,9 @@ flow control a matter of reporting a number.
|
||||
|
||||
## 2. Security model
|
||||
|
||||
**DragonTCP does not provide authenticated encryption. It is not a VPN in the
|
||||
security sense.**
|
||||
**Raw DragonTCP does not provide authenticated encryption.** In the legacy/direct
|
||||
HTTP-proxy mode, DragonTCP is transport obfuscation rather than a cryptographic
|
||||
VPN.
|
||||
|
||||
Legacy payloads are **masked**: XORed with a keystream derived from SHA-256 that
|
||||
varies with session ID, mode, sequence, direction, and block number. New clients
|
||||
@@ -115,6 +153,13 @@ may open sessions. It is not a key and does not affect the mask.
|
||||
HTTPS, not by DragonTCP. Do not assume anything sent over plain HTTP through this
|
||||
tunnel is private.
|
||||
|
||||
**SSH mode changes the client-to-server security layer.** The persistent SSH
|
||||
session provides SSH confidentiality, integrity, server host-key verification
|
||||
(TOFU pinning when configured), and password user authentication inside the
|
||||
DragonTCP carrier. Traffic leaving the DragonTCP server toward its final
|
||||
destination is still protected only by the destination protocol, so HTTPS/TLS
|
||||
remains the correct end-to-end security boundary.
|
||||
|
||||
On the server side, the relay refuses private and special-use destinations by
|
||||
default (§6.5). That restriction is what stops the tunnel being used to reach the
|
||||
host's own localhost services or cloud metadata endpoints.
|
||||
@@ -125,15 +170,20 @@ host's own localhost services or cloud metadata endpoints.
|
||||
|
||||
```text
|
||||
core/
|
||||
go.mod module "dragontcp", Go 1.22, no dependencies
|
||||
go.mod module "dragontcp", Go 1.22; x/crypto for SSH/bcrypt
|
||||
cmd/dragontcp-client/
|
||||
main.go local HTTP/HTTPS proxy, CLI flags
|
||||
chunk.go transport: lanes, probing, adaptation, batching
|
||||
ssh_tunnel.go persistent SSH-over-DragonTCP carrier
|
||||
socks5.go local SOCKS5 TCP + UDP ASSOCIATE front end
|
||||
chunk_test.go
|
||||
cmd/dragontcp-server/
|
||||
main.go listener, DNS cache, address filtering, CLI flags
|
||||
chunk.go session manager, buffering, request dispatch
|
||||
bhttp.go auto-detected BP/BHP1 transport compatibility
|
||||
fake_ssh.go tunnel-only SSH + JSON user CLI
|
||||
udpgw.go embedded BadVPN-compatible UDP gateway
|
||||
internal_targets.go reserved internal-service routing
|
||||
debug.go counters and periodic statistics
|
||||
chunk_test.go
|
||||
internal/wire/
|
||||
@@ -155,18 +205,18 @@ android/
|
||||
vpn/TcpSession.kt userspace TCP endpoint
|
||||
vpn/UdpSession.kt UDP/DNS handling
|
||||
vpn/net/ IP/TCP/UDP headers, checksums, packet builder, DNS
|
||||
vpn/proxy/ProxyClient.kt HTTP CONNECT client
|
||||
vpn/proxy/ProxyClient.kt HTTP CONNECT + SOCKS5/UDP client
|
||||
res/ icon and theme
|
||||
assets/ license texts shipped inside the APK
|
||||
lib/arm64-v8a/libdragontcp_client.so the Go client, packaged as a native lib
|
||||
lib/arm64-v8a/libdragontcp_client.so generated Go client, packaged as a native lib
|
||||
build_apk.ps1 / build_apk.cmd Windows build
|
||||
build_apk.sh Linux/macOS build
|
||||
|
||||
build_core.ps1 / build_core.sh Go builds (Windows / Unix)
|
||||
build_all.sh core + APK in one step (Unix)
|
||||
bin/ built server and client binaries
|
||||
build_all.cmd / build_all.sh core + APK in one step (Windows / Unix)
|
||||
bin/ generated server and client binaries
|
||||
licenses/ full Apache 2.0 text
|
||||
SHA256SUMS digests for the built binaries
|
||||
SHA256SUMS generated digests after Unix core build
|
||||
THIRD_PARTY_NOTICES.md upstream attribution (a license condition)
|
||||
```
|
||||
|
||||
@@ -404,6 +454,29 @@ connection carries a 15-second deadline during the handshake, cleared once
|
||||
relaying starts. Accepts are bounded by `--max-connections` through a slot
|
||||
channel; over the limit the client returns `503`.
|
||||
|
||||
If `--ssh-user` is set, the client also starts a no-auth SOCKS5 listener on
|
||||
`127.0.0.1:1080` by default. That listener is local-only; SSH credentials are
|
||||
used between the DragonTCP client and the embedded SSH service, not by local
|
||||
SOCKS clients.
|
||||
|
||||
* SOCKS5 `CONNECT` opens an SSH `direct-tcpip` channel.
|
||||
* SOCKS5 `UDP ASSOCIATE` opens one SSH `direct-tcpip` channel to the embedded
|
||||
UDPGW and translates SOCKS UDP datagrams to/from BadVPN UDPGW frames.
|
||||
* UDPGW is IPv4-only. Domain-form SOCKS UDP destinations are rejected instead of
|
||||
being resolved locally, preventing an accidental local DNS leak.
|
||||
|
||||
`ssh_tunnel.go` maintains one persistent authenticated SSH connection over one
|
||||
DragonTCP stream and multiplexes the per-flow SSH channels inside it. The SSH
|
||||
packet layer normally emits writes in the tens-of-KiB range; the client therefore
|
||||
uses a bounded 4 MiB carrier send queue and combines consecutive SSH packets into
|
||||
DragonTCP writes of up to 1 MiB (2 ms combine window). On the server, the reserved
|
||||
internal SSH target gets a 25 ms / 512 KiB downstream coalescing policy before a
|
||||
DragonTCP pull response is emitted. This prevents one SSH packet from costing one
|
||||
full DragonTCP WAN round trip while leaving ordinary non-SSH sessions on the
|
||||
original low-latency coalescing behavior. A failed SSH connection is discarded
|
||||
and redialled; individual app flows do not each
|
||||
create their own DragonTCP carrier connection.
|
||||
|
||||
Relaying uses `io.Copy` in both directions and waits for **both** to finish, so
|
||||
TCP half-close is preserved and slow or large responses are not truncated. On
|
||||
Linux this lets the kernel use `splice`.
|
||||
@@ -438,69 +511,55 @@ keepalives, and optionally explicit buffer sizes via `--tcp-buffer`.
|
||||
| `1` (app default) | Auto — starts persistent, retries once after reconnect, and switches that lane to one request per connection only when reuse fails during real traffic |
|
||||
| `N ≥ 2` | Rotate — close and redial after N logical requests |
|
||||
|
||||
### 5.4 Startup profile and path probing
|
||||
### 5.4 Startup header scan and path calibration
|
||||
|
||||
Before normal traffic, the client discovers one fixed wire/header profile. The
|
||||
original direct range remains intact: B provides 32 masks for its mode byte, X
|
||||
provides 96 masks for its `UP`/`OK` magic, and BP provides its original direct
|
||||
profile. The cover range adds both legacy-masked and clear-payload B profiles,
|
||||
clear-payload BP profiles, and covered X profiles. It varies a masked multi-byte
|
||||
preface, all 256 header-mask/first-byte values, and a distributed set of padding
|
||||
lengths: `0, 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 768, 1024, 1400, 2048,
|
||||
4096` bytes.
|
||||
Before normal traffic, the client selects one fixed wire/header profile. Normal
|
||||
Android discovery always keeps SHA-256 payload masking enabled.
|
||||
|
||||
Every candidate is tested with real tunnel traffic: the client opens the tunnel,
|
||||
sends an HTTP request to `http://ip.dr2.site/` over TCP port 80, and accepts the
|
||||
profile only after receiving an HTTP status line. Discovery no longer rejects a
|
||||
profile based only on the synthetic `CPROBE`/`DTP2` exchange.
|
||||
Normal masked discovery now tests **only masks that are mathematically valid for
|
||||
the server's direct first-byte classifier**. It does not run the old covered
|
||||
`0x00`-`0xFF` sweep.
|
||||
|
||||
The safe default is one probe thread. `--wire-probe-threads` can allow 1–16
|
||||
in-flight attempts, while `--wire-probe-delay` (default 1 second) is still the
|
||||
global minimum time between new connection starts. Increasing the thread count
|
||||
therefore permits slow attempts to overlap; it does not launch all candidates at
|
||||
once. One thread is recommended on carriers with connection-rate filtering.
|
||||
|
||||
The winner is cached for the lifetime of the client process and is used
|
||||
unchanged for every connection and record. Padding contents may be fresh random
|
||||
bytes, but the selected length and profile never change until restart; there is
|
||||
no per-packet profile mutation.
|
||||
|
||||
`--wire auto` searches 1,153 distributed B/BP/X profiles. Pinning `--wire b`
|
||||
searches 544 B profiles, `--wire bp` searches 257 BP profiles, and `--wire x`
|
||||
searches 352 X profiles. B and BP try a clear-payload candidate first, followed
|
||||
immediately by a legacy direct fallback. A successful selection is logged as:
|
||||
For B and BP the low three bits carry the request mode (`0..4`), so the mask's
|
||||
low three bits must be zero. That produces exactly 32 candidates:
|
||||
|
||||
```text
|
||||
wire probe: selected=x/mask-6b/cover-91e7/pad-64 completed=141 launched=142 elapsed=42.8s target=http://ip.dr2.site/ validated=true threads=1 fixed_until_restart=true
|
||||
00 08 10 18 20 28 30 38 ... E8 F0 F8
|
||||
```
|
||||
|
||||
Before the first real connection, `getPathProfile` measures the path once and
|
||||
caches the result for **30 minutes**, keyed by server address, token, and size
|
||||
bounds.
|
||||
For X the first byte is `'U' ^ mask`; the server classifies X only when its low
|
||||
three bits are 5, 6, or 7. That produces exactly 96 valid direct X masks. Auto
|
||||
interleaves only these valid B/BP/X candidates in numeric mask order, for 160
|
||||
candidates total instead of roughly 900 probes.
|
||||
|
||||
Upload and download are probed **independently and concurrently**, each by binary
|
||||
search over a fixed ladder:
|
||||
Header discovery uses one tiny **server-local protocol probe**. It does not open
|
||||
`ip.dr2.site` or another Internet target. This keeps the test focused on whether
|
||||
the DragonTCP framing survives the carrier and avoids mixing server-outbound
|
||||
Internet conditions into header selection.
|
||||
|
||||
The Android default is one probe worker and a 100 ms launch spacing. The winner
|
||||
is cached for the lifetime of the client process and reused by every reconnect.
|
||||
A successful selection is logged as:
|
||||
|
||||
```text
|
||||
32, 64, 128, 256, 512, 1024, 1200, 1280, 1320, 1350, 1360, 1380, 1400,
|
||||
1450, 1600, 2048, 3205, 4096, 8192, 16384, 32768, 65536, 98304,
|
||||
131072, 262144, 524288, 786432, 1048576
|
||||
wire probe: selected=x/mask-37/cover-3792/pad-0/masked header_mask=37 completed=57 launched=57 elapsed=... protocol_probe=true threads=1 fixed_until_restart=true
|
||||
[D-TCP] phase=AUTH state=success wire=x header_mask=37 clear_payload=false ...
|
||||
```
|
||||
|
||||
The ladder is filtered to `[--chunk-min, --chunk-max]`, with the configured bounds
|
||||
added if missing. Binary search costs roughly five probes instead of twenty-eight,
|
||||
and avoids having to *fail* at every size on the way down during real traffic.
|
||||
Candidate counts remain bounded: pinned B has 288 candidates, BP 257, X 352,
|
||||
and Auto 897. The extra direct nonzero B/X profiles are retained after the
|
||||
full-range covered scan for compatibility with older servers.
|
||||
|
||||
A third probe sends eight keepalives on one connection to decide whether request
|
||||
reuse survives. Each probe uses a fresh random session ID with a timeout capped at
|
||||
2.5 s. If the search does not finish within 20 s, the client falls back to
|
||||
32 768 up / 1 350 down.
|
||||
After the header is fixed, UP and DW chunk calibration runs separately. It
|
||||
starts at 32 bytes, grows by 4x toward 1 MiB, and after the first failed size
|
||||
refines the last good/bad interval to **32-byte precision**. Boundary decisions
|
||||
use 2-of-3 confirmation on fresh sequential connections. Calibration is always
|
||||
single-poller/single-outstanding-request so the measured ceiling is not inflated
|
||||
by aggregate parallel traffic.
|
||||
|
||||
The outcome is logged once:
|
||||
|
||||
```text
|
||||
path probe: upload=32768 download=1400 persistent=true
|
||||
```
|
||||
Connection-level I/O timeout is not treated as proof that the candidate chunk
|
||||
is too large. A timeout kills/replaces the physical connection instead of
|
||||
forcing a size downgrade.
|
||||
|
||||
### 5.5 Adaptive record sizing
|
||||
|
||||
@@ -704,6 +763,16 @@ these special-use prefixes:
|
||||
|
||||
`--allow-private` disables the whole check. Leave it off on a public server.
|
||||
|
||||
The SSH tunnel uses the **same** destination policy for `direct-tcpip`, so adding
|
||||
SSH does not automatically expose localhost, RFC1918 services, or cloud metadata
|
||||
addresses. Two reserved names are exceptions with intentionally narrow scope:
|
||||
|
||||
* `dragontcp-ssh.internal:2222` is reachable by raw DragonTCP so the client can
|
||||
perform the SSH handshake against the loopback-only embedded SSH listener.
|
||||
* `dragontcp-udpgw.internal:7400` is in a separate SSH-only registry. It is
|
||||
reachable only after SSH authentication and cannot be opened directly by a
|
||||
raw DragonTCP stream.
|
||||
|
||||
If every resolved address is blocked, the error names them. Dial timeout is 10 s,
|
||||
with 30-second keepalives on the resulting connection.
|
||||
|
||||
@@ -717,6 +786,21 @@ A sweep every 30 s closes sessions idle longer than `--chunk-session-timeout`
|
||||
requests, data and wait records, sessions opened and closed, active sessions.
|
||||
`--debug-chunks` logs every record and is very verbose.
|
||||
|
||||
### 6.7 Tunnel-only SSH and user database
|
||||
|
||||
The embedded SSH listener defaults to `127.0.0.1:2222`. It is not published as a
|
||||
normal Internet-facing SSH port; DragonTCP reaches it through the reserved
|
||||
internal target above. Host keys are Ed25519 and are generated automatically on
|
||||
first start with mode `0600`.
|
||||
|
||||
SSH users are stored in `dragontcp-users.json`. Passwords are bcrypt hashes; the
|
||||
database can set an expiry and a maximum number of simultaneous persistent SSH
|
||||
connections per user. The same server binary acts as the account-management CLI
|
||||
with `--ssh-user-add`, `--ssh-user-delete`, and `--ssh-user-list`.
|
||||
|
||||
The integrated UDPGW listens on `127.0.0.1:7400` by default. It implements the
|
||||
BadVPN UDPGW framing used by the SSH VPN path and is not a public UDP service.
|
||||
|
||||
---
|
||||
|
||||
## 7. The Android app
|
||||
@@ -732,9 +816,9 @@ Package `com.dragontcp.client`, `minSdk 29`, `targetSdk 29`, arm64 only.
|
||||
| `LogActivity` / `AppLog` | Java | Live log viewer over a 600-line in-memory ring |
|
||||
| `TunnelEngine` | Kotlin | TUN read/write loops, session tables, housekeeping |
|
||||
| `TcpSession` | Kotlin | Userspace TCP endpoint, one per 4-tuple |
|
||||
| `UdpSession` | Kotlin | UDP handling; in practice DNS only |
|
||||
| `UdpSession` | Kotlin | DNS/direct-mode UDP handling and SOCKS5 UDP in SSH mode |
|
||||
| `net/*` | Kotlin | Header parsing, checksums, packet construction, DNS parsing |
|
||||
| `ProxyClient` | Kotlin | HTTP CONNECT client against the local Go proxy |
|
||||
| `ProxyClient` | Kotlin | HTTP CONNECT or SOCKS5 CONNECT/UDP ASSOCIATE against the local Go core |
|
||||
|
||||
### 7.2 Process model
|
||||
|
||||
@@ -759,8 +843,9 @@ Startup:
|
||||
3. `DragonService` starts in the foreground with a notification carrying a STOP
|
||||
action.
|
||||
4. The Go core is spawned with flags built from the saved settings.
|
||||
5. The service polls `127.0.0.1:8080` for up to 10 s (150 ms connect timeout,
|
||||
100 ms between attempts) until the proxy accepts.
|
||||
5. The service polls the selected local proxy for up to 10 s (150 ms connect
|
||||
timeout, 100 ms between attempts): `127.0.0.1:8080` in direct mode or
|
||||
`127.0.0.1:1080` in SSH mode.
|
||||
6. The TUN interface is established.
|
||||
7. `TunnelEngine` starts; state broadcasts flip to `CONNECTED`.
|
||||
|
||||
@@ -863,22 +948,27 @@ holds the shared output buffer.
|
||||
|
||||
### 7.7 UDP and DNS
|
||||
|
||||
With an HTTP CONNECT upstream, general UDP cannot be carried. `UdpSession`
|
||||
therefore handles **DNS only**; every other UDP flow is dropped, which makes QUIC
|
||||
fail and pushes apps back to TCP.
|
||||
Direct mode still uses an HTTP CONNECT upstream, so general UDP cannot be
|
||||
carried there. `UdpSession` handles DNS and drops other UDP flows, which makes
|
||||
QUIC fail and pushes apps back to TCP.
|
||||
|
||||
DNS queries are converted to **DNS-over-TCP** (RFC 7766: a 2-byte big-endian
|
||||
length prefix followed by the message) and sent to **1.1.1.1:53** through the
|
||||
tunnel. One tunnel is opened per query with a 10-second timeout, and the session
|
||||
ends after the answer is written back to the TUN as a synthesised UDP datagram.
|
||||
SSH mode enables `ProxyProfile.udpOverSocks`. `UdpSession` opens a SOCKS5 UDP
|
||||
association to `127.0.0.1:1080`; the Go client converts those local SOCKS UDP
|
||||
datagrams into BadVPN-compatible UDPGW frames and sends them through an SSH
|
||||
`direct-tcpip` channel. This gives the Android VPN general IPv4 UDP support in
|
||||
SSH mode without running the external `badvpn-udpgw` executable.
|
||||
|
||||
DNS is forced to **1.1.1.1** by the TUN configuration and follows the selected
|
||||
mode. In direct mode the adapter can use DNS-over-TCP through HTTP CONNECT; in
|
||||
SSH mode DNS UDP traffic can use the SOCKS5/UDPGW path.
|
||||
|
||||
Responses are also parsed to populate `HostRegistry`, a 512-entry access-ordered
|
||||
LRU mapping IP → hostname. It exists so diagnostics can say `github.com:443`
|
||||
rather than `140.82.121.4:443`.
|
||||
|
||||
The stack retains hooks for direct (non-tunnelled) DNS, SOCKS5 UDP association,
|
||||
DNS blocking, and per-app attribution. In this build `AppResolver`, `DnsBlocker`,
|
||||
and `TunnelLog` are deliberate no-op stubs that preserve the upstream API.
|
||||
The stack retains hooks for direct (non-tunnelled) DNS, DNS blocking, and
|
||||
per-app attribution. `AppResolver`, `DnsBlocker`, and `TunnelLog` remain
|
||||
deliberate no-op stubs that preserve the upstream API.
|
||||
|
||||
### 7.8 Packet construction
|
||||
|
||||
@@ -893,23 +983,27 @@ checksum of zero is written as `0xFFFF` per RFC 768.
|
||||
| UI field | Default | Flag passed to the core |
|
||||
|---|---|---|
|
||||
| Server | — | `--server-host` |
|
||||
| Port | 53 | `--server-port` |
|
||||
| Port start | 53 | `--server-port-start` |
|
||||
| Port end | 53 | `--server-port-end` |
|
||||
| Token | empty | `--token` (omitted entirely when blank) |
|
||||
| Wire | auto | `--wire`; Auto, B, BP, and X are selectable |
|
||||
| Probe delay (ms) | 1000 | `--wire-probe-delay`; global minimum delay between profile connection starts |
|
||||
| Probe delay (ms) | 100 | `--wire-probe-delay`; spacing between sequential valid-mask probes |
|
||||
| Probe threads | 1 | `--wire-probe-threads`; maximum concurrent profile attempts, 1–16 |
|
||||
| Max chunk | 1048576 | `--chunk-max` **and** `--chunk-start` |
|
||||
| Min chunk | 32 | `--chunk-min` |
|
||||
| Batch max | 1 | `--chunk-concurrency` |
|
||||
| Batch min | 1 | `--chunk-concurrency-min` |
|
||||
| Reconnect every | 1 (auto) | `--chunk-reconnect-every` |
|
||||
| Timeout (s) | 5 | `--chunk-timeout` |
|
||||
|
||||
Fixed by the service: `--listen-host 127.0.0.1`, `--listen-port 8080`,
|
||||
`--transport chunk`, `--chunk-grow-after 16`, `--chunk-adapt-log=true`.
|
||||
`--transport chunk`, `--chunk-min 32`, `--chunk-max 1048576`,
|
||||
`--chunk-start 1048576`, `--chunk-grow-after 16`, and `--chunk-adapt-log=true`.
|
||||
The Android app does not expose manual record-size or clear-payload controls; startup
|
||||
calibration determines independent UP/DW ceilings automatically.
|
||||
|
||||
Settings are laid out in four cards: CONNECTION, RECORD SIZE, DOWNLOAD BATCH,
|
||||
and ADVANCED. The batch card carries a live hint that restates the current setting in
|
||||
Settings are laid out in CONNECTION, WIRE, LIVE TRANSPORT, DOWNLOAD BATCH,
|
||||
and ADVANCED cards. The client scans Port start → Port end in ascending order. A port is
|
||||
shown as WORKING PORT only after a TCP connection and a real DragonTCP wire/header
|
||||
probe both succeed; a merely open TCP port is not accepted. The batch card carries a live hint that restates the current setting in
|
||||
words as you type, so the mode is never ambiguous:
|
||||
|
||||
```text
|
||||
@@ -927,9 +1021,8 @@ before spawning the core, and records the resulting mode in the log:
|
||||
Download batch: pinned at 5 records per request (never adapts)
|
||||
```
|
||||
|
||||
Validation ranges: port 1–65535, max chunk 32–1048576, min chunk 32–max chunk,
|
||||
batch values 1–256 with `min ≤ max`, reconnect 0–1000000,
|
||||
timeout 1–120, probe delay 200–30000 ms, probe threads 1–16.
|
||||
Validation ranges: both ports 1–65535 with `start ≤ end`, batch values 1–256 with `min ≤ max`,
|
||||
reconnect 0–1000000, timeout 1–120, probe delay 50–30000 ms, probe threads 1–16.
|
||||
|
||||
### 7.10 Logs
|
||||
|
||||
@@ -944,6 +1037,15 @@ is written to disk.
|
||||
|
||||
### 8.1 Windows
|
||||
|
||||
For a complete build, double-click `build_all.cmd` at the repository root, or run:
|
||||
|
||||
```cmd
|
||||
build_all.cmd
|
||||
```
|
||||
|
||||
It builds the Go core first and then the signed Android APK. For CI/non-interactive
|
||||
use, `build_all.cmd --no-pause` suppresses the final pause.
|
||||
|
||||
No Gradle and no Android Studio required; `android\build_apk.ps1` drives the SDK
|
||||
command-line tools directly.
|
||||
|
||||
@@ -982,7 +1084,7 @@ Go binaries alone:
|
||||
```powershell
|
||||
.\build_core.ps1 # android .so + linux client + linux amd64/arm64 servers
|
||||
.\build_core.ps1 -ClientOnly # just the .so
|
||||
.\build_core.ps1 -AndroidLibDir <dir>
|
||||
.\build_core.ps1 -AndroidLibRoot <dir>
|
||||
```
|
||||
|
||||
### 8.2 Linux and macOS
|
||||
@@ -998,13 +1100,15 @@ pointing at a Kotlin distribution bundling `lib/kotlinx-coroutines-core-jvm.jar`
|
||||
it defaults to `~/.sdkman/candidates/kotlin/current`. Unlike the PowerShell script
|
||||
it downloads nothing and does not build the Go core for you.
|
||||
|
||||
`build_core.sh` does not build the Linux **client**; `build_core.ps1` does. Build
|
||||
it by hand if you need it.
|
||||
`build_core.sh` builds the Android arm64 client plus Linux amd64 client and
|
||||
amd64/arm64 servers. The PowerShell build can additionally build other Android
|
||||
ABIs when an NDK is available.
|
||||
|
||||
Go builds by hand:
|
||||
|
||||
```bash
|
||||
cd core
|
||||
go mod download
|
||||
go test ./...
|
||||
|
||||
GOOS=android GOARCH=arm64 CGO_ENABLED=0 \
|
||||
@@ -1016,13 +1120,17 @@ GOOS=linux GOARCH=amd64 CGO_ENABLED=0 \
|
||||
-o ../bin/dragontcp-hybrid-server-linux-amd64 ./cmd/dragontcp-server
|
||||
```
|
||||
|
||||
CGO is off for every target, so no NDK and no C toolchain are needed.
|
||||
`golang.org/x/crypto` is the only additional Go module used by the fake-SSH
|
||||
feature (SSH protocol and bcrypt password hashes). CGO is off for every target,
|
||||
so no NDK and no C toolchain are needed for arm64. The first Go build needs
|
||||
network access to download the module unless it is already present in the Go
|
||||
module cache.
|
||||
|
||||
### 8.3 What the build does
|
||||
|
||||
1. **Resolve the toolchain and repo root**, and print what was chosen.
|
||||
2. **Build the native core** if the `.so` is missing or `-BuildCore` was passed,
|
||||
by calling `<repo>\build_core.ps1 -ClientOnly -AndroidLibDir <app>\lib\arm64-v8a`.
|
||||
by calling `<repo>\build_core.ps1 -ClientOnly -AndroidLibRoot <app>\lib`.
|
||||
3. **`aapt package`** — compile `res/`, pack `assets/`, bind the manifest against
|
||||
`android.jar`, producing `resources.ap_`.
|
||||
4. **Kotlin** — compile every `.kt` under `src\` to `build\kclasses`, JVM target
|
||||
@@ -1064,6 +1172,52 @@ keystore out of version control.
|
||||
|
||||
### 9.1 Server
|
||||
|
||||
The easiest way to manage tunnel users is the interactive menu:
|
||||
|
||||
```bash
|
||||
./dragontcp-menu.sh
|
||||
```
|
||||
|
||||
or directly:
|
||||
|
||||
```bash
|
||||
./bin/dragontcp-hybrid-server-linux-amd64 --ssh-menu
|
||||
```
|
||||
|
||||
The menu can create, delete, list, reset, and renew users. **Creating or resetting
|
||||
a user automatically generates a strong password**, so there is no password
|
||||
prompt and no environment variable to export. The generated password is shown
|
||||
once; only its bcrypt hash is stored in `dragontcp-users.json`. Renewing expiry
|
||||
or changing the connection limit does not change the existing password.
|
||||
|
||||
The menu defaults are 30 days and 1 simultaneous SSH connection. Enter `0` for
|
||||
no expiry or unlimited connections. `dragontcp-menu.sh` automatically selects
|
||||
the AMD64 or ARM64 Linux server binary and uses the project-root
|
||||
`dragontcp-users.json`. Set `DRAGONTCP_USERS_FILE=/path/to/users.json` if your
|
||||
running server uses a different account database.
|
||||
|
||||
The original non-interactive CLI is still available for automation. Using an
|
||||
environment variable avoids putting a chosen password directly in shell history:
|
||||
|
||||
```bash
|
||||
export DRAGONTCP_SSH_PASS='CHANGE_ME'
|
||||
./bin/dragontcp-hybrid-server-linux-amd64 \
|
||||
--ssh-user-add alice \
|
||||
--ssh-user-password-env DRAGONTCP_SSH_PASS \
|
||||
--ssh-user-days 30 \
|
||||
--ssh-user-max-connections 1
|
||||
unset DRAGONTCP_SSH_PASS
|
||||
```
|
||||
|
||||
List or delete accounts non-interactively:
|
||||
|
||||
```bash
|
||||
./bin/dragontcp-hybrid-server-linux-amd64 --ssh-user-list
|
||||
./bin/dragontcp-hybrid-server-linux-amd64 --ssh-user-delete alice
|
||||
```
|
||||
|
||||
Then run the server normally:
|
||||
|
||||
```bash
|
||||
sudo ./dragontcp-hybrid-server-linux-amd64 \
|
||||
--port 53 --port-alt 80 --chunk-max 1048576
|
||||
@@ -1090,6 +1244,11 @@ primary listener running. `sudo` is normally needed because both defaults are
|
||||
privileged. If another service owns either port, free it or select a different
|
||||
port. No TUN device or NAT rules are required.
|
||||
|
||||
The embedded SSH listener and UDPGW bind loopback by default (`127.0.0.1:2222`
|
||||
and `127.0.0.1:7400`). Do **not** expose those ports publicly just to make the
|
||||
Android mode work: the intended path is through DragonTCP's reserved internal
|
||||
SSH target.
|
||||
|
||||
### 9.2 Client CLI
|
||||
|
||||
```bash
|
||||
@@ -1106,27 +1265,49 @@ adaptive_chunk=true start=1048576 min=32 max=1048576 grow_after=16 pollers=1 \
|
||||
batch=5-5(pinned) reconnect_every=0 timeout=5s
|
||||
```
|
||||
|
||||
For tunnel-only SSH mode:
|
||||
|
||||
```bash
|
||||
export DRAGONTCP_SSH_PASS='CHANGE_ME'
|
||||
./dragontcp-hybrid-client-linux-amd64 \
|
||||
--server-host YOUR_SERVER_IP --server-port 53 \
|
||||
--ssh-user alice \
|
||||
--ssh-password-env DRAGONTCP_SSH_PASS \
|
||||
--ssh-hostkey-pin-file "$HOME/.dragontcp-ssh-hostkey.pin"
|
||||
```
|
||||
|
||||
The first successful connection writes the SSH host-key SHA-256 fingerprint to
|
||||
the pin file. Later connections fail if that key changes. Point applications at
|
||||
`socks5://127.0.0.1:1080`; TCP is multiplexed as SSH `direct-tcpip` and UDP uses
|
||||
SOCKS5 UDP ASSOCIATE -> SSH -> UDPGW.
|
||||
|
||||
### 9.3 Android
|
||||
|
||||
Install the APK, enter the server address, grant the VPN prompt, connect.
|
||||
A reasonable starting point:
|
||||
Install the APK, enter the server address, grant the VPN prompt, and connect.
|
||||
The Android build is direct DragonTCP only; SSH fields and manual record-size
|
||||
controls are intentionally absent. A reasonable starting point is:
|
||||
|
||||
```text
|
||||
Server: YOUR_SERVER_IP
|
||||
Port: 53
|
||||
Port start: 53
|
||||
Port end: 53
|
||||
Token: (match the server, or leave blank)
|
||||
Max chunk: 1048576
|
||||
Min chunk: 32
|
||||
Wire: Auto
|
||||
Batch max: 1
|
||||
Batch min: 1
|
||||
Pollers: 1
|
||||
Reconnect every: 1
|
||||
Timeout (s): 5
|
||||
Probe delay (ms): 1000
|
||||
Probe delay (ms): 100
|
||||
Probe threads: 1
|
||||
|
||||
Automatic chunk calibration: 32 B -> 1 MiB, 4x ascent, 32 B refinement
|
||||
Payload framing: SHA-256 masked
|
||||
```
|
||||
|
||||
Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
Use the **LIVE TRANSPORT** card to watch the selected WORKING PORT plus UP/DW
|
||||
calibration/runtime sizes without opening the log screen. **OPEN LOGS** still shows the detailed
|
||||
AUTH/CALIBRATION/ACTIVE state transitions and fake-iperf results. The app package
|
||||
is excluded from its own VPN so the DragonTCP core does not loop back into the TUN.
|
||||
|
||||
---
|
||||
|
||||
@@ -1152,18 +1333,37 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| `--debug` | `false` | Session/connect/error logs plus periodic stats |
|
||||
| `--debug-chunks` | `false` | Log every record — very verbose |
|
||||
| `--debug-stats-interval` | `5s` | Statistics period; 0 disables |
|
||||
| `--ssh-enable` | `true` | Enable embedded tunnel-only SSH |
|
||||
| `--ssh-listen` | `127.0.0.1:2222` | Embedded SSH listener; keep loopback for normal use |
|
||||
| `--ssh-internal-host` | `dragontcp-ssh.internal` | Reserved DragonTCP name for the embedded SSH service |
|
||||
| `--ssh-host-key` | `dragontcp_ssh_host_key` | Ed25519 host-key file, generated if absent |
|
||||
| `--ssh-users` | `dragontcp-users.json` | JSON SSH user database |
|
||||
| `--ssh-user-add` | empty | Create/update a tunnel user and exit |
|
||||
| `--ssh-user-delete` | empty | Delete a tunnel user and exit |
|
||||
| `--ssh-user-list` | `false` | List tunnel users and exit |
|
||||
| `--ssh-menu` | `false` | Interactive create/delete/list/reset/renew user menu and exit |
|
||||
| `--ssh-user-password-env` | empty | Env var holding password for `--ssh-user-add` |
|
||||
| `--ssh-user-days` | `0` | Account lifetime in days; 0 = no expiry |
|
||||
| `--ssh-user-max-connections` | `1` | Persistent SSH sessions allowed per user; 0 = unlimited |
|
||||
| `--udpgw-enable` | `true` | Enable embedded BadVPN-compatible UDPGW |
|
||||
| `--udpgw-listen` | `127.0.0.1:7400` | UDPGW TCP listener; keep loopback |
|
||||
| `--udpgw-internal-host` | `dragontcp-udpgw.internal` | SSH-only reserved UDPGW destination |
|
||||
| `--udpgw-max-clients` | `10000` | Concurrent UDPGW TCP clients |
|
||||
| `--udpgw-debug` | `false` | Verbose UDPGW errors |
|
||||
|
||||
### 10.2 Client flags
|
||||
|
||||
| Flag | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `--listen-host` / `--listen-port` | `127.0.0.1` / `8080` | Local proxy bind |
|
||||
| `--server-host` / `--server-port` | — / `53` | Remote server (host required) |
|
||||
| `--server-host` / `--server-port` | — / `53` | Remote server; `--server-port` remains the single-port compatibility default |
|
||||
| `--server-port-start` | `0` | First port in ascending scan; 0 uses `--server-port` |
|
||||
| `--server-port-end` | `0` | Last port in ascending scan; 0 uses the resolved start port |
|
||||
| `--token` | empty | Shared secret |
|
||||
| `--transport` | `chunk` | Must be `chunk` |
|
||||
| `--wire` | `auto` | Search B/BP/X; `b`, `bp`, or `x` pins one mode |
|
||||
| `--wire-probe-delay` | `1s` | Global minimum delay between profile probe starts; range 200ms–30s |
|
||||
| `--wire-probe-threads` | `1` | Maximum concurrent real HTTP profile probes; range 1–16 |
|
||||
| `--wire-probe-delay` | `100ms` | Spacing between header-profile probe starts; range 50ms–30s |
|
||||
| `--wire-probe-threads` | `1` | Maximum concurrent server-local header probes; range 1–16 |
|
||||
| `--max-connections` | `20000` | Concurrent proxied connections |
|
||||
| `--tcp-buffer` | `0` | Explicit socket buffers |
|
||||
| `--chunk-start` | `1048576` | Initial record size (the probe overrides it) |
|
||||
@@ -1179,6 +1379,14 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| `--chunk-poll-delay` | `2ms` | Pause after an empty poll |
|
||||
| `--chunk-timeout` | `5s` | Per-record transaction timeout |
|
||||
| `--chunk-pollers` | `1` | Accepted for compatibility; validated 1–128 but unused |
|
||||
| `--ssh-user` | empty | Enables tunnel-only SSH/SOCKS mode when non-empty |
|
||||
| `--ssh-password` | empty | SSH password; prefer `--ssh-password-env` on CLI |
|
||||
| `--ssh-password-env` | empty | Environment variable containing the SSH password |
|
||||
| `--ssh-internal-host` | `dragontcp-ssh.internal` | Reserved DragonTCP SSH target |
|
||||
| `--ssh-internal-port` | `2222` | Embedded SSH target port |
|
||||
| `--ssh-hostkey-pin-file` | empty | TOFU SHA-256 host-key fingerprint file |
|
||||
| `--ssh-socks-host` / `--ssh-socks-port` | `127.0.0.1` / `1080` | Local SOCKS5 listener in SSH mode |
|
||||
| `--ssh-udpgw-host` / `--ssh-udpgw-port` | `dragontcp-udpgw.internal` / `7400` | SSH-only embedded UDPGW target |
|
||||
|
||||
The `concurrency` flag names are historical. They control the download **batch
|
||||
depth** described in §5.6, not any form of threading.
|
||||
@@ -1189,7 +1397,8 @@ depth** described in §5.6, not any form of threading.
|
||||
|---|---|---|
|
||||
| Client | 15 s | Local-connection handshake deadline |
|
||||
| Client | 10 s | Dial timeout to the server |
|
||||
| Client | 2.5 s | Per-probe timeout cap |
|
||||
| Client | 2.5 s | Quick single-record probe timeout cap |
|
||||
| Client | 8-20 s | Synthetic UP/DW calibration connection deadline |
|
||||
| Client | 20 s | Whole-probe timeout per direction |
|
||||
| Client | 30 min | Path profile cache lifetime |
|
||||
| Client | 30 ms | Pause between failed record retries |
|
||||
@@ -1203,12 +1412,71 @@ depth** described in §5.6, not any form of threading.
|
||||
|
||||
---
|
||||
|
||||
## 10.4 Staged startup and fake-iperf calibration
|
||||
|
||||
The client uses an explicit startup boundary before it exposes the local proxy:
|
||||
|
||||
```text
|
||||
AUTH / wire validation
|
||||
-> UP calibration
|
||||
-> DW calibration
|
||||
-> lock the validated per-direction chunk ceilings
|
||||
-> DragonTCP ACTIVE
|
||||
-> local proxy / VPN ready
|
||||
```
|
||||
|
||||
Calibration is server-backed and is implemented for **B, BP, and X**. It does not
|
||||
download an external file. Each direction starts at 32 bytes, grows aggressively by
|
||||
4x while candidates succeed, and stops the coarse ascent at the first failed size or
|
||||
at the fixed 1 MiB ceiling. It then binary-refines the last good/failed interval until
|
||||
the boundary is within **32 bytes**. UP and DW are calibrated sequentially to avoid
|
||||
self-induced startup bursts.
|
||||
|
||||
Carrier decisions near the boundary use confirmation retries so one transient loss or
|
||||
short-lived network hiccup does not collapse the selected size. The fast 4x ascent is
|
||||
still single-shot while it succeeds. When the first candidate fails, DragonTCP retries
|
||||
that exact size on fresh connections and requires a **2-of-3** success/failure majority.
|
||||
Every 32-byte refinement candidate uses the same majority rule. Socket timeouts are
|
||||
inconclusive and are not counted as evidence that a size is too large.
|
||||
|
||||
For B/BP, normal Android discovery uses SHA-256-masked payload framing only. Clear
|
||||
payload support remains in the Go CLI for compatibility/testing, but the Android app
|
||||
does not expose or request it. X uses its native UP/OK + XOR framing and has dedicated
|
||||
`CIPERFUP`/`CIPERFDW` server calibration commands, so X is measured on the real X
|
||||
wire rather than through the binary protocol.
|
||||
|
||||
A real socket I/O timeout is treated as a dead physical connection, not evidence that
|
||||
the candidate size is too large. The last proven size is retained and the connection is
|
||||
replaced instead of walking the chunk size downward. The validated UP/DW sizes become
|
||||
the runtime ceilings for that VPN session.
|
||||
|
||||
Typical client logs are:
|
||||
|
||||
```text
|
||||
[D-TCP] phase=AUTH state=success wire=x ...
|
||||
[D-TCP] phase=CALIBRATION state=starting wire=x strategy=ascending min=32 max=1048576 growth=4x fine_resolution=32 up_down=sequential
|
||||
[D-TCP] phase=CALIBRATION fake_iperf=upload wire=x stage=ascend chunk=524288 ... result=success
|
||||
[D-TCP] phase=CALIBRATION fake_iperf=upload wire=x stage=refine chunk=... result=...
|
||||
[D-TCP] phase=CALIBRATION state=success wire=x upload=... download=...
|
||||
[D-TCP] phase=ACTIVE wire=x upload_chunk=... download_chunk=...
|
||||
```
|
||||
|
||||
Server debug logs show matching `CALIBRATION fake_iperf=upload` and
|
||||
`CALIBRATION fake_iperf=download` events for B/BP and X.
|
||||
|
||||
## 11. Tuning
|
||||
|
||||
Start with the defaults. Path probing already picks sensible sizes, and most
|
||||
manual tuning makes things worse.
|
||||
|
||||
* **Throughput feels capped.** Raise `Batch max` to 4–16 and leave `Batch min` at
|
||||
* **SSH mode is much slower than direct mode.** Current builds combine small SSH
|
||||
packets before they enter DragonTCP and coalesce the internal SSH downstream
|
||||
stream on the server. You should see `ssh carrier: DragonTCP stream connected`
|
||||
followed by `ssh authenticated:` in the Android log. If those lines are absent,
|
||||
rebuild both the server and the Android native client so they run the same
|
||||
source version. This SSH-specific combining does **not** override `Batch min` /
|
||||
`Batch max`; a user-pinned value of 1 remains pinned at 1.
|
||||
* **Throughput feels capped in direct/non-SSH mode.** Raise `Batch max` to 4–16 and leave `Batch min` at
|
||||
1. More records per request is the main lever when latency to the server is
|
||||
high, because each round trip returns more data. If the log repeatedly shows
|
||||
`adaptive download batch: N -> N/2`, the path cannot sustain that depth.
|
||||
@@ -1230,8 +1498,10 @@ manual tuning makes things worse.
|
||||
depth the path tolerates and `Batch max` higher; adaptation then works inside
|
||||
that window instead of falling all the way to 1.
|
||||
* **Frequent `after transport failure` lines.** The network is dropping large
|
||||
records. Lower `Max chunk` to a size the probe already found safe — 1400 is
|
||||
common — so the client stops rediscovering the limit.
|
||||
records. Android startup already runs ascending UP/DW calibration and locks the
|
||||
highest validated ceiling. Runtime recovery reduces the active chunk by 200 B
|
||||
immediately for recoverable transfer failures; an actual I/O timeout kills the
|
||||
physical tunnel instead of changing the chunk size.
|
||||
* **`i/o timeout`.** The active physical connection is closed and the tunnel
|
||||
exits immediately. It no longer redials that session or repeats the request at
|
||||
512 KiB, 256 KiB, and every smaller size.
|
||||
@@ -1271,10 +1541,13 @@ manual tuning makes things worse.
|
||||
| `target resolves only to blocked addresses` | The destination is private or special-use. Intentional; see §6.5. |
|
||||
| `download offset N was already acknowledged` | Client and server disagree on stream position, almost always a stale session after a restart. Reconnect. |
|
||||
| `upload gap: got N expected M` | The same, in the upload direction. Reconnect. |
|
||||
| `server maximum chunk N is below client minimum M` | Raise the server's `--chunk-max` or lower the client's `Min chunk`. |
|
||||
| `server maximum chunk N is below client minimum M` | Android uses a fixed 32-byte minimum; raise the server's `--chunk-max` to at least 32. CLI users may also lower `--chunk-min`. |
|
||||
| `download failed at minimum chunk` | Eight consecutive failures at the smallest record size — the path is not passing traffic at all. |
|
||||
| `i/o timeout` | The physical connection timed out; its tunnel was terminated immediately without replay or a chunk-size retry loop. |
|
||||
| DNS works, QUIC/UDP apps do not | By design: only DNS is carried over UDP (§7.7). |
|
||||
| DNS works, QUIC/UDP apps do not | The current Android build is direct DragonTCP/HTTP only, so general UDP is not tunneled. SSH/UDPGW code remains available in the Go project but is disabled in the app. |
|
||||
| `SSH handshake/auth failed` | Check the SSH username/password, account expiry/connection limit, and that the server was rebuilt with the new core. |
|
||||
| Android says SSH mode but no `ssh authenticated:` line | The old app filtered SSH core logs or the native core is stale. Current startup blocks until authentication succeeds and exposes all `ssh ...` state lines. Rebuild the native core/APK. |
|
||||
| `SSH host key changed` | The saved TOFU pin no longer matches. Verify that the server key was intentionally replaced before deleting the pin. |
|
||||
| No IPv6 anywhere | By design: IPv6 is captured and blackholed (§7.4). |
|
||||
| Connections hang instead of failing fast | Expected only for non-SYN packets to unknown flows, which receive an RST. Anything else warrants the logs. |
|
||||
|
||||
@@ -1298,7 +1571,10 @@ Coverage:
|
||||
* a pinned batch surviving the ~1 MiB per-batch cap while an unpinned one is still
|
||||
trimmed by it,
|
||||
* an adaptive batch halving down to its floor and growing back to its ceiling,
|
||||
* `1..1` being treated as pinned.
|
||||
* `1..1` being treated as pinned,
|
||||
* SOCKS5 IPv4 UDP parsing and UDPGW frame layout,
|
||||
* embedded UDPGW IPv4 relay against a real local UDP echo socket,
|
||||
* separation of raw-DragonTCP internal targets from SSH-only UDPGW targets.
|
||||
|
||||
Beyond unit tests, the transport has been exercised with an 8 MiB HTTP download
|
||||
verified by SHA-256, an HTTPS `CONNECT` download verified byte for byte,
|
||||
@@ -1306,9 +1582,8 @@ persistent and forced-rotation connection modes, and a server restricted to
|
||||
1400-byte records where probing selected 1400 automatically and the download still
|
||||
completed correctly.
|
||||
|
||||
`SHA256SUMS` records digests for the built binaries; the digest for
|
||||
`android/lib/arm64-v8a/libdragontcp_client.so` matches the copy packaged inside
|
||||
the APK.
|
||||
After a successful Unix `build_core.sh`, `SHA256SUMS` is regenerated for the
|
||||
new client/server binaries and Android core.
|
||||
|
||||
---
|
||||
|
||||
@@ -1321,6 +1596,8 @@ marker comment at the top.
|
||||
|
||||
* Attribution: [`THIRD_PARTY_NOTICES.md`](THIRD_PARTY_NOTICES.md)
|
||||
* License text: [`licenses/FreeProxy-APACHE-2.0.txt`](licenses/FreeProxy-APACHE-2.0.txt)
|
||||
* Go SSH/bcrypt dependency license:
|
||||
[`licenses/golang-x-crypto-BSD-3-Clause.txt`](licenses/golang-x-crypto-BSD-3-Clause.txt)
|
||||
|
||||
Both are also shipped inside the APK under `assets/`. They are a license
|
||||
condition rather than documentation, and must not be folded into this file or
|
||||
@@ -1328,3 +1605,8 @@ removed.
|
||||
|
||||
The remaining DragonTCP glue, UI, Go transport, and server code is provided as
|
||||
part of this project.
|
||||
|
||||
## Calibration single-poller rule
|
||||
|
||||
Startup UP/DW calibration is strictly single-lane: each candidate size sends exactly one outstanding record, waits for its response/ACK, and only then continues. Boundary stability retries (2-of-3) are sequential and use fresh connections; they never overlap. Runtime concurrency is unchanged and starts only after calibration. Server debug lines include `pollers=1 outstanding=1` so this is directly visible.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user