Detecting Symmetric NAT
For discussion.
I'd like to add some info to zerotier-cli status -j in the interest of making it more obvious, to us and the user, when Hard NAT is causing a problem.
We currently have these two objects in status
"listeningOn": [
"192.168.82.193/9993",
"192.168.82.193/63095",
"192.168.82.193/37301"
],
"surfaceAddresses": [
"198.51.100.2/50261",
"198.51.100.2/63095",
"198.51.100.2/37301"
],
If the surfaceAddresses are growing bigger than the listeningOns, you can guess that Symmetric NAT is happening.
I think if there was some kind of map between these two sets, and maybe with the roots, it will be easier to manually read what is happening, and to make tools that can read this and display it to the user. One of the main heuristics is, are the different roots seeing the same mapped port?
I don't know if we have access to the right info in the right place to build this output.
I'm not sure what the shape should be yet. I noodled on some shapes and liked this best so far. It's verbose. You could do nested objects to make it smaller, but those are more of a pain to deal with in my experience.
// easy nat. each listen port gets the same surface port for every root
{
"surface": [
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "cafe04eba9" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "cafe9ccda7" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "778cde7190" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "cafe9efeb9" },
{ listenAddress: "192.168.82.193", listenPort: 12345, surfaceAddress: "198.51.100.2", surfacePort: 10001, root: "cafe04eba9" },
{ listenAddress: "192.168.82.193", listenPort: 12345, surfaceAddress: "198.51.100.2", surfacePort: 10001, root: "cafe9ccda7" },
{ listenAddress: "192.168.82.193", listenPort: 12345, surfaceAddress: "198.51.100.2", surfacePort: 10001, root: "778cde7190" },
{ listenAddress: "192.168.82.193", listenPort: 12345, surfaceAddress: "198.51.100.2", surfacePort: 10001, root: "cafe9efeb9" },
{ listenAddress: "192.168.82.193", listenPort: 24680, surfaceAddress: "198.51.100.2", surfacePort: 20002, root: "cafe04eba9" },
{ listenAddress: "192.168.82.193", listenPort: 24680, surfaceAddress: "198.51.100.2", surfacePort: 20002, root: "cafe9ccda7" },
{ listenAddress: "192.168.82.193", listenPort: 24680, surfaceAddress: "198.51.100.2", surfacePort: 20002, root: "778cde7190" },
{ listenAddress: "192.168.82.193", listenPort: 24680, surfaceAddress: "198.51.100.2", surfacePort: 20002, root: "cafe9efeb9" },
]
}
// hard nat. surfacePort mapped to unique port for every connection
{
"surface": [
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "cafe04eba9" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 55555, root: "cafe9ccda7" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 24680, root: "778cde7190" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 36912, root: "cafe9efeb9" },
etc...
]
}
(by the way the address/port format we commonly use is kind of a pain to deal with in external tools IMO. easier to select addr, port and proto fields, than grepping for colons and slashes. )
highly normalized could look something like this;
{
"surface": {
"192.168.82.193/9993": {
"cafe04eba9": {
"surfaceAddress": "198.51.100.2/12345"
},
"cafe9ccda7": {
"surfaceAddress": "198.51.100.2/12345"
}
}
}
}
But again I think it's harder to deal maps with in js and in typed languages. and it's not clearer to just read; the "types" are implicit.
Does the cache of surface address hold more than one mapping per address/port/root? Like if you move locations or your router changes the mapping?
for example, could this happen?
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 12345, root: "cafe04eba9" },
{ listenAddress: "192.168.82.193", listenPort: 9993, surfaceAddress: "198.51.100.2", surfacePort: 22222, root: "cafe04eba9" },
It the info was ouput to a terminal or html table, it would probably look like
| listenAddr | listenAddrPort | surfaceAddr | surfacePort | rootAddress |
|---|---|---|---|---|
| 192.168.82.3 | 9993 | 198.51.100.2 | 9993 | cafe04eba9 |
| 192.168.82.3 | 9993 | 198.51.100.2 | 9993 | cafe9ccda7 |
if a UI reduced it down:
| listeningOn | mappings | ok? |
|---|---|---|
| 192.168.82.3/9993 | 1 | good |
| listeningOn | mappings | ok? |
|---|---|---|
| 192.168.82.3/9993 | 4 | bad |
or to simplify further:
Connection type : WAN π― Easy NATπ Hard NAT π (Link to fixing here)