Skip to content

High-performance multi-connection TCP file transfer with intelligent NAT traversal

License

Notifications You must be signed in to change notification settings

well0nez/tcp-multi-transfer

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

85 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

tcp-multi-transfer

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.

Features

  • 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

How It Works

  1. Both sender and receiver connect to the relay server with the same session ID
  2. The relay server probes NAT behavior (if needed) and exchanges peer addresses
  3. Both peers attempt TCP hole punching simultaneously
  4. Once connected, the file is transferred directly peer-to-peer
  5. SHA256 verification ensures file integrity

Sequential Multi-Connection Punching (Workflow)

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 report conn_established or conn_abandoned before 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.

Port Prediction and Scan Method

The relay server runs a short NAT probing phase when a peer does not preserve ports:

  • The client opens --probe-count quick 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 by MAX_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_port and cap to MAX_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 to MAX_SCAN_PORTS.
  • The server sends peer_info with 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.

Scan Cap (MAX_SCAN_PORTS)

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.

Client Prediction Mode

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.

Probe Debug Mode

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-debug

Usage

Prerequisites

Start the relay server:

python3 run_server.py --port 9999 --probe-port 9998 --max-scan-ports 512

Ensure 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]

Receiver (start first)

./tcp-multi-transfer -s relay-server:9999 -i my-session -m receive

Sender

./tcp-multi-transfer -s relay-server:9999 -i my-session -m send -f myfile.mp4

Options

Options:
  -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]

Building

cargo build --release

The binary will be at target/release/tcp-multi-transfer.

Protocol

Relay Server Protocol (JSON over TCP)

  1. Registration: Client sends {"type": "register", "session_id": "...", "role": "sender|receiver", "local_port": 12345}
    • Optional for Multi-TCP: "tcp_connections": N and "extra_ports": [..]
  2. Registered: Server responds {"type": "registered", "your_public_addr": ["ip", port], "needs_probing": true|false, "probe_port": 9998}
  3. 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_num and peer_extra_ports when available.

File Transfer Protocol (Binary over direct TCP)

  1. HELLO: Both peers exchange [type=1][len][role]
  2. FILE_INFO: Sender sends [type=2][name_len][size][filename][sha256]
  3. FILE_INFO_ACK: Receiver acknowledges [type=3]
  4. Data Stream: Raw file bytes (TCP handles reliability)
  5. DONE: Sender signals completion [type=5]
  6. ACK: Receiver confirms SHA256 verified [type=6]

Comparison with UDP Hole Punching (general)

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

Troubleshooting

Hole punch fails

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:

  1. Using a TURN-style relay fallback
  2. Retrying multiple times for random-port or symmetric NATs (success can be probabilistic)

Debug mode

Use --debug for detailed logging.

Connection timeout

Increase the timeout: --timeout 60

References

These papers and implementations informed the NAT probing and prediction approach:

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.

License

MIT

About

High-performance multi-connection TCP file transfer with intelligent NAT traversal

Resources

License

Stars

Watchers

Forks

Packages

No packages published