Skip to content

Implements STUN support feature requested in issue #71 by @willglynn#72

Merged
sqe merged 11 commits intomainfrom
feature-add-stun-support
Oct 8, 2025
Merged

Implements STUN support feature requested in issue #71 by @willglynn#72
sqe merged 11 commits intomainfrom
feature-add-stun-support

Conversation

@sqe
Copy link
Owner

@sqe sqe commented Oct 8, 2025

Milestone: STUN Support Added

The integration of STUN protocol in ESDDNS was made possible by a community feature request, marking a major milestone: it is the first time STUN-based public IP detection is available in this project.

STUN support was requested in issue #71 by willglynn, opened yesterday. This enhancement fulfills the need for a robust, modern method for NAT traversal and public IPv4 discovery—an essential capability for reliable dynamic DNS updates.

Session Traversal Utilities for NAT (STUN) is a standardized protocol for discovering the public IP address and port assigned to a device through NAT. Historically used in VoIP (e.g., SIP), STUN is now common in WebRTC, so many large tech providers operate public STUN infrastructure.

Recommended public STUN servers:

  • stun.l.google.com:19302 (Google)
  • stun.cloudflare.com:3478 (Cloudflare)
  • global.stun.twilio.com:3478 (Twilio)
  • See this public STUN server list for additional choices.

This milestone reflects ESDDNS's commitment to open development, modern standards, and responsiveness to user feedback. Special thanks to willglynn and all contributors for driving this important feature forward.

STUN Protocol Integration

ESDDNS now supports STUN (Session Traversal Utilities for NAT) protocol as a first-priority WAN IP discovery method, with automatic fallback to HTTP services.

Features

  • RFC 8489 compliant STUN implementation
  • Async/concurrent queries using asyncio
  • UDP and TCP query support with parallel execution
  • Retry logic with exponential backoff (matching HTTP behavior)
  • Three configuration modes: STUN-only, HTTP-only, or both
  • Faster discovery - STUN typically ~2x faster than HTTP
  • NAT-aware - Designed specifically for NAT traversal
  • Zero breaking changes - Fully backward compatible

Configuration

STUN + HTTP (Recommended)

Add [STUNConfig] section to dns.ini:

