New
This commit is contained in:
@@ -90,10 +90,11 @@ flow control a matter of reporting a number.
|
||||
**DragonTCP does not provide authenticated encryption. It is not a VPN in the
|
||||
security sense.**
|
||||
|
||||
Payloads are **masked**: XORed with a keystream derived from SHA-256 that varies
|
||||
with session ID, mode, sequence, direction, and block number. The same plaintext
|
||||
therefore does not produce the same ciphertext twice, and no fixed ASCII markers
|
||||
appear on the wire.
|
||||
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
|
||||
also support a self-described **clear-payload** profile that removes the per-32-byte
|
||||
SHA-256 work. Auto tries clear first and falls back to the legacy mask for older
|
||||
servers or networks that reject the clear profile.
|
||||
|
||||
This defeats trivial pattern matching. It does not defeat anyone who can read the
|
||||
traffic:
|
||||
@@ -101,7 +102,9 @@ traffic:
|
||||
* The mask is derived from the **session ID, which is transmitted in cleartext in
|
||||
every request header.** Anyone who sees the header can regenerate the keystream
|
||||
and recover the plaintext. This is obfuscation, not confidentiality.
|
||||
* Record headers — mode, session, sequence, length — are never masked.
|
||||
* Session, sequence, and length fields remain clear. The mode/status byte may
|
||||
use a startup-selected header mask, but this is traffic shaping rather than
|
||||
cryptographic protection.
|
||||
* `StatusError` bodies are sent **unmasked**, as plain text.
|
||||
* There is no integrity check, so payloads can be tampered with undetected.
|
||||
|
||||
@@ -130,6 +133,7 @@ core/
|
||||
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
|
||||
debug.go counters and periodic statistics
|
||||
chunk_test.go
|
||||
internal/wire/
|
||||
@@ -227,7 +231,7 @@ A single request may be answered by **several** responses — see `ModeDownload`
|
||||
| `StatusWait` | 3 | Nothing available yet; ask again |
|
||||
| `StatusEOF` | 4 | Target closed the stream |
|
||||
|
||||
### 4.3 Payload masking
|
||||
### 4.3 Payload encoding
|
||||
|
||||
```text
|
||||
seed[0:16] = session ID
|
||||
@@ -245,6 +249,12 @@ sequence field is a **byte offset** (§4.5), consecutive records never reuse a
|
||||
keystream position, and re-sending the same offset reproduces the same bytes —
|
||||
which is what makes idempotent retries safe.
|
||||
|
||||
When the cover preface carries the clear-payload flag, request and data bodies are
|
||||
sent without that transform. Headers, session semantics, retry offsets, and all
|
||||
payload layouts remain identical. Old clients remain masked and are accepted by
|
||||
the new server. An old server rejects the new flag, so the new client's next
|
||||
startup candidate is the corresponding legacy direct profile.
|
||||
|
||||
Not everything is masked. `WriteResponse` sends the body as-is and is used for
|
||||
empty `StatusOK`, `StatusWait`, `StatusEOF`, and every `StatusError`.
|
||||
`WriteMaskedResponse` is used for `StatusData` and for the `OPEN` result. On the
|
||||
@@ -350,6 +360,27 @@ client server
|
||||
│◀─ OK ────────────────────────────────────────│
|
||||
```
|
||||
|
||||
### 4.7 BP transport compatibility
|
||||
|
||||
Binary connections are auto-detected as either native Dragon B or the
|
||||
observable BP protocol used by `bhttp_remote_test.py`. BP support uses the
|
||||
same 29-byte request header, five-byte response header, and SHA-256 counter mask,
|
||||
but maps modes as `0=probe`, `1=upload/register`, `2=single download`, `3=batch
|
||||
download`, and `4=ACK`. It implements `BHP1` version-1 probe integrity, probe
|
||||
batching, empty mode-1 session registration, upload acknowledgements, mode-2's
|
||||
header-only size hint, the six-byte batch request, ACK, expiry, and unknown-session
|
||||
errors. Dragon peers can additionally negotiate the clear-payload encoding via
|
||||
the cover preface; reference clients continue through the original direct masked
|
||||
encoding unchanged.
|
||||
|
||||
The available reference client does not expose a destination-selection or
|
||||
authentication exchange. Its observable registration/upload/download/ACK
|
||||
behavior remains accepted unchanged. Dragon's BP client adds a `DOP1` upload
|
||||
extension after registration to carry the normal token, target host, and port;
|
||||
that extension gives the Android app a complete bidirectional stream without
|
||||
changing the reference client's frames. Native Dragon B remains available and
|
||||
is not replaced.
|
||||
|
||||
---
|
||||
|
||||
## 5. The Go client
|
||||
@@ -404,10 +435,44 @@ keepalives, and optionally explicit buffer sizes via `--tcp-buffer`.
|
||||
| `--chunk-reconnect-every` | Behaviour |
|
||||
|---|---|
|
||||
| `0` | Persistent — one connection for the life of the lane |
|
||||
| `1` (default) | Auto — persistent if the path probe showed reuse works, otherwise one logical request per connection. Resolved silently, since it runs once per flow |
|
||||
| `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 Path probing
|
||||
### 5.4 Startup profile and path probing
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
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:
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
Before the first real connection, `getPathProfile` measures the path once and
|
||||
caches the result for **30 minutes**, keyed by server address, token, and size
|
||||
@@ -558,10 +623,11 @@ Source: `core/cmd/dragontcp-server/`.
|
||||
### 6.1 Connection handling
|
||||
|
||||
The server listens on `0.0.0.0:53` by default, bounded by `--max-connections`
|
||||
through a slot channel. Each connection runs a loop: read one request with a
|
||||
30-second deadline, dispatch it, repeat. Because session state is keyed by session
|
||||
ID rather than by connection, requests for one logical stream may arrive over many
|
||||
connections in whatever pattern the client chooses.
|
||||
through a slot channel. Each connection runs a loop: read one request with an
|
||||
idle deadline, dispatch it, repeat. The deadline is refreshed halfway through its
|
||||
window rather than issuing a system call for every record. Because session state
|
||||
is keyed by session ID rather than by connection, requests for one logical stream
|
||||
may arrive over many connections in whatever pattern the client chooses.
|
||||
|
||||
### 6.2 Session state and buffering
|
||||
|
||||
@@ -574,7 +640,8 @@ target plus a download buffer:
|
||||
|
||||
That goroutine **blocks when the buffer is full**, which is the entire flow
|
||||
control story described in §4.5. Buffer size is `--chunk-buffered × 65536`,
|
||||
clamped to 1 MiB…64 MiB (default 256 → 16 MiB per session).
|
||||
clamped to 1 MiB…64 MiB (default 32 → 2 MiB per session). The same byte limit is
|
||||
enforced for X; it is not multiplied by the negotiated chunk size.
|
||||
|
||||
Acknowledgement drops bytes off the front and advances `base`. The buffer is
|
||||
compacted when its capacity exceeds four times its length and is over 1 MiB, so
|
||||
@@ -673,9 +740,9 @@ a helper binary in an APK.
|
||||
|
||||
`DragonService` launches it with `ProcessBuilder`, merges stderr into stdout, and
|
||||
reads the output on a background thread. Only interesting lines reach the UI log:
|
||||
those beginning with `adaptive ` or `path probe:`, and anything containing `error`
|
||||
or `failed`. A watchdog thread waits on the process; if the core exits while the
|
||||
tunnel is supposed to be up, the whole VPN is torn down.
|
||||
those beginning with `wire`, `adaptive `, or `path probe:`, and anything
|
||||
containing `error` or `failed`. A watchdog thread waits on the process; if the
|
||||
core exits while the tunnel is supposed to be up, the whole VPN is torn down.
|
||||
|
||||
### 7.3 Startup and shutdown
|
||||
|
||||
@@ -822,12 +889,15 @@ checksum of zero is written as `0xFFFF` per RFC 768.
|
||||
| Server | — | `--server-host` |
|
||||
| Port | 53 | `--server-port` |
|
||||
| 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 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) | 2 | `--chunk-timeout` |
|
||||
| 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`.
|
||||
@@ -853,7 +923,7 @@ 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.
|
||||
timeout 1–120, probe delay 200–30000 ms, probe threads 1–16.
|
||||
|
||||
### 7.10 Logs
|
||||
|
||||
@@ -989,26 +1059,30 @@ keystore out of version control.
|
||||
### 9.1 Server
|
||||
|
||||
```bash
|
||||
sudo ./dragontcp-hybrid-server-linux-amd64 --port 53 --chunk-max 1048576
|
||||
sudo ./dragontcp-hybrid-server-linux-amd64 \
|
||||
--port 53 --port-alt 80 --chunk-max 1048576
|
||||
```
|
||||
|
||||
With a token:
|
||||
|
||||
```bash
|
||||
sudo ./dragontcp-hybrid-server-linux-amd64 \
|
||||
--token 'YOUR_SECRET' --port 53 --chunk-max 1048576
|
||||
--token 'YOUR_SECRET' --port 53 --port-alt 80 --chunk-max 1048576
|
||||
```
|
||||
|
||||
With diagnostics:
|
||||
|
||||
```bash
|
||||
sudo ./dragontcp-hybrid-server-linux-amd64 \
|
||||
--port 53 --chunk-max 1048576 --debug --debug-stats-interval 10s
|
||||
--port 53 --port-alt 80 --chunk-max 1048576 --debug --debug-stats-interval 10s
|
||||
```
|
||||
|
||||
`sudo` is needed only because port 53 is privileged. If `systemd-resolved` or
|
||||
`dnsmasq` already owns port 53, free it or choose another port. No TUN device, NAT,
|
||||
or firewall rules are required.
|
||||
The server listens on TCP ports 53 and 80 simultaneously by default. Set
|
||||
`--port-alt 0` to disable the second listener. Failure to bind the primary port
|
||||
is fatal; failure to bind the secondary port prints a warning and leaves the
|
||||
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.
|
||||
|
||||
### 9.2 Client CLI
|
||||
|
||||
@@ -1023,7 +1097,7 @@ summarises the active configuration:
|
||||
|
||||
```text
|
||||
adaptive_chunk=true start=1048576 min=32 max=1048576 grow_after=16 pollers=1 \
|
||||
batch=5-5(pinned) reconnect_every=0 timeout=2s
|
||||
batch=5-5(pinned) reconnect_every=0 timeout=5s
|
||||
```
|
||||
|
||||
### 9.3 Android
|
||||
@@ -1041,7 +1115,9 @@ Batch max: 1
|
||||
Batch min: 1
|
||||
Pollers: 1
|
||||
Reconnect every: 1
|
||||
Timeout (s): 2
|
||||
Timeout (s): 5
|
||||
Probe delay (ms): 1000
|
||||
Probe threads: 1
|
||||
```
|
||||
|
||||
Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
@@ -1055,7 +1131,8 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| Flag | Default | Meaning |
|
||||
|---|---|---|
|
||||
| `--host` | `0.0.0.0` | Listen address |
|
||||
| `--port` | `53` | Listen port |
|
||||
| `--port` | `53` | Primary listen port |
|
||||
| `--port-alt` | `80` | Simultaneous secondary listen port; 0 disables it |
|
||||
| `--token` | empty | Optional shared secret |
|
||||
| `--max-connections` | `20000` | Concurrent TCP connections |
|
||||
| `--allow-private` | `false` | Allow private/loopback targets — keep off in public |
|
||||
@@ -1063,7 +1140,7 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| `--dns-cache-size` | `4096` | Cached hostnames |
|
||||
| `--tcp-buffer` | `0` | Explicit socket buffers; 0 = OS autotuning |
|
||||
| `--chunk-max` | `1048576` | Largest record accepted (32 B – 1 MiB) |
|
||||
| `--chunk-buffered` | `256` | Per-session buffer in 64 KiB units (≈16 MiB) |
|
||||
| `--chunk-buffered` | `32` | Per-session buffer in 64 KiB units (≈2 MiB) |
|
||||
| `--chunk-poll-wait` | `200ms` | Long-poll wait for a batch's first record |
|
||||
| `--chunk-session-timeout` | `2m` | Idle session reaping |
|
||||
| `--debug` | `false` | Session/connect/error logs plus periodic stats |
|
||||
@@ -1078,6 +1155,9 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| `--server-host` / `--server-port` | — / `53` | Remote server (host required) |
|
||||
| `--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 |
|
||||
| `--max-connections` | `20000` | Concurrent proxied connections |
|
||||
| `--tcp-buffer` | `0` | Explicit socket buffers |
|
||||
| `--chunk-start` | `1048576` | Initial record size (the probe overrides it) |
|
||||
@@ -1091,7 +1171,7 @@ Use **OPEN LOGS** to watch the path probe and any adaptation.
|
||||
| `--chunk-concurrency-min` | `1` | Download batch floor, 1–256; equal to the ceiling pins the depth |
|
||||
| `--chunk-reconnect-every` | `0` | 0 persistent, 1 auto, N rotate |
|
||||
| `--chunk-poll-delay` | `2ms` | Pause after an empty poll |
|
||||
| `--chunk-timeout` | `2s` | Per-record transaction timeout |
|
||||
| `--chunk-timeout` | `5s` | Per-record transaction timeout |
|
||||
| `--chunk-pollers` | `1` | Accepted for compatibility; validated 1–128 but unused |
|
||||
|
||||
The `concurrency` flag names are historical. They control the download **batch
|
||||
@@ -1127,10 +1207,10 @@ manual tuning makes things worse.
|
||||
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.
|
||||
* **Streams die mid-transfer, or nothing loads at all.** Set `Reconnect every`
|
||||
to `1` (auto). `0` forces persistent connections, and many networks silently
|
||||
kill long-lived port-53 connections; auto probes first and falls back to one
|
||||
logical request per connection when persistence does not survive. This is the
|
||||
single most important setting on a restrictive path.
|
||||
to `1` (auto). Auto starts persistent and retries an idempotent request once on
|
||||
a fresh connection. If reuse itself failed, only that lane switches to one
|
||||
logical request per connection. This avoids turning one imperfect startup
|
||||
probe into connection churn for every active tunnel.
|
||||
* **Logs show the same transition many times over (`128 -> 64` repeatedly).**
|
||||
Each tunnel adapts independently, so a burst of flows produces a burst of
|
||||
identical lines. The app collapses consecutive duplicates into a counted line;
|
||||
@@ -1151,7 +1231,8 @@ manual tuning makes things worse.
|
||||
a small number of high-BDP connections is `1048576` or `4194304` worth trying;
|
||||
across many connections it costs memory for nothing.
|
||||
* **Server memory.** Each session can hold `--chunk-buffered × 64 KiB` (default
|
||||
16 MiB). Lower it when running many concurrent sessions.
|
||||
2 MiB). Lower it when running many concurrent sessions; the minimum effective
|
||||
window is 1 MiB.
|
||||
|
||||
---
|
||||
|
||||
@@ -1196,8 +1277,8 @@ cd core && go test ./...
|
||||
|
||||
Coverage:
|
||||
|
||||
* the masking round-trip, and that different sequences produce different wire
|
||||
bytes,
|
||||
* legacy masking round-trips and different-sequence wire bytes,
|
||||
* clear-payload B and BP profiles end to end, including nonzero header masks,
|
||||
* the adaptive record sizer recovering from the minimum rather than latching
|
||||
there,
|
||||
* `reconnectEvery == 0` meaning persistent,
|
||||
|
||||
Reference in New Issue
Block a user