Project

General

Profile

Actions

Bug #17096

closed
AG

DynDNS: update requests egress via the default gateway despite interface source binding

Bug #17096: DynDNS: update requests egress via the default gateway despite interface source binding

Added by Alexander Gott 6 days ago. Updated 6 days ago.

Status:
Rejected
Priority:
Normal
Assignee:
-
Category:
Dynamic DNS
Target version:
-
Start date:
Due date:
% Done:

0%

Estimated time:
Plus Target Version:
Release Notes:
Default
Affected Version:
2.9.x
Affected Architecture:
All

Description

Environment:
- pfSense 2.9.0-RELEASE (amd64)
- Two DHCP WANs: WAN (default gateway) and WAN2 (own gateway, not part of any gateway group)
- Dynamic DNS client (Cloudflare) bound to interface WAN2

Problem:
The DynDNS update times out even though WAN2 itself is fully functional:

[phpDynDNS] Curl error occurred: Failed to connect to api.cloudflare.com port 443 after 76439 ms: Could not connect to server

(~75 s = FreeBSD TCP connect timeout: SYN sent, no reply -> packets silently dropped, not rejected)

Root cause:
services_dyndns binds update requests to the source IP of the monitored interface (CURLOPT_INTERFACE in dyndns.class), but locally generated traffic still follows the system routing table. The request therefore egresses via the default gateway (WAN) while carrying a source address of WAN2. Upstreams performing source address filtering (BCP38/uRPF, common on access lines) silently drop such packets.

Evidence:
1) route -n get api.cloudflare.com
-> gateway: <WAN gateway>, interface: <WAN interface> (default route)
2) curl -4 --interface <WAN2 address> https://api.cloudflare.com/client/v4
-> times out on all resolved addresses
3) route add -host <api.cloudflare.com address> <WAN2 gateway>, then same curl:
-> connects immediately, clean TLS handshake, valid HTTP response from the API

This proves the source binding is correct; only the egress path is wrong.

Relation to existing tickets:
#14605 addressed the Check IP service following the default gateway (gateway groups behind NAT). The update request itself still follows the default route on 2.9.0 even when a plain (non-group) interface is selected. Related: #14610.

Proposed fix (patch attached, pull request follows):
Wrap curl_exec() in dyndns.class: resolve the request target, install temporary host routes via the gateway of the monitored interface (get_interface_gateway()/get_interface_gateway_v6(), dynamic gateways taken from the <if>_router files, gateway groups via the currently active member), pin the handle to the routed addresses using CURLOPT_RESOLVE, and remove the routes created by the patch once the request is done. Private/reserved destinations and the 'ods' service are excluded.

Tested on 2.9.0 with the setup above (default WAN upstream filters foreign source addresses): updates on WAN2 succeed, and no residual host routes remain after the update (verified with route -n get).


Files

dyndns-upstream.patch (13.1 KB) dyndns-upstream.patch Alexander Gott, 09/12/2026 01:02 PM
Actions

Also available in: Atom