Hi, thanks for reviving the old PySTUN! However I've found that the NAT detection is strange and gives results that are later not verifiable. So I would like to share my findings with you, to see if it's possible to improve PyNAT.
PyNAT detects my (consumer-grade, typical home router) NAT as Symmetric, but I don't think this is actually true. From my manual tests, the NAT seems to be Port-Restricted Cone NAT, i.e. ports allow inbound packets if they come from same IP:port than a previously outbound packet was sent, and local outbound ports are kept the same on the external side.
I feel the issue comes from using the STUN response's CHANGED-ADDRESS field, which I didn't know about until I saw it in PyNAT code. Went ahead to have a look at STUN spec: RFC 8489 but it only says that attribute 0x0005 is "Reserved; was CHANGED-ADDRESS prior to [RFC5389]"...
In RFC 5389 they still refer to an even older version of the spec, mentioning that this attribute got removed:
Removed the usage of STUN for NAT type detection and binding
lifetime discovery. These techniques have proven overly brittle
due to wider variations in the types of NAT devices than described
in this document. Removed the RESPONSE-ADDRESS, CHANGED-ADDRESS,
CHANGE-REQUEST, SOURCE-ADDRESS, and REFLECTED-FROM attributes.
I added some logs in the PyNAT code and saw this:
$ pynat
stun_addr: ('stun.voipbuster.com', 3478)
ext_ip, ext_port: 1.2.3.4 54320 <--- External port = 54320
change_addr: ('77.72.169.213', 3479)
recv_ext_ip, recv_ext_port: 1.2.3.4 1053 <--- Mismatch, 54320 != 1053
Network type: Symmetric NAT
Internal address: 192.168.1.2:54320
External address: 1.2.3.4:54320
Where 1.2.3.4 is my public IP address. PySTUN performs a STUN request from internal port 54320, and that's the external port that comes in the response from voipbuster. However, the next request that is done with the change_addr claims a different external port 1053, which is why PyNAT classifies my NAT as Symmetric.
What seems strange to me is that this second port, 1053, is repeated in all tests, even though PyNAT varies the STUN server that is used for each test. If this actually was a Symmetric NAT, the external port should be changing for each different destination, shouldn't it?
Then I went ahead and did this same test on my own. For this I use 2 different cloud servers, I access them with SSH and use Netcat to perform hole-punching on my NAT and later check connectivity. This is the result:
On SERVER 1 (IP: 1.0.0.1): listen for incoming UDP on port 51000.
$ nc -vnul 51000
Listening on 0.0.0.0 51000
On SERVER 2 (IP: 2.0.0.2): listen for incoming UDP on port 52000.
$ nc -vnul 52000
Listening on 0.0.0.0 52000
On CLIENT (IP: 1.2.3.4): Send data from port 55000 to Server 1 and Server 2, i.e. hole-punch the NAT's port 55000.
$ echo "TO SERVER 1" | nc -vnu -p 55000 1.0.0.1 51000
Connected to 1.0.0.1:51000
$ echo "TO SERVER 2" | nc -vnu -p 55000 2.0.0.2 52000
Connected to 2.0.0.2:52000
Data appears on Server 1 and 2 output, confirming that it has arrived correctly:
Connection received from 1.2.3.4 55000
TO SERVER 1
Connection received from 1.2.3.4 55000
TO SERVER 2
On CLIENT: listen for incoming UDP on port 55000 which has been hole-punched in previous commands.
$ nc -vnul 55000
Listening on 0.0.0.0 55000
On SERVER 1: stop listening, and send data to Client.
(Ctrl+C)
$ echo "FROM SERVER 1" | nc -vnu -p 51000 1.2.3.4 55000
Connected to 1.2.3.4:55000
Data appears on Client output:
Connection received from 1.0.0.1 51000
FROM SERVER 1
... confirming that Server 1 can send data. This means there is connectivity to the Client through the port 55000 that was punched before.
On CLIENT: restart the listener (this is needed by Netcat).
(Ctrl+C)
$ nc -vnul 55000
Listening on 0.0.0.0 55000
On SERVER 2: stop listening, and send data to Client.
(Ctrl+C)
$ echo "FROM SERVER 2" | nc -vnu -p 52000 1.2.3.4 55000
Connected to 1.2.3.4:55000
Here is the interesting thing: data appears on Client output:
Connection received from 2.0.0.2 52000
FROM SERVER 2
... confirming that Server 2 can also send data to the same port, and also that when multiple, consecutive outbound connections are made to different servers, the external port is not randomly changed by the NAT.
In principle that reminds of the behavior of Full-Cone NAT, but in reality it is an (Address- and) Port-Restricted NAT. I know because the ports that get opened in the NAT, will only allow inbound packets from the same exact source IP and port. I tested changing the listening ports, or opening a port only for Server 1 and then trying to send data from Server 2, and it didn't work, which means it's not a Full-Cone NAT.
I believe the external port 1053 is maybe an unintended consequence of using an old and now deprecated STUN method to try and force different Address and Port on the STUN responses from the servers, but it could be causing bad detection of NAT types.
I wish I had more time to work on this and provide some code, but right now I only could get time to do this study and write this issue... but what do you think? Maybe it would be possible to use two different STUN servers for testing, like I did by hand in my tests? This would mean that CHANGED-ADDRESS is not needed any more.
Hi, thanks for reviving the old PySTUN! However I've found that the NAT detection is strange and gives results that are later not verifiable. So I would like to share my findings with you, to see if it's possible to improve PyNAT.
PyNAT detects my (consumer-grade, typical home router) NAT as Symmetric, but I don't think this is actually true. From my manual tests, the NAT seems to be Port-Restricted Cone NAT, i.e. ports allow inbound packets if they come from same IP:port than a previously outbound packet was sent, and local outbound ports are kept the same on the external side.
I feel the issue comes from using the STUN response's
CHANGED-ADDRESSfield, which I didn't know about until I saw it in PyNAT code. Went ahead to have a look at STUN spec: RFC 8489 but it only says that attribute 0x0005 is "Reserved; was CHANGED-ADDRESS prior to [RFC5389]"...In RFC 5389 they still refer to an even older version of the spec, mentioning that this attribute got removed:
I added some logs in the PyNAT code and saw this:
Where
1.2.3.4is my public IP address. PySTUN performs a STUN request from internal port 54320, and that's the external port that comes in the response from voipbuster. However, the next request that is done with the change_addr claims a different external port 1053, which is why PyNAT classifies my NAT as Symmetric.What seems strange to me is that this second port, 1053, is repeated in all tests, even though PyNAT varies the STUN server that is used for each test. If this actually was a Symmetric NAT, the external port should be changing for each different destination, shouldn't it?
Then I went ahead and did this same test on my own. For this I use 2 different cloud servers, I access them with SSH and use Netcat to perform hole-punching on my NAT and later check connectivity. This is the result:
On SERVER 1 (IP: 1.0.0.1): listen for incoming UDP on port 51000.
On SERVER 2 (IP: 2.0.0.2): listen for incoming UDP on port 52000.
On CLIENT (IP: 1.2.3.4): Send data from port 55000 to Server 1 and Server 2, i.e. hole-punch the NAT's port 55000.
Data appears on Server 1 and 2 output, confirming that it has arrived correctly:
On CLIENT: listen for incoming UDP on port 55000 which has been hole-punched in previous commands.
On SERVER 1: stop listening, and send data to Client.
Data appears on Client output:
... confirming that Server 1 can send data. This means there is connectivity to the Client through the port 55000 that was punched before.
On CLIENT: restart the listener (this is needed by Netcat).
On SERVER 2: stop listening, and send data to Client.
Here is the interesting thing: data appears on Client output:
... confirming that Server 2 can also send data to the same port, and also that when multiple, consecutive outbound connections are made to different servers, the external port is not randomly changed by the NAT.
In principle that reminds of the behavior of Full-Cone NAT, but in reality it is an (Address- and) Port-Restricted NAT. I know because the ports that get opened in the NAT, will only allow inbound packets from the same exact source IP and port. I tested changing the listening ports, or opening a port only for Server 1 and then trying to send data from Server 2, and it didn't work, which means it's not a Full-Cone NAT.
I believe the external port 1053 is maybe an unintended consequence of using an old and now deprecated STUN method to try and force different Address and Port on the STUN responses from the servers, but it could be causing bad detection of NAT types.
I wish I had more time to work on this and provide some code, but right now I only could get time to do this study and write this issue... but what do you think? Maybe it would be possible to use two different STUN servers for testing, like I did by hand in my tests? This would mean that CHANGED-ADDRESS is not needed any more.