[STUNConfig]
udp_host_list_url = [https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts.txt](https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts.txt)
tcp_host_list_url = [https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts_tcp.txt](https://raw.githubusercontent.com/pradt2/always-online-stun/master/valid_hosts_tcp.txt)
bind_request = 1
magic_cookie = 554869826
xor_mapped_address = 32
udp_limit = 10
tcp_limit = 10
retry_attempts = 3
retry_cooldown_seconds = 5

Configuration Modes

[WANIPState] [STUNConfig] Behavior
✅ Present ✅ Present STUN first, HTTP verification
❌ Missing ✅ Present STUN only
✅ Present ❌ Missing HTTP only (original)

Files Added

  • api/async_stun_discovery.py - Core STUN protocol implementation
  • api/get_ip_stun.py - STUN provider wrapper

Expected Output

2025-10-08 02:21:01,371 INFO HTTP-based IP services enabled
2025-10-08 02:21:01,371 INFO STUN protocol enabled for IP discovery
2025-10-08 02:21:01,372 INFO [stun] Fetching IP via STUN protocol...
2025-10-08 02:21:01,566 INFO Loaded 85 hosts from [https://raw.githubusercontent.com/.../valid_hosts.txt](https://raw.githubusercontent.com/.../valid_hosts.txt)
2025-10-08 02:21:01,726 INFO Loaded 85 hosts from [https://raw.githubusercontent.com/.../valid_hosts_tcp.txt](https://raw.githubusercontent.com/.../valid_hosts_tcp.txt)
2025-10-08 02:21:01,727 DEBUG Starting new STUN UDP query: stun.stochastix.de:3478
2025-10-08 02:21:01,727 DEBUG Starting new STUN UDP query: stun.healthtap.com:3478
2025-10-08 02:21:01,728 DEBUG Starting new STUN TCP query: stun.hot-chilli.net:3478
2025-10-08 02:21:01,728 DEBUG Starting new STUN TCP query: stun.3wayint.com:3478
2025-10-08 02:21:01,850 INFO [UDP] stun.healthtap.com:3478 → Public IP: 174.247.177.50, Port: 1570
2025-10-08 02:21:01,851 INFO [UDP] stun.frozenmountain.com:3478 → Public IP: 174.247.177.50, Port: 1595
2025-10-08 02:21:01,935 INFO [UDP] stun.stochastix.de:3478 → Public IP: 174.247.177.50, Port: 1592
2025-10-08 02:21:02,153 INFO [TCP] stun.thinkrosystem.com:3478 → Public IP: 174.247.177.50, Port: 3558
2025-10-08 02:21:02,196 WARNING [TCP] stun.verbo.be:3478 → XOR-MAPPED-ADDRESS not found in response.
2025-10-08 02:21:03,741 INFO [stun] Successfully obtained IP: 174.247.177.50 from stun:stun.stochastix.de:3478:udp
2025-10-08 02:21:03,744 INFO SUCCESS: STUN returned 174.247.177.50 from stun:stun.stochastix.de:3478:udp
2025-10-08 02:21:03,755 DEBUG Starting new HTTPS connection (1): checkip.amazonaws.com:443
2025-10-08 02:21:03,755 DEBUG Starting new HTTPS connection (1): ifconfig.me:443
2025-10-08 02:21:03,756 DEBUG Starting new HTTPS connection (1): api.ipify.org:443
2025-10-08 02:21:03,972 INFO "SUCCESS: [https://api.ipify.org/?format=text](https://api.ipify.org/?format=text) Returned: 174.247.177.50 as your WAN IPv4"
2025-10-08 02:21:04,191 INFO "SUCCESS: [https://checkip.amazonaws.com/](https://checkip.amazonaws.com/) Returned: 174.247.177.50 as your WAN IPv4"
2025-10-08 02:21:04,199 INFO "SUCCESS: IPv4 addresses from external services match! {'wan_ip_state': {'usable': True, 'IP': '174.247.177.50'}}"

Note: Failed STUN servers (like stun.verbo.be with XOR-MAPPED-ADDRESS not found) return None and are skipped - they don't affect validation.

IP Validation

All IPs (from STUN and HTTP) are validated together in wan_ip_state():

Scenario 1: All Match (1 STUN + 2 HTTP)

ipaddress_list = ["174.247.177.50", "174.247.177.50", "174.247.177.50"]
→ Log: "SUCCESS: IPv4 addresses from external services match!"

Scenario 2: Single Source (STUN only)

ipaddress_list = ["174.247.177.50"]
→ Log: "SUCCESS: At least one IPv4 was collected!"

Scenario 3: Mismatch (Different IPs)

ipaddress_list = ["174.247.177.50", "73.96.163.207"]
→ Log: "CRITICAL: Mismatch in IPv4 addresses" [EXITS]

Failed queries returning None are filtered out and don't affect validation.

Performance

Scenario HTTP Only STUN Only STUN + HTTP
Success ~2s ~1.5s ~3s
With retry ~7s ~6.5s ~8s

Troubleshooting

No STUN output?

  • Check for: INFO STUN protocol enabled for IP discovery
  • If missing: Verify [STUNConfig] section exists in dns.ini

STUN fails?

  • Check firewall allows UDP/TCP port 3478
  • View retry logs: WARNING [stun] RETRY: Attempt #2...
  • System falls back to HTTP automatically

@sqe sqe linked an issue Oct 8, 2025 that may be closed by this pull request
@sqe sqe mentioned this pull request Oct 8, 2025
@sqe
Copy link
Owner Author

sqe commented Oct 8, 2025

All tests and checks have passed, merging to main

@sqe sqe merged commit 5d133c2 into main Oct 8, 2025
10 checks passed
Comment on lines +170 to +177
if attr_type == 0x0020 and attr_len >= 8:
family = attr[1]
if family == 0x01: # IPv4 only
x_port = struct.unpack('!H', attr[2:4])[0] ^ (self.stun_conf.MAGIC_COOKIE >> 16)
x_ip = struct.unpack('!I', attr[4:8])[0] ^ self.stun_conf.MAGIC_COOKIE
ip_address = socket.inet_ntoa(struct.pack('!I', x_ip))
return ip_address, x_port
offset += 4 + attr_len

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel sad looking at XOR-MAPPED-ADDRESS, but then I feel sadder thinking about everyone who had to deal with troubleshooting MAPPED-ADDRESS in the first place:

   Note: XOR-MAPPED-ADDRESS and MAPPED-ADDRESS differ only in their
   encoding of the transport address.  The former encodes the transport
   address by XOR'ing it with the magic cookie.  The latter encodes it
   directly in binary.  RFC 3489 originally specified only MAPPED-
   ADDRESS.  However, deployment experience found that some NATs rewrite
   the 32-bit binary payloads containing the NAT's public IP address,
   such as STUN's MAPPED-ADDRESS attribute, in the well-meaning but
   misguided attempt to provide a generic Application Layer Gateway
   (ALG) function.  Such behavior interferes with the operation of STUN
   and also causes failure of STUN's message-integrity checking.

…which in turn reminds me of some Adam Langley blog posts:

   Despite it being nearly twelve years since the publication of TLS 1.0
   [RFC2246], around 3% of HTTPS servers will reject a valid TLS
   "ClientHello".  These rejections can take the form of immediately
   closing the connection or a fatal alert.  Intolerance to the
   following has been observed:

      Advertising version TLS 1.0.

      Advertising a TLS version greater than TLS 1.0 (around 2% for 1.1
      or 1.2, around 3% for greater than 1.2).

      Advertising a version greater than 0x03ff (around 65% of servers)

      The presence of any extensions (around 7% of servers)

      The presence of specific extensions ("server_name" and
      "status_request" intolerance has been observed, although in very
      low numbers).

      The presence of any advertised compression algorithms

Sadly, it's precisely this sort of proxy misbehaviour that has delayed TLS 1.3 for over a year while my colleagues (David Benjamin and Steven Valdez) repeatedly deployed experiments and measured success rates of different serialisations. In the end we found that making TLS 1.3 look like a TLS 1.2 resumption solved a huge number of problems, suggesting that many proxies blindly pass through such connections. (Which should, again, make one wonder about what security properties they're providing.)

But, given all that, you might ponder why we bothered encrypting certificates? Partly it's one component of an effort to make browsing more private but, more concretely, it's because anything not encrypted suffers these problems. TLS 1.3 was difficult to deploy because TLS's handshake is, perforce, exposed to the network. The idea that we should make TLS a little more efficient by compressing certificates has been bouncing around for many years. But it's only with TLS 1.3 that we might make it happen because everyone expected to hit another swamp of proxy issues if we tried it without encrypting certificates first.

Anyway, good work! 👍

Copy link
Owner Author

@sqe sqe Oct 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for sharing such insightful info!
It really hits home given all the similar challenges we run into with protocol quirks and real-world networking!

One of the reasons for sticking with IPv4, too, was the unfortunate state of IPv6 adoption as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Add STUN support

2 participants