Skip to content

Add support for V3 tunnels#845

Open
11EJDE11 wants to merge 78 commits into
CnCNet:developfrom
11EJDE11:new-v3-tunnels
Open

Add support for V3 tunnels#845
11EJDE11 wants to merge 78 commits into
CnCNet:developfrom
11EJDE11:new-v3-tunnels

Conversation

@11EJDE11
Copy link
Copy Markdown
Member

@11EJDE11 11EJDE11 commented Oct 11, 2025

Closes #349

  • V2 tunnels remain fully supported.
  • Adds V3 Static tunnels: One host-chosen tunnel shared by all players (similar to V2).
  • Adds V3 Dynamic tunnels: Each pair of players automatically negotiates the optimal tunnel between them.

New UserINISettings

Setting Description
UseLegacyTunnels If on: games use V2 tunnels (overrides UseDynamicTunnels).
If off: games use V3 tunnels.
UseDynamicTunnels If on: lobbies start with no predefined tunnel; tunnel negotiations occurs as players join.
If off: games use static V3 tunnels (unless legacy mode is enabled).

New Lobby Commands

Command Description
/tunnelmode [<mode>] Cycles through tunnel modes or sets one explicitly.
Modes:
0 - V3 (static)
1 - V3 (dynamic)
2 - V2 (legacy)
/negstatus Opens the negotiation status panel, showing per-pair negotiation progress and measured pings.
/tunnelinfo Modified to display the current tunnel version. Doesn't display info when dynamic tunnels are enabled.

New CTCP Messages

Command Description
PLYTNL <address>:<port> Announces which tunnel a player pair is using (sent by the decider).
NEGINFO <target_player>;status[;<ping>] Broadcasts negotiation progress and ping results.
TNLRENEG <failed_address>:<failed_port> Requests renegotiation if a tunnel fails in dynamic mode.
TNLFAIL <tunnel_name> Reports a failed tunnel to other players.
STARTV2 / STARTV3 START has been versioned into STARTV2 and STARTV3.

How V3 Tunnels Work

In V2, all players share a single tunnel.
Example:

Player1 (Australia)
Player2 (New Zealand)
Player3 (England)
Tunnel: France

For P1 to send data to P2:
P1 Australia -> Tunnel France -> P2 NZ  = 550ms

In V3 dynamic mode, each pair of players negotiates the best tunnel route:

P1 <--> P2  -> Tunnel: Australia (50ms)
P1 <--> P3  -> Tunnel: France (300ms)
P2 <--> P3  -> Tunnel: Seattle (310ms)
So now for P1 to send data to P2:
P1 Australia > Tunnel Australia > P2 NZ = 50ms (90.9% faster)
And the slowest link is now 310ms instead of 550ms (43.6% faster overall).

The client now acts as an intermediary: the game connects to the client, which then routes packets to the negotiated tunnels. This is for both static and dynamic V3.


Negotiation Process

Negotiations occur automatically when players join a lobby and take a few seconds once the player list has arrived.

  1. Each player pair tests all available tunnels by exchanging 5 UDP pings per tunnel.
    RTT, packet loss, and average latency are measured.
  2. One player is designated as the decider (the one with the lower player ID).
    • The decider receives Connected packets and sends PingRequest packets.
    • The non-decider responds with PingResponse packets.
  3. Once 80% of tunnels have been tested, the decider picks the best tunnel and informs the non-decider.
  4. The non-decider acknowledges, completing negotiation.

If Ping unofficial CnCNet tunnels is disabled, you will only negotiate over official tunnels.


Example Log Output (truncated)

