From d4d0119d8ee7e83769b92cc85225dbeb177d1d27 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Mon, 10 Aug 2026 00:29:58 +0200 Subject: feat(node): punch_nat() — NAT traversal from QUIC server socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SFR residential NAT is Port-Restricted Cone: inbound is only allowed from (peer_ip, peer_port) if the node previously sent a packet TO (peer_ip, peer_port) from the SAME socket. punch_nat(peer_ip, peer_port): sends a probe UDP packet from the QUIC server's own transport (_transport.sendto), creating the correct NAT entry. Used after server.start() to enable direct QUIC connections through SFR NAT without UPnP or relay. demo-v2 result: QUIC/UDP direct Fedora→SFR NAT→meshbay.org validated. Connection time 12.7s (QUIC handshake through NAT). File transfer 700B. QuicChunkClient local_port param: ensures client binds to same port as punch_nat destination (Port-Restricted Cone requirement). Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../src/meshbay_node/transport/quic_server.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'packages/meshbay-node') diff --git a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py index 8ab359d..9cb3bd8 100644 --- a/packages/meshbay-node/src/meshbay_node/transport/quic_server.py +++ b/packages/meshbay-node/src/meshbay_node/transport/quic_server.py @@ -273,6 +273,22 @@ class QuicChunkServer: ) log.info("QuicChunkServer listening on %s:%d (QUIC/UDP)", self._host, self._port) + def punch_nat(self, peer_ip: str, peer_port: int) -> None: + """ + Send a probe UDP packet FROM the QUIC server's own socket. + + Critical for Port-Restricted Cone NAT (SFR résidentiel) : + the NAT only allows inbound from (peer_ip, peer_port) if the server + previously sent a packet TO (peer_ip, peer_port) from this same socket. + + The QUIC client must connect FROM peer_port for the NAT entry to match. + """ + if self._server and hasattr(self._server, '_transport') and self._server._transport: + self._server._transport.sendto(b'MESHBAY:NAT:PUNCH', (peer_ip, peer_port)) + log.info("NAT probe sent → %s:%d", peer_ip, peer_port) + else: + log.warning("punch_nat: server transport not available") + async def stop(self) -> None: if self._server: self._server.close() -- cgit v1.2.3