NAT_P2P

P2P problem

Origination of the problem

I am going to build a game which only involve two player, then it would be nature to consider connecting them directly, aka P2P networking.

The game is written in Javascript, so it WebRTC appear to be my answer. After some research, I choose to use the library called peerjs, a wrapper around the WebRTC API.

Test P2P Network

In the examples given by peerjs, I test the ‘Peer-to-Peer Cue System’, and other P2P app in the wild. It appears that the app only works when the sender and receiver are on the same host. It doesn’t work when my Mac serves as sender and Lab Desktop serves as receiver.

Dig into the code

I download the ‘Peer-to-Peer Cue System’ source code from Github to modify and get some sense. Finally I come with the following test code by following the peerjs documentation.

Sender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<!DOCTYPE html>
<html>
<head>

</head>
<body>
<div id="peerID"> ID :</div>
<div id="otherID"></div>
<div id="status"></div>
<input id="destID">
<button onclick="connectPeer()">Connect</button>
</body>
<script src="https://unpkg.com/peerjs@1.5.4/dist/peerjs.min.js"></script>
<script>

var peerID = document.getElementById('peerID');
var otherID = document.getElementById('otherID');
var stat = document.getElementById('status');

var peer = new Peer({
config : {
'iceServers' : [
{ url: 'stun:stun.l.google.com:19302' }
]
}
});

//create a new peer
peer.on('open', function(id) {
console.log('My peer ID is: ' + id);
peerID.textContent = 'My peer ID is: ' + id;
});

function connectPeer(){
var destID = document.getElementById('destID').value;
console.log("try to connect to " + destID)

//connect to peer
var conn = peer.connect(destID, {reliable : true});

console.log("connected to : " + conn.peer);
otherID.innerText = "connected to : " + conn.peer ;
stat.innerText = "Connection is " + conn.open ;

// open the connection
conn.on('open' , function () {
stat.innerHTML = "Connection turned to open " + conn.open ;
});
}
</script>
</html>

Receiver

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!DOCTYPE html>
<html>
<head></head>
<body>
<div id="peerID"></div>
<div id="otherID"></div>
<div id="status"></div>
</body>
<script src="https://unpkg.com/peerjs@1.5.4/dist/peerjs.min.js"></script>
<script>
var peer = new Peer({
config : {
'iceServers' : [
{ url: 'stun:stun.l.google.com:19302' }
]
}
});

var peerID = document.getElementById('peerID');
var otherID = document.getElementById('otherID');
var stat = document.getElementById('status');

peer.on('open', function(id) {
console.log('My peer ID is: ' + id);
peerID.innerText = 'My peer ID is: ' + id ;
});
// waiting for connection
peer.on('connection', function(conn) {
//once p2p is established
otherID.innerText = "Connected with " + conn.peer ;
stat.innerText = "The connection is " + conn.open ;

//open the connection
conn.on('open' , function(){
stat.innerText = "Connection turned to open : " + conn.open ;
})
});
</script>
</html>

Result

Sender and receiver will successfully establish connection with conn become both readable and writeable on the same host.

When they need to communicate through the network, even specify the STUN server wouldn’t help. The conn will always be false, indicating it is not R/W.

STUN, TURN and Symmetrical NAT

I then think maybe it is the STUN server doesn’t work. But it turns out that the reason is I am sitting behind a symmetrical NAT.

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.

So the STUN server can only punching the Asymmetrical NAT, by exchanging NAT mapped IP and port of P2P pair. For symmetrical NAT, a TURN server will be needed. A TURN server serves as a relay, connecting P2P pair.

That makes me give the idea of building the game via P2P communication a second thought. Since a server is a must have, the question become choosing between C/S architecture vs P2P architecture. If we use P2P architecture, the server will do nothing but forwording the traffic, but if we choose C/S architecture, the server will also response for game state computation and other stuff.

Test

Pystun3 is a Python STUN client for getting NAT type and external IP. Supports Python versions 2 and 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
$ pip install pystun3
$ pystun3 -d
DEBUG:pystun3:Do Test1
DEBUG:pystun3:Trying STUN host: stun.ekiga.net
DEBUG:pystun3:sendto: ('stun.ekiga.net', 3478)
DEBUG:pystun3:sendto: ('stun.ekiga.net', 3478)
DEBUG:pystun3:sendto: ('stun.ekiga.net', 3478)
DEBUG:pystun3:sendto: ('stun.ekiga.net', 3478)
DEBUG:pystun3:Trying STUN host: stun.ideasip.com
DEBUG:pystun3:sendto: ('stun.ideasip.com', 3478)
DEBUG:pystun3:Trying STUN host: stun.voiparound.com
DEBUG:pystun3:sendto: ('stun.voiparound.com', 3478)
DEBUG:pystun3:sendto: ('stun.voiparound.com', 3478)
DEBUG:pystun3:sendto: ('stun.voiparound.com', 3478)
DEBUG:pystun3:sendto: ('stun.voiparound.com', 3478)
DEBUG:pystun3:Trying STUN host: stun.voipbuster.com
DEBUG:pystun3:sendto: ('stun.voipbuster.com', 3478)
DEBUG:pystun3:recvfrom: ('77.72.169.211', 3478)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': '121.229.106.72', 'ExternalPort': 41821, 'SourceIP': '77.72.169.211', 'SourcePort': 3478, 'ChangedIP': '77.72.169.210', 'ChangedPort': 3479}
DEBUG:pystun3:Do Test2
DEBUG:pystun3:sendto: ('stun.voipbuster.com', 3478)
DEBUG:pystun3:sendto: ('stun.voipbuster.com', 3478)
DEBUG:pystun3:sendto: ('stun.voipbuster.com', 3478)
DEBUG:pystun3:sendto: ('stun.voipbuster.com', 3478)
DEBUG:pystun3:Result: {'Resp': False, 'ExternalIP': None, 'ExternalPort': None, 'SourceIP': None, 'SourcePort': None, 'ChangedIP': None, 'ChangedPort': None}
DEBUG:pystun3:Do Test1
DEBUG:pystun3:sendto: ('77.72.169.210', 3479)
DEBUG:pystun3:recvfrom: ('77.72.169.210', 3479)
DEBUG:pystun3:Result: {'Resp': True, 'ExternalIP': '121.229.106.72', 'ExternalPort': 42299, 'SourceIP': '77.72.169.210', 'SourcePort': 3479, 'ChangedIP': '77.72.169.211', 'ChangedPort': 3478}
NAT Type: Symmetric NAT
External IP: 121.229.106.72
External Port: 42299
$ pystun3
NAT Type: Symmetric NAT
External IP: 121.229.106.72
External Port: 4737

If P2P is your choice, open relay should be considered. Or you want to build your own TURN server, it is also very simple with coTURN.

Also, there is a good feer TURN provider called Metered.ca. When using TURN Credentials, beware to delete the STUN server url and use TURN servers only.

How about IPv6

Unfortunately, ISP and router doesn’t support it, hardware upgrade is difficult.