09.10. 12:18:15.170    === Negotiation Results for QWE21 (ID: 3760759627) ===
Player: QWE21 | Tunnel: CnCNet Australia | Avg RTT: 65.2ms | Real ping: 34.0ms | Real ping*2: 68.0ms | Difference: -2.8ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: CnCNet Europe | Avg RTT: 583.5ms | Real ping: 284.0ms | Real ping*2: 568.0ms | Difference: 15.5ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: CnCNet Japan | Avg RTT: 291.3ms | Real ping: 193.0ms | Real ping*2: 386.0ms | Difference: -94.7ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: CnCNet Singapore | Avg RTT: 256.3ms | Real ping: 123.0ms | Real ping*2: 246.0ms | Difference: 10.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: United-Forum.de | Avg RTT: N/A | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: "[EU] Fast Server HA Germany leardev.de" | Avg RTT: 633.7ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [ EJ ] N. America (California) | Avg RTT: 269.9ms | Real ping: 140.0ms | Real ping*2: 280.0ms | Difference: -10.1ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [CN][JS]CORA_SERVER | Avg RTT: 349.1ms | Real ping: 176.0ms | Real ping*2: 352.0ms | Difference: -2.9ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [CN][JS]MonkeyRay's Server #1 | Avg RTT: 371.3ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [CN][NBO]long_ken's Server #1 | Avg RTT: 393.3ms | Real ping: 192.0ms | Real ping*2: 384.0ms | Difference: 9.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [CN][SH]REV_v3_N | Avg RTT: 393.3ms | Real ping: 186.0ms | Real ping*2: 372.0ms | Difference: 21.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [DE]XuanServerDE | Avg RTT: 591.3ms | Real ping: 288.0ms | Real ping*2: 576.0ms | Difference: 15.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [EU] Kisiek.net | Avg RTT: 560.9ms | Real ping: 284.0ms | Real ping*2: 568.0ms | Difference: -7.1ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [EU] Z Server MP https://zserver.org | Avg RTT: 592.2ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [TR] CK Server | Avg RTT: 653.1ms | Real ping: 307.0ms | Real ping*2: 614.0ms | Difference: 39.1ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [TW] Jun's Tunnel Server | Avg RTT: 380.7ms | Real ping: 192.0ms | Real ping*2: 384.0ms | Difference: -3.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [UK] London | Avg RTT: 547.7ms | Real ping: 275.0ms | Real ping*2: 550.0ms | Difference: -2.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [US West] Z Server MP https://zserver.org | Avg RTT: 317.4ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [US-C] Bot.Rip CNC Relay | Avg RTT: 363.6ms | Real ping: 180.0ms | Real ping*2: 360.0ms | Difference: 3.6ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [US-LA]XuanServerUS-LA | Avg RTT: 289.0ms | Real ping: 140.0ms | Real ping*2: 280.0ms | Difference: 9.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: [US-NY]XuanServerUS-NY | Avg RTT: N/A | Real ping: 305.0ms | Real ping*2: 610.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: [US][EU] Coolissy | Avg RTT: 306.0ms | Real ping: 152.0ms | Real ping*2: 304.0ms | Difference: 2.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: aliyunbj.cdn.leohearts.com | Avg RTT: N/A | Real ping: 217.0ms | Real ping*2: 434.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: AsSaltJordan | Avg RTT: 694.1ms | Real ping: 347.0ms | Real ping*2: 694.0ms | Difference: 0.1ms | Packet Loss: 20.0% | Pings: 4/5 | Connected: True
Player: QWE21 | Tunnel: Bloody Eye | Avg RTT: 422.5ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: bot.rip-eastus | Avg RTT: 560.0ms | Real ping: 284.0ms | Real ping*2: 568.0ms | Difference: -8.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: ISS Server (HK) | Avg RTT: 322.6ms | Real ping: 163.0ms | Real ping*2: 326.0ms | Difference: -3.4ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: KFCV50-BeiJing-1-mindwhy.top-by-2049265547@qq.com- | Avg RTT: 416.0ms | Real ping: 205.0ms | Real ping*2: 410.0ms | Difference: 6.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: KFCV50-BeiJing-2-mindwhy.top-by-2049265547@qq.com- | Avg RTT: 416.0ms | Real ping: 207.0ms | Real ping*2: 414.0ms | Difference: 2.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: KFCV50-QingDao-mindwhy.top-by-2049265547@qq.com- | Avg RTT: N/A | Real ping: 201.0ms | Real ping*2: 402.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: Los Angeles Server | Avg RTT: 293.9ms | Real ping: 139.0ms | Real ping*2: 278.0ms | Difference: 15.9ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: MangosServer - NY | Avg RTT: 416.0ms | Real ping: 199.0ms | Real ping*2: 398.0ms | Difference: 18.0ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: Mixyzzs Server | Avg RTT: N/A | Real ping: 193.0ms | Real ping*2: 386.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: Mytunnel | Avg RTT: 23.8ms | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: Mytunnel | Avg RTT: 534.3ms | Real ping: 266.0ms | Real ping*2: 532.0ms | Difference: 2.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: PL TurnWar - turn-guild.ru | Avg RTT: 567.5ms | Real ping: 281.0ms | Real ping*2: 562.0ms | Difference: 5.5ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: RedAlert2.com the best | Avg RTT: 544.7ms | Real ping: 274.0ms | Real ping*2: 548.0ms | Difference: -3.3ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: RMMD UK Tunnel | Avg RTT: 544.9ms | Real ping: 271.0ms | Real ping*2: 542.0ms | Difference: 2.9ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: RU MSK TurnWar - turn-guild.ru | Avg RTT: 649.1ms | Real ping: 296.0ms | Real ping*2: 592.0ms | Difference: 57.1ms | Packet Loss: 0.0% | Pings: 5/5 | Connected: True
Player: QWE21 | Tunnel: Studio DNA | Avg RTT: N/A | Real ping: N/A | Real ping*2: N/A | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: Wick-Server | Avg RTT: N/A | Real ping: 196.0ms | Real ping*2: 392.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
Player: QWE21 | Tunnel: zz-tunnel | Avg RTT: N/A | Real ping: 349.0ms | Real ping*2: 698.0ms | Difference: N/A | Packet Loss: 100.0% | Pings: 0/0 | Connected: False
BEST TUNNEL for QWE21: Mytunnel (RTT: 23.8ms, Loss: 0.0%)
=== End Results for QWE21 ===

