NAT traversal - CESNET/UltraGrid GitHub Wiki

There are multiple options how to traverse NAT depending on NAT type and whether or not one of endpoint has public IP address. Some of the techniques is implemented in UltraGrid (see below) but most aren't and need to be handled manually. The list below is incomplete, feel free to contact us for details.

Table of contents

UltraGrid options

NAT-PMP/PCP

UltraGrid currently implements PCP and NAT PMP which can be triggered by -N option for the host behind NAT. If you see something like this:

behind-NAT$ uv -N -d gl -r portaudio
[NAT PMP] Public IP address: 93.184.216.34
Successfully set NAT traversal with NAT PMP. Sender can send to external IP address.

other-peer$ uv -t testcard -c libavcodec -s testcard 93.184.216.34

It means that UltraGrid ports (UDP 5004 and 5006 by default) are being forwarded. Remote peer may send to the external IP address. The -N/--nat-traversal parameter also accepts some options, see uv -Nhelp.

Advantage of this approach is that both peers can be behind NAT.

Server mode

Note: Do not confuse with RTSP Server or SDP which is conceptually different (and mostly incompatible) approach solving other problems.

If one side has a public IP address, it can act as a server. In this mode it awaits for a connection from a receiver and sends data in a response, which should pass through NAT and even a firewall (depending on configuration). This obviously makes sense only if the peer behind NAT wants to receive the stream (otherwise no special handling is needed).

Current limitations:

  1. As soon as the "connection" is established, receiver cannot be changed (the server sends to the receiver until stopped).
  2. Multiple clients are not supported.

Usage:

uv [-S|--server] [-C|--client addr]

Example:

sender$ uv -t testcard -c libavcodec -s testcard --server
client$ uv -d gl -r alsa --client senderIP

There can be also the reverse traffic:

sender$ uv -t testcard -c libavcodec -s testcard -r alsa -d gl --server 
client$ uv -d gl -r alsa -t decklink -s alsa --client senderIP

If neither of the ends has public IP address, this approach can be combined with NAT-PMP/PCP if one of the ends supports that protocol.

UDP hole punching

In this scenario, we assume one host has a public IP while the other doesn't. We are assuming that the side behind NAT is a receiver. If it were a sender only, the situation is trivial and doesn't need any workaround.

Note: This manual approach is conceptually the same as the server mode described above is using. You can refer there for a simpler solution.

The idea behind the approach described below is that the computer behind NAT initializes the connection. It is analogous to TCP in a sense that the NAT keeps track of the "connection" state even for UDP. Client behind the NAT is required to send keepalive data to keep the UDP hole open the whole time (even if it acts as a receiver only).

In easiest scenario, we create an UDP hole from inside and send to it from outside. We are assuming use of default ports (5004 for video, 5006 for audio). Assuming that A has public IP while B is behind NAT (doesn't have a public IP address).

A ---------------- B NAT --- B
^                     ^      ^
|                     |       \
A IP (pub)      NAT IP (pub)   B IP (private)

This approach utilizes a concept of UDP hole punching. Follows the setup of both machines.

Behind NAT (host B)

If we act in a full-duplex way (both sender and receiver), you can use simply:

uv -t decklink -c JPEG -d gl A

Please note that both -t and -d are required.

If you want to receive only, use something like:

uv -t testcard:32:32:10:UYVY -d gl A

to generate a dummy keepalive traffic (to keep the connection open).

Outside NAT (host A)

In easiest case you can run UltraGrid as usual (assuming you now B's NAT IP address):

uv -t decklink -d gl B_NAT

This is sufficient if you send and receive, if outside UltraGrid is sender only, use:

uv -t decklink -c JPEG -P 5004:5004

This is because if UltraGrid acts as a sender only, it uses random source port which may confuse NAT.

This works as long as you know the NAT address and NAT keeps port numbers unchanged. If not, you may obtain the address of the B's NAT and source port by running:

sudo tcpdump udp port 5004 2>/dev/null | head -n 1
16:24:25.346523 IP receiver_ip.57278 > sender_ip.5004: UDP, length 68

There you should see address and src port of the NAT.

With this knowledge, you can send to B using the NAT address and port:

uv -t decklink -P 5004:57278 receiver_ip

Audio

Audio setup is not described to details for simplicity but is analogous to video - we need always send data from inside the NAT (host B) to keep the UDP hole opened.

If the NAT changes port numbers you will also need to obtain the audio source port used by NAT (can be done by jnettop - see above). Full commands may look like:

B$ uv -t decklink -s analog A

A$ uv -t decklink -s alsa -P 5004:NAT_VID_RX:5006:NAT_AUD_RX NAT_IP

or simply (if NAT keeps port numbers):

A$ uv -t decklink -s alsa NAT_IP

Both hosts private address

When both hosts are behind their respective NATs, they both need to punch a hole by sending packets to each others NAT public address. Doing this is challenging, because both hosts need to somehow obtain their public facing address and exchange them. Since they can not communicate yet at this stage, a separate server with a public ip address is needed to coordinate a facilitate this exchange. UltraGrid has a small utility called nat-helper (located in the nat_helper directory of the git repository) for this purpose (see included readme for build instructions).

To use this type of hole punching there needs to be a server with a public ip address running the nat-helper utility and a STUN server. UltraGrid is then launched with the argument

-Nholepunch:room=<name_of_room>:server=<server_running_nat-helper_and_STUN>

if the nat-helper and STUN server are running on the same ip and standard ports (3478 for STUN, 12558 for nat-helper), otherwise you can specify the address and port with coord_srv=<host:port> and stun_srv=<host:port>. When two hosts join the same room, nat-helper forwards the necessary info between the hosts and a connection is made.

Note that this technique works only if the NAT used preserves the : mapping between connections to other addresses.

Another option is to host a relay/translator on some third party host with public IP address. To realize such a scenario, you can use reflectors. One of techniques bellow may also be used.

External tools

Tools/protocol listed below can facilitate NAT traversal for UltraGrid stream using external tools. Its applicability depends on NAT type and its setting.

STUN

Note: Not tested

STUN can be used to discover external address and port mapping that can be subsequently used by other side to send to.

With STUN you can traverse several NAT types. First you need to check your NAT type, you can issue:

hostA$ stun -p 5004 stun.sonetel.net
STUN client version 0.97
Primary: Independent Mapping, Port Dependent Filter, preserves ports, no hairpin
Return value is 0x000017

Then match your NAT type according to this page. If your NAT is (address-) restricted cone NAT or full cone NAT (see also here and here), you may be able to send to port opend by STUN:

hostB$ uv -t testcard -c libavcodec -P 3478:5004 <hostA_external_IP>

NAT PMP

NAT PMP is a signalization technique to instruct gateway to forward ports, sample usage using libnatpmp:

natpmpc -a 5004 5004 UDP 7200
natpmpc -a 5006 5006 UDP 7200

adds forwarding of UDP ports 5004 and 5006 to current host for 7200 seconds.

Note: NAT PMP is supported natively by UltraGrid.

UPnP IGP

You can use MiniUPnP tool, first check status:

upnpc -s

If your router accepts IGP, you can set forwarding:

upnpc -a <IP> 5004 5004 UDP [duration]
upnpc -a <IP> 5006 5006 UDP [duration]

PCP

PCP is a evolution of NAT PMP. A working implementation can be found here. Simple use for UltraGrid would be:

pcp -u -i <IP>:5004
pcp -u -i <IP>:5006

where <IP> is machine LAN IP address.

Note: PCP is supported natively by UltraGrid.

⚠️ **GitHub.com Fallback** ⚠️