Skip to content

Pangolin/android connection failing #3478

Description

@yann117

Describe the Bug

Running Pangolin on my own server at home and my own domain, having the whole setup working properly:

  • IPv6 only server
  • hosting my services on the server with the same Docker-
  • using Oracle VPS to perform the IPv4 to IPv6 translation

I can properly access my resources from every situation. For better security I'd like to use now Private resources and Pangolin/VPN on my mobile.

Since private resources can only be defined on non-local node, I'm running newt (commandline) on the server as loopback to itself.

When no/zero private resources are defined, my Pangolin/mobile can connect and register properly.
As soon as I add a private resources, Pangolin/mobile client connect but is not able to register anymore.

Environment

  • OS Type & Version: linux + Docker
  • Pangolin Version: v1.21.0 (was also occurring in many previous versions)
  • Edition (Community or Enterprise): Enterprise (single/own use)
  • Gerbil Version: v1.4.1 (from sha256:7aa94231188fb9a738c9e1ab9cbf89001356da72453e57da9722d3e0fb37d196)
  • Traefik Version: 3.6.23
  • Newt Version: 1.15.0
  • Client Version: 0.4.0

To Reproduce

When no private resources are defined, my Pangolin/mobile client can connect and register properly:

2026-07-22T19:21:19+00:00 [info]: Establishing websocket connection
2026-07-22T19:21:19+00:00 [info]: Client added to tracking - OLM ID: dfrg4iatyetu4fh, Connection ID: 480cd7cb-aea8-447a-8e7a-9dfb09c3a2bf, Total connections: 1, Config version: 0
2026-07-22T19:21:19+00:00 [info]: WebSocket connection fully established and ready - OLM ID: dfrg4iatyetu4fh
2026-07-22T19:21:20+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:21:20+00:00 [info]: [handleOlmRegisterMessage] Public key mismatch. Updating public key and clearing session info... {"orgId":"home","clientId":4}

Once I setup one private resource BUT keep the prior Pangolin/mobile client connected (step above), then I can properly access the newly defined private resource, here http://192.168.1.7:2283:

2026-07-22T19:24:06+00:00 [info]: Created site resource 8 for org home
2026-07-22T19:24:06+00:00 [info]: Certificate already exists for domain pictures.private.mydomain.com
2026-07-22T19:24:06+00:00 [info]: Initiated 1 peer add handshake(s) to olms (batch)
INFO: 2026/07/22 21:24:06 Added target subnet from 100.90.128.0/32 to 100.96.128.8/32 rewrite to  with port ranges: [{443 443 tcp} {80 80 tcp} {0 0 udp}]
INFO: 2026/07/22 21:24:06 Added target subnet from 100.90.128.1/32 to 100.96.128.8/32 rewrite to  with port ranges: [{443 443 tcp} {80 80 tcp} {0 0 udp}]
INFO: 2026/07/22 21:24:06 Added target subnet from 100.90.128.3/32 to 100.96.128.8/32 rewrite to  with port ranges: [{443 443 tcp} {80 80 tcp} {0 0 udp}]
2026-07-22T19:24:07+00:00 [info]: ClientSitesAssociationsCache for client 4 endpoint changed to [2a02:8440:8503:2ae9:38c3:f1ff:feb0:acb2]:55253 for 1 site(s) on exit node 1
INFO: 2026/07/22 21:24:07 Triggering on-demand hole punch to 1 exit nodes
2026-07-22T19:24:07+00:00 [info]: Updated peer +KKour53wh...fyOKSCGc= on newt 7af1cumf044874j
INFO: 2026/07/22 21:24:07 Successfully sent on-demand hole punch to 1/1 exit nodes
INFO: 2026/07/22 21:24:07 Updating Endpoint for peer +KKour53wh...fyOKSCGc= to [2a02:8440:8503:2ae9:38c3:f1ff:feb0:acb2]:55253
INFO: 2026/07/22 21:24:07 Peer +KKour53wh...fyOKSCGc= updated successfully
2026-07-22T19:24:07+00:00 [info]: Handling register olm message!
INFO: 2026/07/22 21:24:07 Triggering on-demand hole punch to 1 exit nodes
2026-07-22T19:24:07+00:00 [info]: Added peer +KKour53wh...fyOKSCGc= to newt 7af1cumf044874j
INFO: 2026/07/22 21:24:07 Successfully sent on-demand hole punch to 1/1 exit nodes
INFO: 2026/07/22 21:24:07 Peer +KKour53wh...fyOKSCGc= added successfully
2026-07-22T19:24:09+00:00 [info]: Handling local olm message!
INFO: 2026/07/22 21:24:10 Triggering on-demand hole punch to 1 exit nodes
2026-07-22T19:24:10+00:00 [info]: Updated peer +KKour53wh...fyOKSCGc= on newt 7af1cumf044874j
INFO: 2026/07/22 21:24:10 Successfully sent on-demand hole punch to 1/1 exit nodes
INFO: 2026/07/22 21:24:10 Removing Endpoint for peer +KKour53wh...fyOKSCGc=
INFO: 2026/07/22 21:24:10 Peer +KKour53wh...fyOKSCGc= updated successfully
2026-07-22T19:24:10+00:00 [info]: Handling local olm message!
INFO: 2026/07/22 21:24:10 Triggering on-demand hole punch to 1 exit nodes
2026-07-22T19:24:10+00:00 [info]: Updated peer +KKour53wh...fyOKSCGc= on newt 7af1cumf044874j
INFO: 2026/07/22 21:24:10 Successfully sent on-demand hole punch to 1/1 exit nodes
INFO: 2026/07/22 21:24:10 Removing Endpoint for peer +KKour53wh...fyOKSCGc=
INFO: 2026/07/22 21:24:10 Peer +KKour53wh...fyOKSCGc= updated successfully
2026-07-22T19:24:11+00:00 [info]: Updated site resource 8
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50120 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 HTTP handler: GET / -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:14 HTTP handler: GET /_app/immutable/assets/GoogleSans.DGRbB7N7.ttf -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50122 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50138 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50162 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50136 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 TCP Forwarder: Routing 100.90.128.1:50154 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:14 HTTP handler: GET /_app/immutable/assets/GoogleSansCode.a41q3NA0.ttf -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:14 HTTP handler: GET /_app/immutable/entry/start.DgZvZQ22.js -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:14 HTTP handler: GET /_app/immutable/chunks/Leq4p3Ca.js -> http://192.168.1.7:2283
...
INFO: 2026/07/22 21:26:15 HTTP handler: GET /_app/immutable/chunks/NmZS8EsT.js -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:15 HTTP handler: GET /_app/immutable/chunks/DoEPtPFQ2.js -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:15 TCP Forwarder: Routing 100.90.128.1:50176 -> 100.96.128.8:443 to HTTP handler (https)
INFO: 2026/07/22 21:26:15 HTTP handler: GET /apple-icon-180.png -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:15 HTTP handler: GET /api/server/config -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:15 HTTP handler: GET /service-worker.js -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:15 HTTP handler: GET /api/server/features -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:16 HTTP handler: GET /api/server/media-types -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:16 HTTP handler: POST /api/oauth/authorize -> http://192.168.1.7:2283
INFO: 2026/07/22 21:26:34 HTTP request logger: sent 81 requests to server