Tunnel Failure Handling

A new TunnelFailed event has been added to the TunnelHandler.

  • Dynamic tunnels:
    Automatically trigger renegotiation when a connection goes down (only affected players).

  • Static tunnels:

    • Hosts automatically select the next best tunnel if their tunnel fails.
    • Non-hosts broadcast failure messages so the host can respond (switch tunnel or kick the player).

Still to come...

  • GameLoadingLobby integration
  • Peer-to-Peer (P2P) connections
  • In-game tunnel monitoring and hot-swapping

Showing the negotiation in the lobby, and the negotiation status panel (available for all players).
image

Showing a change to the game creation window and a game's ping:
image

New settings:
image

Add V3GameTunnelBridge
Add V3TunnelCommunicator
Add V3TunnelNegotiator
Add TunnelFailed event
Update supported tunnel versions
Fixup GameCreationWindow control visibility
Remove unnecessary tunnel parameter in V3PlayerInfo
Add NegotiationStatusPanel
Add tunnel negotiations to CnCNetGameLobby
Add V3 tunnel support to CnCNetGameLobby
Move to file-scoped namespaces
Fix missing texture on status panel
Fix issue with decider seeing OK instead of ping results
Fix an issue with automatic tunnel selection
Style fixups
Split TunnelChosenEventArgs into new file
Remove IDisposable on bridge
Remove unecessary code
Better updating of player ping indicators
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Oct 11, 2025

Nightly build for this pull request:

  • artifacts.zip
    This comment is automatic and is meant to allow guests to get latest automatic builds without registering. It is updated on every successful build.

@CnCRAZER
Copy link
Copy Markdown
Contributor

Perhaps the ability to add a button within one of the clients ini files to toggle between V2 and V3 would be useful. Just a thought

Comment thread DXMainClient/Domain/Multiplayer/CnCNet/NegotiationDataManager.cs
tunnel.PingInMs = pingResult;
}

if (previousPing > 0 && (tunnel.PingInMs <= 0 || tunnel.PingInMs > TUNNEL_FAILED_PING_AMOUNT))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does 0 ms indicate a failure for now? Would be better using -1, in case some one runs a tunnel server AND plays the game, so the distance between tunnel server and the gaming computer is so close

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@11EJDE11

public override int Ping => TunnelServer?.PingInMs ?? 0;

0 or -1?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also previousPing > 0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest fully go through the code to ensure the ping value successfully deals with 0 and -1. Alternatively to simplify the procedure and reduce error you might also want to wrap the ping int as a readonly record (so we can use something like ping.IsValid()), if needed

Comment thread DXMainClient/Domain/Multiplayer/CnCNet/TunnelHandler.cs Outdated
Comment thread DXMainClient/Domain/Multiplayer/CnCNet/V3PlayerInfo.cs Outdated
Comment thread DXMainClient/Domain/Multiplayer/CnCNet/V3PlayerInfo.cs Outdated
Comment thread DXMainClient/Domain/Multiplayer/CnCNet/V3PlayerNegotiator.cs Outdated
Comment thread DXMainClient/Domain/Multiplayer/CnCNet/V3PlayerNegotiator.cs
Comment thread DXMainClient/Domain/Multiplayer/CnCNet/V3TunnelCommunicator.cs
11EJDE11 added 28 commits March 14, 2026 16:26
Fix selectionTcs hang when totalTunnels <= 1
Resolved conflicts in ProgramConstants.cs (protocol revision "A"),
V3PlayerNegotiator.cs (keep NegotiationFailed packet on non-decider
timeout, use remote's RaiseNegotiationComplete pattern), and
CnCNetGameLobby.cs (combine no-tunnels early return with remote's
_negotiationData.UpdateStatus and visual indicator updates).
…o the lobby doesn't broadcast Failed when an in-flight negotiation already exists for the player.
…acket arrives, instead of wasting up to 10 seconds on retries that the non-decider has already given up on.
…ich would otherwise leave the awaited selection task unresolved forever.
…ider role is chosen by ID comparison and a collision would silently deadlock the negotiation.
@SadPencil SadPencil added this to the long term milestone May 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

P2P & V3 tunnel support

6 participants