NAT Punchthrough

NAT Punchthrough enables peer-to-peer connections between clients behind NAT routers.

Overview

Most home internet connections use NAT (Network Address Translation). Direct P2P connections fail because:

  1. External IP is shared among devices

  2. Router blocks unsolicited incoming packets

  3. Port mappings are dynamic

NAT Punchthrough solves this using a facilitator server.

Architecture

Client A (behind NAT)     Facilitator Server     Client B (behind NAT)
       │                         │                        │
       ├────── Register ────────►│                        │
       │                         │◄────── Register ───────┤
       │                         │                        │
       ├─── Request Punch to B ─►│                        │
       │                         │──── Notify A wants ───►│
       │                         │        to connect      │
       │                         │                        │
       │◄──── Punch timing ──────┤─── Punch timing ──────►│
       │                         │                        │
       ├──────── UDP Punch ──────┼────────────────────────┤
       │◄─────── UDP Punch ──────┼────────────────────────┤
       │                         │                        │
       │◄════════ Direct P2P Connection ═════════════════►│

Server Setup (Facilitator)

#include "mafianet/NatPunchthroughServer.h"

MafiaNet::RakPeerInterface* server = MafiaNet::RakPeerInterface::GetInstance();

// Attach NAT punchthrough server
MafiaNet::NatPunchthroughServer* natServer =
    MafiaNet::NatPunchthroughServer::GetInstance();
server->AttachPlugin(natServer);

// Start server
MafiaNet::SocketDescriptor sd(NAT_SERVER_PORT, 0);
server->Startup(1000, &sd, 1);
server->SetMaximumIncomingConnections(1000);

Client Setup

#include "mafianet/NatPunchthroughClient.h"

MafiaNet::RakPeerInterface* peer = MafiaNet::RakPeerInterface::GetInstance();

// Attach NAT punchthrough client
MafiaNet::NatPunchthroughClient* natClient =
    MafiaNet::NatPunchthroughClient::GetInstance();
peer->AttachPlugin(natClient);

// Start peer
MafiaNet::SocketDescriptor sd;
peer->Startup(8, &sd, 1);

// Connect to facilitator
peer->Connect(NAT_SERVER_IP, NAT_SERVER_PORT, nullptr, 0);

Initiating Punchthrough

After connecting to the facilitator:

// Get target's GUID (from matchmaking, lobby, etc.)
MafiaNet::RakNetGUID targetGuid = GetTargetFromLobby();

// Initiate punchthrough
MafiaNet::SystemAddress facilitatorAddr = GetFacilitatorAddress();
natClient->OpenNAT(targetGuid, facilitatorAddr);

Handling Results

switch (packet->data[0]) {
    case ID_NAT_PUNCHTHROUGH_SUCCEEDED: {
        // Can now connect directly!
        MafiaNet::SystemAddress addr = packet->systemAddress;
        printf("NAT punch succeeded to %s\n", addr.ToString());

        // Connect directly
        peer->Connect(addr.ToString(false), addr.GetPort(),
                     nullptr, 0);
        break;
    }

    case ID_NAT_PUNCHTHROUGH_FAILED: {
        MafiaNet::RakNetGUID failedGuid;
        MafiaNet::BitStream bs(packet->data, packet->length, false);
        bs.IgnoreBytes(1);
        bs.Read(failedGuid);

        printf("NAT punch failed to %s\n", failedGuid.ToString());

        // Consider using Router2 as fallback
        break;
    }

    case ID_NAT_TARGET_NOT_CONNECTED:
        printf("Target not connected to facilitator\n");
        break;

    case ID_NAT_ALREADY_IN_PROGRESS:
        printf("Punchthrough already in progress\n");
        break;
}

NAT Types

Not all NAT types can be punched through:

Type

Punchthrough Success

Full Cone

Always succeeds

Address Restricted

Usually succeeds

Port Restricted

Usually succeeds

Symmetric

May fail (use Router2)

Use NATTypeDetection Plugin to check NAT types before attempting.

Best Practices

  1. Always have a fallback: Use Router2 when punchthrough fails

  2. Detect NAT type first: Warn users with symmetric NAT

  3. Use multiple facilitators: For redundancy

  4. Timeout handling: Set reasonable timeouts

See Also