Bug #17096
closedDynDNS: update requests egress via the default gateway despite interface source binding
0%
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
MM Updated by Marcos M 6 days ago
- Status changed from New to Rejected
This sounds more like a configuration issue. The default pf rules with route-to should be catching the relevant source address and forcing it out the right interface/gateway. See:
https://docs.netgate.com/pfsense/en/latest/development/bug-reports.html#what-to-avoid
AG Updated by Alexander Gott 6 days ago ยท Edited
Marcos M wrote in #note-1:
This sounds more like a configuration issue. The default pf rules with route-to should be catching the relevant source address and forcing it out the right interface/gateway. See:
https://docs.netgate.com/pfsense/en/latest/development/bug-reports.html#what-to-avoid
I found this error in the log while I was looking for issues that were preventing the IP address from changing in cloudflare. I didn't have this problem on the main gateway, but only on the second gateway. Once I set the route to the Cloudflare API statically via gateway 2, it worked(but then only for the Gateway 2). For that reason, I assumed it must have something to do with the routing. So I thought a dynamic solution would be a good choice here. Did I miss something?
ERROR [phpDynDNS] Curl error occurred: Failed to connect to api.cloudflare.com port 443 after 76245 ms: Could not connect to server