-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Network Address Translation
An old version of the stun spec (RFC3489) defined the following NAT variations:
Full Cone: A full cone NAT is one where all requests from the same internal IP address and port are mapped to the same external IP address and port. Furthermore, any external host can send a packet to the internal host, by sending a packet to the mapped external address.
Restricted Cone: A restricted cone NAT is one where all requests from the same internal IP address and port are mapped to the same external IP address and port. Unlike a full cone NAT, an external host (with IP address X) can send a packet to the internal host only if the internal host had previously sent a packet to IP address X.
Port Restricted Cone: A port restricted cone NAT is like a restricted cone NAT, but the restriction includes port numbers. Specifically, an external host can send a packet, with source IP address X and source port P, to the internal host only if the internal host had previously sent a packet to IP address X and port P.
Symmetric: A symmetric NAT is one where all requests from the same internal IP address and port, to a specific destination IP address and port, are mapped to the same external IP address and port. If the same host sends a packet with the same source address and port, but to a different destination, a different mapping is used. Furthermore, only the external host that receives a packet can send a UDP packet back to the internal host.
Additional research can be found in RFC5780
ccding/go-stun may be helpful to figure out your nat type(s).
NAT bindings should not be considered to be deterministic:
- If an external port is already mapped to another host the NAT has to assign you another port.
- Some routers implement randomized port mapping.
Hole punching is an easy way to circumvent a NAT. Peers try to establish a direct connection by learning their respective public addresses using a 3rd party server. The 3rd party server is also used to keep the NAT binding alive.
The hole punching examples below can be used to get through the Full Cone, Restricted Cone and Port Restricted Cone NAT types. Symmetric NATs require more complex processes. However, this NAT type is more rare and can still be circumvented using a TURN server to relay traffic.
First establisch a local connection by listening using net.ListenUDP
. Next use WriteTo
and ReadFrom
to send and receive packets to and from the desired remote addresses.
TCP hole punching is a little more involved. In order to keep the NAT binding alive the same local TCP port should be used. This can be done by setting the SO_REUSEADDR and SO_REUSEPORT socket options on most operating systems. Sadly this isn't well supported until go1.11 as discussed in golang/go#9661. Starting from go1.11 the flag can be set using the Dialer.Control function.
Example (dial.go):
var d net.Dialer
d.Control = controlOnConnSetup
func controlOnConnSetup(network string, address string, c syscall.RawConn) error {
return nil
}
For Unix (dial_unix.go):
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
func controlOnConnSetup(c syscall.RawConn, addr Addr) error {
var operr error
fn := func(s uintptr) {
operr = syscall.SetsockoptInt(int(s), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if operr != nil {
return
}
operr = syscall.SetsockoptInt(int(s), syscall.SOL_SOCKET, syscall.SO_REUSEPORT, 1)
if operr != nil {
return
}
}
if err := c.Control(fn); err != nil {
return err
}
if operr != nil {
return operr
}
return nil
}
For Windows (dial_windows.go):
func controlOnConnSetup(network string, address string, c syscall.RawConn) error {
var operr error
fn := func(s uintptr) {
handle := syscall.Handle(fd)
operr = syscall.SetsockoptInt(handle, syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
if operr != nil {
return
}
}
if err := c.Control(fn); err != nil {
return err
}
if operr != nil {
return operr
}
return nil
}
Following chart summarises which types of candidates contribute to a successful NAT traversal in each combination of local and remote NAT types.
Open | F.Cone | R.Cone | PR.Cone | Symmetric | |
---|---|---|---|---|---|
Open | host | srflx or prflx | srflx or prflx | srflx or prflx | prflx |
F.Cone | srflx or prflx | srflx | srflx | srflx | srflx and prflx |
R.Cone | srflx or prflx | srflx | srflx | srflx | srflx and prflx |
PR.Cone | srflx or prflx | srflx | srflx | srflx | relay |
Symmetric | prflx | srflx and prflx | srflx and prflx | relay | relay |
- Open: Open to the Internet
- F.Cone: Full-cone NAT
- R.Cone: Restricted-cone NAT
- PR.Cone: Port restricted-cone NAT
- Symmetric: Symmetric NAT
- host: Local (host) address candidate
- srflx: Server reflexive candidate (a candidate derived from STUN)
- prflx: Peer reflexive candidate
- relay: Relay NAT (a candidate derived from TURN)
Sign up for the Golang Slack and join the #pion channel for discussions and support
If you need commercial support/don't want to use public methods you can contact us at team@pion.ly