If now, I disconnect my Pangolin/mobile, any new attempt to connect/register is failing with:

2026-07-22T19:28:07+00:00 [info]: All connections removed for OLM ID: dfrg4iatyetu4fh
2026-07-22T19:28:07+00:00 [info]: Client disconnected - OLM ID: dfrg4iatyetu4fh
2026-07-22T19:28:44+00:00 [info]: Establishing websocket connection
2026-07-22T19:28:44+00:00 [info]: Client added to tracking - OLM ID: dfrg4iatyetu4fh, Connection ID: fa5a92cf-11c9-4745-b2c7-bc8b69f728c0, Total connections: 1, Config version: 0
2026-07-22T19:28:44+00:00 [info]: WebSocket connection fully established and ready - OLM ID: dfrg4iatyetu4fh
2026-07-22T19:28:45+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:28:45+00:00 [info]: [handleOlmRegisterMessage] Public key mismatch. Updating public key and clearing session info... {"orgId":"home","clientId":4}
2026-07-22T19:28:45+00:00 [warn]: [handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port 21820? {"orgId":"home","clientId":4}
2026-07-22T19:28:47+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:28:47+00:00 [warn]: [handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port 21820? {"orgId":"home","clientId":4}
2026-07-22T19:28:49+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:28:49+00:00 [warn]: [handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port 21820? {"orgId":"home","clientId":4}
2026-07-22T19:28:51+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:28:51+00:00 [warn]: [handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port 21820? {"orgId":"home","clientId":4}
2026-07-22T19:28:53+00:00 [info]: [handleOlmRegisterMessage] Handling register olm message
2026-07-22T19:28:53+00:00 [warn]: [handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port 21820? {"orgId":"home","clientId":4}

Once I remove the single private resource (zero remaining), then I can connect/register Pangolin/mobile again.
Below are the logs from the Pangolin/mobile app:

2026-07-24 11:15:50.176 [INFO] PangolinGo: Starting hole punch for 1 exit nodes
2026-07-24 11:15:50.176 [DEBUG] PangolinGo: Starting UDP hole punch to 1 exit nodes with shared bind
2026-07-24 11:15:50.176 [DEBUG] PangolinGo: Resolved exit node: [<IPv6_redacted>] -> [<IPv6_redacted>]:21820
2026-07-24 11:15:50.177 [WARN] PangolinGo: Failed to send initial hole punch to [<IPv6_redacted>]: failed to write to UDP: write udp [::]:56198->[<IPv6_redacted>]:21820: sendto: no route to host

It is properly picking up the IPv6 address as expected, but fails with a no route to host.

The ports (21820/51820) are open and listening and the same address:port can be reached from the same mobile using a different application, here termux, as I could confirm gerbil is receiving the messages I sent directly on the socket (socat).

As per the no route to host, the issue doesn't seem to be with the ports, but rather at another level (see below further details #3478 (comment)).

AI suggests the following hypothesis:

Why Pangolin Fails (The Go Socket Lifecycle Issue)

  1. Android VPN Service Socket Protection:
    If Pangolin is running inside an Android app context (or VPN Service), sockets created by Go must be protected via Android's VpnService.protect(socket) so they bypass the VPN tunnel loop. If a shared socket is bound without proper socket protection or interface binding, Android's netd daemon drops the outbound packet with EHOSTUNREACH (no route to host).

  2. Binding to 0.0.0.0 / IPv4-mapped IPv6:
    If Pangolin initializes its "shared bind" using net.ListenPacket("udp", ":0") on a system where Go's dual-stack socket behavior is restricted by Android/Linux kernel policies (IPV6_V6ONLY), attempting to write to a pure IPv6 address through that shared socket fails.

  3. Stale Interface / Socket Binding:
    PangolinGo is using a shared bound socket (reusing an existing net.PacketConn or net.UDPConn instance). If that socket was created when the app started, or before your mobile device established/switched its IPv6 route, the underlying file descriptor is bound to an old routing context. In Go, calling WriteToUDP() on a stale [::] socket throws sendto: no route to host even if new sockets (like nc) work fine.

Expected Behavior

Pangolin/mobile client should be able to connect when private resources are configured.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions