ZeroTierOne icon indicating copy to clipboard operation
ZeroTierOne copied to clipboard

Detecting Symmetric NAT

Open laduke opened this issue 1 year ago β€’ 0 comments

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)

laduke avatar Sep 04 '24 20:09 laduke