Add ihtc design spec

This commit is contained in:
Kreato 2026-05-31 23:53:37 +03:00
commit a41b35ee74
No known key found for this signature in database

View file

@ -0,0 +1,107 @@
# ihtc — DPI Bypass via HTTP Proxy with TLS Fragmentation
## Purpose
A local HTTP forward proxy that circumvents SNI-based DPI censorship (e.g., blocked access to Discord in Turkey) by splitting the TLS ClientHello across multiple TCP packets, rendering the SNI field unreadable to simple DPI systems. Fully local, single-hop, no remote relay required.
## Architecture
```
Browser/App ──► ihtc proxy (127.0.0.1:8080) ──► Target (discord.com:443)
│ │
│ 1. Split TLS ClientHello │
│ across N small TCP packets │
│ 2. DPI cannot reassemble SNI │
│ 3. Post-handshake: transparent │
│ bidirectional relay │
```
Single-binary Go program. Configured via CLI flags. No config file, no daemon, no relay server.
## Source Layout
```
cmd/ihtc/main.go — CLI entry point, flag parsing, signal handling
internal/proxy/proxy.go — HTTP forward proxy (CONNECT + direct HTTP)
internal/obfuscate/obfuscate.go — TLS ClientHello splitting engine
internal/log/log.go — minimal structured logging
```
Go module path: `github.com/kreato/ihtc`
## CLI
```
ihtc [flags]
--listen string Address to bind (default "127.0.0.1:8080")
--min-chunk int Minimum bytes per fragment (default 2)
--max-chunk int Maximum bytes per fragment (default 8)
--delay-us int Max microsecond delay between fragments (default 500)
--verbose bool Enable debug logging
```
Graceful shutdown on SIGINT/SIGTERM — drain in-flight connections with a 5-second deadline.
## Components
### internal/proxy
Standard Go `net/http` reverse proxy.
- Handles CONNECT for HTTPS tunnels
- Handles plain HTTP requests (forward with optional header manipulation)
- Target unreachable → HTTP 502
- Client disconnects mid-handshake → clean up connection
### internal/obfuscate
TLS ClientHello fragmentation engine.
**Algorithm:**
1. Read TLS record header from client (5 bytes: type, version, record length)
2. Compute total ClientHello size from record header
3. Read exactly that many remaining bytes
4. Split the complete ClientHello into N randomized chunks (size between `min-chunk` and `max-chunk` bytes, uniform random)
5. Write each chunk to target via `syscall.Write()` (bypasses buffered IO) with `TCP_NODELAY` disabled on the socket
6. Between chunks, sleep a randomized microsecond delay (0 to `delay-us` µs) so the kernel flushes each chunk as its own TCP segment
7. Return and let caller resume bidirectional `io.Copy`
**TCP_NODELAY trick:** Disabling Nagle's algorithm would merge small writes. Instead, `TCP_NODELAY` is disabled (the default), and the deliberate inter-chunk delay causes the kernel's TCP stack to send each chunk in its own segment. If this proves insufficient, `TCP_CORK` (Linux) / `TCP_NOPUSH` (macOS/BSD) can be used to force explicit segment boundaries.
**Optional re-fragmentation (v2):** For DPIs that reassemble TCP streams, insert a deliberately invalid TLS record in the first chunk, followed by a correct segment that overrides it. Toggled via future flag.
### internal/log
Minimal structured logging with severity levels (info, warn, error). In verbose mode, logs per-connection fragmentation statistics.
## Error Handling
| Scenario | Behavior |
|---|---|
| Target unreachable | Return HTTP 502 to client |
| Client disconnects mid-handshake | Close target connection, clean up |
| TLS handshake failure | Relay raw bytes — browser displays normal TLS error |
| Signal (SIGINT/SIGTERM) | Stop accepting connections, drain existing with 5s deadline |
## Testing Strategy
1. **Unit:** `internal/obfuscate` — feed known ClientHello byte sequence, verify fragmented output produces correct reassembled bytes, verify individual chunk sizes stay within [min, max]
2. **Integration:** `internal/proxy` — start ihtc, point `curl -x http://127.0.0.1:8080 https://example.com` at it, verify response matches direct connection
3. **Packet capture:** Run tcpdump/Wireshark while proxying through ihtc to visually confirm SNI is not readable in fragmented packets
4. **Live test:** Configure browser proxy to ihtc, visit discord.com from a blocked network
## Limitations
- Only handles HTTP/HTTPS traffic (not arbitrary TCP)
- SNI fragmentation may not work against advanced DPIs that perform TCP stream reassembly
- Platform-specific TCP behavior may require tuning (tested primarily on Linux/macOS)
- No encrypted configuration or authentication (local-only, trusted environment)
## Roadmap (post-v1)
- `TCP_CORK`/`TCP_NOPUSH` for explicit segment boundary control
- Re-fragmentation / dummy-record injection for advanced DPIs
- Optional two-hop relay mode for environments where fragmentation alone is insufficient
- Per-domain rules (only fragment blocked domains, passthrough for everything else)