High-performance multi-TCP file transfer with NAT traversal (hole punching).
Current version: v0.9.3
Successor to the single-connection prototype at https://github.com/well0nez/tcp-transfer-ice.
This version extends the same relay + NAT probing model with coordinated multi-connection punching while keeping direct P2P TCP and SHA256 integrity checks.
- TCP Hole Punching: Establishes direct peer-to-peer TCP connections through NAT
- NAT Probing + Prediction: Uses a probe port to predict a NAT port range and build a capped scan list
- SHA256 Verification: Ensures file integrity after transfer
- Progress Bar: Real-time transfer progress with speed display
- Multi-TCP (optional): Use multiple parallel TCP connections for transfer
- Session-Based: Both peers connect using a shared session ID
- Both sender and receiver connect to the relay server with the same session ID
- The relay server probes NAT behavior (if needed) and exchanges peer addresses
- Both peers attempt TCP hole punching simultaneously
- Once connected, the file is transferred directly peer-to-peer
- SHA256 verification ensures file integrity
For multi-TCP sessions, the relay coordinates one connection at a time to keep both peers synchronized and to prevent NAT prediction drift:
- Sequential coordination: For connection n, the relay sends
peer_info+GO, then waits for both peers to reportconn_establishedorconn_abandonedbefore moving to connection n+1. - On-demand ports: Clients bind a fresh local port per connection (and per retry) and announce it
via
add_ports. The relay maps that port to the current connection. - Retries are symmetric: A retry only starts after both peers request it and have provided new ports.
Behavior by NAT type:
- NAT-friendly (port-preserved / stable delta): The peer list is usually a single candidate port. Punching is fast; sequential mode mainly enforces timing and clean state transitions.
- Symmetric / random-like NATs: The relay sends a bounded scan list derived from probes.
Sequential coordination reduces scan load and keeps both peers aligned on the same candidate set.
Optional fallback (
--allow-fallback,--min-connections) lets you proceed with fewer streams if needed.
The relay server runs a short NAT probing phase when a peer does not preserve ports:
- The client opens
--probe-countquick probe connections to--probe-port. The server uses whatever it receives (analysis is most meaningful with >=2). - The server records
(local_port, observed_public_port, timestamp)and computes a prediction model:- delta = public_port - local_port
- predicted_port = local_port + median(delta)
- error_range = max deviation + jitter (2 * stdev, min 2)
- For progressing symmetric NATs, estimate a port allocation rate and shift the prediction forward
(
port_rate * prediction_delay * RATE_DAMPING, capped byMAX_RATE_SHIFT).
- The server classifies the pattern (
port_preserved,constant_delta,small/medium/large_delta_range,random_like) and builds a bounded candidate list:- For non-random patterns, use a contiguous window around
predicted_portand cap toMAX_SCAN_PORTS. - For
random_like, use the observed min/max range and build a sparse list:predicted_port,min_port+1..+5, plus evenly spaced samples, capped toMAX_SCAN_PORTS.
- For non-random patterns, use a contiguous window around
- The server sends
peer_infowith the prioritized candidate list; the client tries only those candidates.
These additions (rate-based forward shift and sparse random-like sampling) extend the standard delta-only prediction and reduce the number of attempts without a full port sweep.
You can raise the scan cap with --max-scan-ports. Higher values can improve success on symmetric/random
NAT because the peer-specific NAT port may lie outside the small probe-derived range. The tradeoffs:
- Pros: Higher success probability when NAT port allocation is wide or target-dependent.
- Cons: More outbound connection attempts, higher CPU/network load, and potential throttling by NATs/ISPs.
In our tests, the biggest gains came from widening the scan window with
--prediction-range-extra-pct (often 20-50). Combined with --max-scan-ports 512, this reached
roughly 99% success on difficult NAT pairs, but results still depend on the networks and devices involved.
Use --prediction-mode external to predict from observed public ports only (mean + deviation), without
using local-port deltas. This can help when symmetric/random NATs allocate ports in a target-dependent way.
- Pros: Can stabilize prediction when local-port deltas are noisy or misleading.
- Cons: Ignores local-port correlation; may reduce accuracy on stable delta NATs.
You can also expand the scan range with --prediction-range-extra-pct (applies to both modes).
The value is a percentage of the computed scan span and is split across both sides of the
window. Example: a range of 100..200 with 5 expands to 98..203, and the final candidate
list is still capped by MAX_SCAN_PORTS.
In practice, this can yield significantly better results when the true NAT ports sit within
a wider band than the probe-derived window. If delta or external prediction is too tight,
raising this to around 20-50 can materially improve success rates.
Use --probe-debug to resolve and print the relay endpoint and exit (no probe traffic).
The probe port is provided by the server in the registered response when probing is required
(server default is 9998).
Example:
./tcp-multi-transfer -s 1.2.3.4:9999 -i test -m receive --probe-debugStart the relay server:
python3 run_server.py --port 9999 --probe-port 9998 --max-scan-ports 512Ensure both ports are reachable from the public Internet.
Relay server options:
--host <HOST> Host to bind to [default: 0.0.0.0]
--port <PORT> Main port [default: 9999]
--probe-port <PORT> Probe port for NAT analysis [default: 9998]
--max-scan-ports <N> Max candidate ports sent to clients [default: 512]
./tcp-multi-transfer -s relay-server:9999 -i my-session -m receive./tcp-multi-transfer -s relay-server:9999 -i my-session -m send -f myfile.mp4Options:
-s, --server <SERVER>
Relay server address (host:port)
-i, --session-id <SESSION_ID>
Session ID (both sender and receiver must use the same ID)
-m, --mode <MODE>
Mode: send or receive [possible values: send, receive]
-f, --file <FILE>
File to send (sender mode only)
--timeout <TIMEOUT>
Hole punch timeout in seconds [default: 30]
--probe-count <PROBE_COUNT>
Number of NAT probes to send [default: 10]
--probe-debug
Run probe-only debug mode
--prediction-mode <PREDICTION_MODE>
NAT prediction mode [default: delta] [possible values: delta, external]
--prediction-range-extra-pct <PREDICTION_RANGE_EXTRA_PCT>
Expand prediction scan range by percentage [default: 0]
--debug
Enable debug logging
--chunk <CHUNK>
Chunk size for transfer (e.g., 512KB, 1MB, 2MB) [default: 8MB]
-h, --help
Print help
-V, --version
Print version
Multi-TCP:
--tcp-connections <TCP_CONNECTIONS>
Number of parallel TCP connections (multi TCP) [default: 1]
--max-attempts <MAX_ATTEMPTS>
Maximum number of sequential punch attempts [default: 20]
--allow-fallback
Allow fallback to fewer connections if punch fails
--min-connections <MIN_CONNECTIONS>
Minimum number of connections required (if fallback allowed) [default: 1]
cargo build --releaseThe binary will be at target/release/tcp-multi-transfer.
- Registration: Client sends
{"type": "register", "session_id": "...", "role": "sender|receiver", "local_port": 12345}- Optional for Multi-TCP:
"tcp_connections": Nand"extra_ports": [..]
- Optional for Multi-TCP:
- Registered: Server responds
{"type": "registered", "your_public_addr": ["ip", port], "needs_probing": true|false, "probe_port": 9998} - Peer Info: When both peers are connected, server sends
{"type": "peer_info", "peer_public_addr": ["ip", port], "peer_addresses": [...], "peer_nat_analysis": {...}}- Multi-connection hints include
connection_numandpeer_extra_portswhen available.
- Multi-connection hints include
- HELLO: Both peers exchange
[type=1][len][role] - FILE_INFO: Sender sends
[type=2][name_len][size][filename][sha256] - FILE_INFO_ACK: Receiver acknowledges
[type=3] - Data Stream: Raw file bytes (TCP handles reliability)
- DONE: Sender signals completion
[type=5] - ACK: Receiver confirms SHA256 verified
[type=6]
Note: This repo does not include a UDP implementation; the comparison is conceptual.
| Aspect | UDP hole punching | TCP hole punching |
|---|---|---|
| Transport state | Connectionless; mapping created by outbound packets | Connection-oriented; requires SYN exchange |
| Timing sensitivity | More tolerant of timing skew | More sensitive; often needs simultaneous open |
| NAT traversal difficulty | Generally easier across NAT types | Generally harder, especially with symmetric NATs |
| Reliability/ordering | Must be implemented by the application if needed | Built-in reliability, ordering, congestion control |
| Probing cost | Lightweight probes to learn mappings | Probing uses TCP handshakes and is more rigid |
| Success on symmetric NAT | Often needs prediction/relay | Often fails without prediction/relay |
TCP hole punching is more difficult than UDP and may not work with all NAT types:
- Full Cone NAT: Usually works
- Restricted Cone NAT: Usually works
- Port Restricted Cone NAT: May work
- Symmetric NAT: Unlikely to work
If hole punching fails, consider:
- Using a TURN-style relay fallback
- Retrying multiple times for random-port or symmetric NATs (success can be probabilistic)
Use --debug for detailed logging.
Increase the timeout: --timeout 60
These papers and implementations informed the NAT probing and prediction approach:
- Kazuhiro Tobe, Akihiro Shimoda, Shigeki Goto. "Extended UDP Multiple Hole Punching Method to Traverse Large Scale NATs." https://pdfs.semanticscholar.org/953d/438516e9b2eb2bf35528de3e1fb0e9b164f8.pdf
- Daniel Maier, Oliver Haase, Juergen Waesch, Marcel Waldvogel. "NAT Hole Punching Revisited." Technical Report No. KN-2011-DiSy-02. https://kops.uni-konstanz.de/server/api/core/bitstreams/29a35a1d-40f1-4290-9d03-dae21f2b9c36/content
- Simon Keller, Tobias Hossfeld, Sebastian von Mammen. "Edge-Case Integration into Established NAT Traversal Techniques." IEEE ICCE 2022. https://downloads.hci.informatik.uni-wuerzburg.de/2022-icce-keller.pdf
- Chongyc/natblaster (GitHub). https://github.com/chongyc/natblaster
Influence summary (high level):
- Multi-probe port prediction and bounded scan lists for CGN/LSN-style NATs.
- Progressing vs random symmetric NAT handling, including rate-based forward shift and heuristics.
- Practical scanning strategies and implementation patterns for NAT probing.
MIT