Skip to content

Conversation

@p4bpj
Copy link

@p4bpj p4bpj commented Aug 22, 2025

Discussion Invitation

This PR proposes adding a DynDNS-based external IP tracking fallback to complement (or replace when unavailable) UPnP-based discovery. It's aimed at improving inbound reachability for home users who disable UPnP but rely on static/manual port forwarding plus a dynamic DNS hostname. Feedback on ergonomics, defaults, naming, and refresh strategy is welcome before finalizing. Also checkout PR #2

Summary

Introduces an optional DynDNS resolver service that periodically resolves a user-specified hostname and updates the node’s advertised external address (best_local_address) when the underlying WAN IP changes. If UPnP successfully yields a public external IP, DynDNS is skipped. If UPnP is disabled or produces no routable address, the DynDNS path performs:

  1. A minimal synchronous one-shot resolve at startup (non-blocking except for a single DNS lookup) to seed the initial external IP before P2P connects.
  2. A background DynDnsExtender task that refreshes on an adaptive interval (min→exponential backoff up to max) and notifies registered sinks upon change.

The result: nodes behind manually forwarded routers (with UPnP off) can automatically keep their advertised address current without restarts when their ISP-assigned IP updates.

Motivation

Problem: Many home operators disable UPnP but still forward a port manually and operate behind a dynamic WAN IP served via an ISP or consumer router. Previously:

  • Without specifying --externalip, the node could fail to advertise a valid routable address if interface auto-discovery yielded none.
  • Even if initially correct (e.g. via manual --externalip), future WAN IP rotations (DHCP/PPPoE lease renewal) required manual restarts to propagate the new address.
  • Users who already maintain a DynDNS hostname (common with manual port forwarding) gain no automatic benefit unless they hard-code resolved IPs.

Goal: Seamless external IP lifecycle management when UPnP is unavailable, leveraging an operator’s existing DynDNS hostname.

Key Changes

  • AddressManager
    • Extended constructor now returns both optional UPnP port-mapping extender and optional DynDnsExtender.
    • Adds minimal initial DynDNS resolve (try_initial_dyndns_resolve) if UPnP not active and a hostname is configured; prefers IPv4 in Auto mode.
    • Provides set_best_local_address already used by both UPnP and DynDNS flows.
    • Maintains a list of ExternalIpChangeSink subscribers (unchanged pattern) reused for DynDNS notifications.
  • DynDnsExtender (new file components/addressmanager/src/dyndns_extender.rs)
    • Periodically resolves the configured hostname with adaptive interval (reset to min on success; exponential backoff up to max on failures / no public result).
    • Filters to publicly routable IPs; selects according to IpVersionMode (Ipv4|Ipv6|Auto with IPv4 preference).
    • Detects changes and updates AddressManager + asynchronously notifies sinks (on_external_ip_changed(new, old)).
  • Config / CLI
    • New arguments:
      • --external-dyndns-host=<HOSTNAME>
      • --external-dyndns-min-refresh-sec=<N> (default 30)
      • --external-dyndns-max-refresh-sec=<N> (default 300)
      • --external-dyndns-ip-version=<auto|ipv4|ipv6> (default auto; auto prefers IPv4 if present)
    • Existing --disable-upnp and --disable-ipv6-interface-discovery continue to apply.
    • Config struct extended with DynDNS fields and IpVersionMode.
  • Config Builder
    • Injects defaults (host = None; refresh bounds; mode = Auto).
  • Daemon
    • Integrates new triple return from AddressManager::new, wiring (if present) the dyn DNS extender service similarly to the UPnP extender (service start handled elsewhere in lifecycle).
  • Cleanup / Refactor
    • Removed unused outbound reconnect helper remnants (pre-existing logic now internally managed).
    • Minor logging simplifications and removal of stale debug/imports in subsequent refactor commit.

Behavior

Decision tree:

  1. Startup:
    • Attempt local routable interface discovery (respecting --disable-ipv6-interface-discovery).
    • If none and UPnP enabled: attempt UPnP external IP + port mapping.
    • If UPnP fails (or disabled) AND a DynDNS host is configured: perform a one-shot resolve and set initial external IP if found.
  2. Background:
    • If UPnP succeeded: UPnP extender handles periodic external IP refresh (pre-existing behavior); DynDNS skipped.
    • Else if DynDNS configured: DynDnsExtender resolves host on a loop (min interval → backoff).
  3. On IP change (DynDNS or UPnP path):
    • best_local_address updated (retains port from existing best or default P2P port).
    • All registered ExternalIpChangeSinks notified asynchronously (fire-and-forget).

