TCP hole punching for Bitcoin nodes behind home NATs?

I’ve set up a server on two different IPs that returns the NAT IP:port to a connecting client. One can use nat-check.py to connect to these and get a classification of their NAT. The code for this, mostly written by an LLM, can be found in GitHub - 0xB10C/tcp-nat-check: A tool to check the NAT type of your router, which tells you if TCP hole punching would work on your router. · GitHub. Making requests to my hosts leaks your IP address to me, so you might chose to run your own servers. IP addresses are masked in output by default.

For me, at home and also using a phone hotspot indicates APDM for IPv4 and “no NAT” for IPv6:

$ python3 nat-check.py http://b10c.me:7770 http://b10c.me:7771 http://bnoc.xyz:7770 http://bnoc.xyz:7771

nat-check  TCP NAT mapping classifier

── IPv4 ──

local source port: 53321

  destination                            external addr             
  ────────────────────────────────────────────────────────────────
  b10c.me:7770 (x.x.x.1)                 x.x.x.2:61314             
  b10c.me:7771 (x.x.x.1)                 x.x.x.2:59431             
  bnoc.xyz:7770 (x.x.x.3)                x.x.x.2:63172             
  bnoc.xyz:7771 (x.x.x.3)                x.x.x.2:64569             

classification: ADDRESS+PORT-DEPENDENT MAPPING (APDM, 'symmetric')

  External port varies per destination (IP, port). TCP hole punching is very
  unlikely to work for you: the coordinator cannot predict the external port
  your NAT will assign for any given peer. This pattern is typical of CGNAT,
  mobile carriers, and restrictive enterprise networks.

── IPv6 ──

local source port: 53322

  destination                            external addr             
  ────────────────────────────────────────────────────────────────
  b10c.me:7770 (x::x:4)                  x::x:5:53322              
  b10c.me:7771 (x::x:4)                  x::x:5:53322              
  bnoc.xyz:7770 (x::x:6)                 x::x:5:53322              
  bnoc.xyz:7771 (x::x:6)                 x::x:5:53322              

classification: NO NAT

  External address (x::x:5:53322) equals the local address. There is no NAT;
  hole punching is unnecessary. However, your home router may still have a
  stateful firewall that blocks unsolicited inbound connections. You may need to
  open the port on your router to be reachable.

Via Obscura VPN it’s EIM for both IPv4 and IPv6:

python3 nat-check.py http://b10c.me:7770 http://b10c.me:7771 http://bnoc.xyz:7770 http://bnoc.xyz:7771
── IPv4 ──

local source port: 53486

  destination                            external addr             
  ────────────────────────────────────────────────────────────────
  b10c.me:7770 (x.x.x.1)                 x.x.x.2:53486             
  b10c.me:7771 (x.x.x.1)                 x.x.x.2:53486             
  bnoc.xyz:7770 (x.x.x.3)                x.x.x.2:53486             
  bnoc.xyz:7771 (x.x.x.3)                x.x.x.2:53486             

classification: ENDPOINT-INDEPENDENT MAPPING (EIM)

  All four destinations saw the same external port. Your NAT maps (internal IP,
  internal port) to a single external port regardless of destination. TCP hole
  punching has a strong chance of working: a coordinator can reliably tell a
  peer which external port to send to.

── IPv6 ──

local source port: 53487

  destination                            external addr             
  ────────────────────────────────────────────────────────────────
  b10c.me:7770 (x::x:4)                  x::x:5:53487              
  b10c.me:7771 (x::x:4)                  x::x:5:53487              
  bnoc.xyz:7770 (x::x:6)                 x::x:5:53487              
  bnoc.xyz:7771 (x::x:6)                 x::x:5:53487              

classification: ENDPOINT-INDEPENDENT MAPPING (EIM)

  All four destinations saw the same external port. Your NAT maps (internal IP,
  internal port) to a single external port regardless of destination. TCP hole
  punching has a strong chance of working: a coordinator can reliably tell a
  peer which external port to send to.

So IPv4 TCP hole punching would not work at home nor via phone hotspot due to being APDM and IPv6 likely requiring opening the firewall, but would work when using Obscura VPN on both IPv4 and IPv6 as it’s EIM.

I would be interested in seeing results from others.

Who and what IPv4 NAT IPv6 NAT
b10c at home & mobile hotspot APDM no NAT
Obscura VPN EIM EIM
@sipa at home EIM no NAT
@sipa using conference wifi EIM no IPv6
@sipa using hotel wifi EIM no IPv6
@sipa using airport wifi APDM no IPv6
@sipa using plane :airplane: wifi (Viasat) EIM no IPv6
@willcl-ark via starlink (business local priority) EIM no NAT
@dunxen at home EIM no NAT
@cedarctic at university campus APDM -
anon using ProtonVPN (default NAT) APDM -
@m3dwards using office internet on Mac EIM no IPv6
@m3dwards using office internet on Linux EIM no IPv6
@m3dwards using Docker Desktop on Mac APDM no IPv6
@m3dwards using Docker on Linux EIM no IPv6
@m3dwards using T-Mobile US hotspot EIM no IPv6
@m3dwards using Home router (OPNSense) APDM no IPv6
@Crypt-iQ using home internet EIM no NAT
@b10c via eduroam at University APDM -

As mentioned in this comment by sipa, it seems we could hole punch (through the firewall; not through NAT) for no NAT IPv6 too.