[GH-ISSUE #2951] --holepunch=false (relay mode) fails when UDP 21820 is blocked — server still requires recent hole punch for registration #32343

Closed
opened 2026-06-15 09:46:09 -05:00 by GiteaMirror · 4 comments
Owner

Originally created by @z10n-dev on GitHub (May 1, 2026).
Original GitHub issue: https://github.com/fosrl/pangolin/issues/2951

Describe the Bug

When UDP port 21820 is blocked by a firewall (e.g. school or corporate network), the --holepunch=false flag does not work as expected. Even with holepunch disabled on the client, the server still requires a successful hole punch within the last 5 seconds before it will respond to olm/wg/register. This makes it impossible to use relay mode on networks that block UDP 21820.

Actual behavior
Server-side validation in handleOlmRegisterMessage checks if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) and skips the register regardless of whether the client requested relay mode.

Additional context

  • The connection works fine on a hotspot (UDP 21820 unblocked)
  • Port 21820 UDP is hardcoded in Gerbil and not yet configurable via config.yml (clients_start_port does not affect Gerbil's actual listening port)
  • This is a regression from pre-0.8.0 behavior, where relay fallback worked silently when hole punch failed
  • tcpdump on the server confirms hole punch packets from the school network never reach port 21820, but do reach 51820

Environment

  • Pangolin server: 1.18.1
  • Olm client: 0.8.0
  • OS: Arch Linux (kernel 7.0.2-arch1-1)
  • Network: School WiFi — UDP 21820 blocked, UDP 51820 allowed (confirmed via tcpdump)

To Reproduce

  1. Block outbound UDP port 21820 (or be on a network that does, e.g. school/corporate WiFi)
  2. Run: pangolin up --holepunch=false
  3. The websocket connects successfully, but the server keeps responding with:
    Client last hole punch is too old and we have sites to send; skipping this register
  4. The client times out and disconnects — relay mode never establishes

Expected Behavior

When --holepunch=false is passed, the server should accept the registration without requiring a recent hole punch and fall back to relay mode, just as it did in versions prior to 0.8.0.

Originally created by @z10n-dev on GitHub (May 1, 2026). Original GitHub issue: https://github.com/fosrl/pangolin/issues/2951 ### Describe the Bug When UDP port 21820 is blocked by a firewall (e.g. school or corporate network), the `--holepunch=false` flag does not work as expected. Even with holepunch disabled on the client, the server still requires a successful hole punch within the last 5 seconds before it will respond to `olm/wg/register`. This makes it impossible to use relay mode on networks that block UDP 21820. **Actual behavior** Server-side validation in `handleOlmRegisterMessage` checks `if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0)` and skips the register regardless of whether the client requested relay mode. **Additional context** - The connection works fine on a hotspot (UDP 21820 unblocked) - Port 21820 UDP is hardcoded in Gerbil and not yet configurable via config.yml (`clients_start_port` does not affect Gerbil's actual listening port) - This is a regression from pre-0.8.0 behavior, where relay fallback worked silently when hole punch failed - tcpdump on the server confirms hole punch packets from the school network never reach port 21820, but do reach 51820 ### Environment - Pangolin server: 1.18.1 - Olm client: 0.8.0 - OS: Arch Linux (kernel 7.0.2-arch1-1) - Network: School WiFi — UDP 21820 blocked, UDP 51820 allowed (confirmed via tcpdump) ### To Reproduce 1. Block outbound UDP port 21820 (or be on a network that does, e.g. school/corporate WiFi) 2. Run: `pangolin up --holepunch=false` 3. The websocket connects successfully, but the server keeps responding with: `Client last hole punch is too old and we have sites to send; skipping this register` 4. The client times out and disconnects — relay mode never establishes ### Expected Behavior When `--holepunch=false` is passed, the server should accept the registration without requiring a recent hole punch and fall back to relay mode, just as it did in versions prior to 0.8.0.
Author
Owner

@AstralDestiny commented on GitHub (May 1, 2026):

Relay is done via 21820 Out to the pangolin stack then it connects down to site over you going directly.

<!-- gh-comment-id:4358731769 --> @AstralDestiny commented on GitHub (May 1, 2026): Relay is done via 21820 Out to the pangolin stack then it connects down to site over you going directly.
Author
Owner

@z10n-dev commented on GitHub (May 1, 2026):

So there is no other way, than using my Hotspot, when the port 21820 is blocked in school wifi?

<!-- gh-comment-id:4358766476 --> @z10n-dev commented on GitHub (May 1, 2026): So there is no other way, than using my Hotspot, when the port 21820 is blocked in school wifi?
Author
Owner

@oschwartz10612 commented on GitHub (May 1, 2026):

Not right now sorry. Relay over UDP is the only option though I know some work is being done on websocket relaying which we might be able to take.

<!-- gh-comment-id:4360921345 --> @oschwartz10612 commented on GitHub (May 1, 2026): Not right now sorry. Relay over UDP is the only option though I know some work is being done on websocket relaying which we might be able to take.
Author
Owner

@jecabe commented on GitHub (Jun 2, 2026):

I don't think this is only related to the server configuration.

In my case, using exactly the same Pangolin server, the same client and the same user account:

  • Connected through WiFi → registration completes successfully and the client reaches "Connected".
  • Connected through the mobile network (5G) → the client remains indefinitely in "Registering" and Pangolin logs show Client last hole punch is too old.

The interesting part is that the server receives the WebSocket connection correctly, but the UDP hole punch validation never completes.

I also contacted my mobile provider and they confirmed that mobile connections use a carrier-grade NAT-like architecture that cannot be disabled for individual customers.

Since most mobile operators today use CG-NAT or similar carrier NAT technologies, I suspect the current hole punching implementation may have problems with some mobile NAT environments (symmetric NAT, aggressive UDP port remapping, carrier NAT, etc.).

The same server works perfectly over WiFi but consistently fails over the mobile network, which points more towards a NAT traversal issue than a firewall or port-forwarding problem.

Has anyone successfully tested Android clients over mobile networks using CG-NAT?

<!-- gh-comment-id:4602157926 --> @jecabe commented on GitHub (Jun 2, 2026): I don't think this is only related to the server configuration. In my case, using exactly the same Pangolin server, the same client and the same user account: * Connected through WiFi → registration completes successfully and the client reaches "Connected". * Connected through the mobile network (5G) → the client remains indefinitely in "Registering" and Pangolin logs show `Client last hole punch is too old`. The interesting part is that the server receives the WebSocket connection correctly, but the UDP hole punch validation never completes. I also contacted my mobile provider and they confirmed that mobile connections use a carrier-grade NAT-like architecture that cannot be disabled for individual customers. Since most mobile operators today use CG-NAT or similar carrier NAT technologies, I suspect the current hole punching implementation may have problems with some mobile NAT environments (symmetric NAT, aggressive UDP port remapping, carrier NAT, etc.). The same server works perfectly over WiFi but consistently fails over the mobile network, which points more towards a NAT traversal issue than a firewall or port-forwarding problem. Has anyone successfully tested Android clients over mobile networks using CG-NAT?
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/pangolin#32343