Configuration

Examples:

# Typical manual port forward + DynDNS hostname
kaspad --disable-upnp \
       --external-dyndns-host=mynode.exampleddns.org \
       --external-dyndns-min-refresh-sec=30 \
       --external-dyndns-max-refresh-sec=600 \
       --external-dyndns-ip-version=auto

IPv6-only preference:

--external-dyndns-host=myv6node.example \
--external-dyndns-ip-version=ipv6

Force IPv4 only:

--external-dyndns-ip-version=ipv4

IP Selection Logic

Given resolved set R (filtered to publicly routable):

  • Mode ipv4: first IPv4 in R.
  • Mode ipv6: first IPv6 in R.
  • Mode auto: first IPv4 if any else first element (possibly IPv6).
    No persistence of multiple candidates—single best address advertised.

Testing / Verification Suggestions

Manual:

  1. UPnP enabled & working + DynDNS configured:
    • Expect UPnP success, DynDNS skip log: [AddrMan] UPnP extender active; DynDNS skipped.
  2. UPnP disabled, DynDNS host valid:
    • On startup see initial resolve log and Initial DynDNS external IP set....
    • Modify DNS A record to new IP; after min-refresh interval expect change log + sink callbacks.
  3. Failure then success:
    • Point hostname to unroutable/private IP (or remove A record): observe warnings + interval growth (doubling until max).
    • Restore public A record: observe resolution success and interval reset to min.
  4. --external-dyndns-ip-version=ipv6 with dual-stack host:
    • Ensure selected IP is IPv6 even if IPv4 present.
  5. --disable-ipv6-interface-discovery with only IPv6 routable interface + DynDNS providing IPv4:
    • Should still use DynDNS IPv4 (flag only affects interface scanning, not DynDNS selection).

Limitations / Future Work

  • Resolver currently uses ToSocketAddrs (blocking DNS semantics) for the initial single resolve; background loop also synchronous per cycle—could offload to dedicated async DNS (e.g. trust-dns) if needed.
  • Does not (yet) attempt multi-address advertisement or simultaneous IPv4+IPv6 publication.

Backward Compatibility

  • If both UPnP and DynDNS host configured, UPnP remains first-class and unchanged unless it fails.

Operational Guidance

  • Operators already using --externalip can continue; DynDNS is optional and may reduce manual maintenance.
  • Recommended for users with:
    • Disabled UPnP
    • Reliable manual port forwarding
    • Existing dynamic DNS hostname

Authorship Note

This description was drafted with AI assistance and reviewed by the author (p4bpj) to ensure accuracy of the represented changes.

p4bpj added 9 commits August 19, 2025 18:46
…utbound peers on change

Previously the external IP (via UPnP) was fetched and mapped only once at node startup; dynamic WAN IP changes (e.g. ISP 24h leases, router reconnects) were not propagated, leaving peers with a stale address until manual restart.

This change:

Adds periodic external IP refresh in the UPnP port mapping extender (re-uses the existing renew cycle to also call get_external_ip).
Detects IP changes and updates the AddressManager best local address.
Introduces ExternalIpChangeSink; ConnectionManager implements it to trigger a staggered outbound reconnect so new peers learn the updated public address sooner.
Adds set_best_local_address helper.
Adds CLI/config flag --disable-ipv6-interface-discovery to skip automatic IPv6 interface scanning while still allowing explicit IPv6 config.
Registers ConnectionManager as an external IP change sink.
…ef resolve

UPnP external IP refresh remains first attempt; if it fails, construct a DynDNS extender
Perform a minimal synchronous DynDNS resolve in AddressManager (outside extender) to set initial external IP before P2P starts
Make DynDnsExtender constructor non-blocking; periodic worker handles future changes and sink notifications
Prefer IPv4 in Auto mode (otherwise honor configured IpVersionMode)
- Rename init_local_addresses_with_arc -> init_local_addresses (Arc param made name redundant).
- Remove unused ConnectionManager methods trigger_outbound_reconnect_simple and reconnect_outbound_gradually (logic now handled internally elsewhere).
- Clean up stale UPnP/DynDNS setup comment in daemon.
- Minor formatting tidy in UPnP gateway search call.
…update AddressManager initialization logic
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.

1 participant