dns/ddclient: add a service-specific INWX account class that sets myipv6 (native backend)#5472
Open
jonolt wants to merge 2 commits into
Open
dns/ddclient: add a service-specific INWX account class that sets myipv6 (native backend)#5472jonolt wants to merge 2 commits into
myipv6 (native backend)#5472jonolt wants to merge 2 commits into
Conversation
The native dyndns2 backend always sent the resolved address as "myip", with no IP-family awareness. Providers that treat "myip" as IPv4-only and expect IPv6 in a separate "myipv6" parameter (INWX, Dynu) therefore never got an AAAA record, and behind CGNAT/DS-Lite the A record was set to the carrier's IPv4 via the connection source-IP fallback. Add a small per-service allow-list (_myipv6_services) and send an IPv6 address as "myipv6" for those services; IPv4 and every other service keep "myip" exactly as before, so existing setups are unaffected. This keeps the plugin's one-IP-family-per-account model and does not attempt to send both families in one request (related to opnsense#3233/opnsense#3535, which were declined in favour of registering two accounts). Also add verbose-only syslog lines reporting the chosen parameter (and, on the custom GET/POST/PUT path, the substituted __MYIP__/__HOSTNAME__ token values only, never the assembled URL, which may carry secrets) so users can self-diagnose provider parameter-name quirks. Related to opnsense#5100, opnsense#2872. AI assistance: Claude (Anthropic), Claude Opus 4.8. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Member
|
If a specific service requires different parameters, best just override |
Author
|
Thanks for the feedback. I have some questions before I start implementing.
|
Member
|
I would start easy with a service specific implementation, there are other examples as well in the |
Second round, addressing review feedback on the first commit. The first approach routed IPv6 to myipv6 via a per-service allow-list (_myipv6_services) and a parameter-name branch inside DynDNS2.execute(). The reviewer noted the dyndns2 legacy standard only specifies myip, so DynDNS2 should stay spec-pure and a provider needing different parameters should get its own service-specific account class. This reverts the DynDNS2 changes, removes 'inwx' from DynDNS2._services, and adds a dedicated INWX(BaseAccount) class (modelled on domeneshop.py) that sends an IPv6 address as myipv6 (setting the AAAA record) and IPv4 as myip; an IPv4 request is byte-identical to the previous DynDNS2 output. Scope narrowed to INWX only (dynu and dual-stack deferred). Auto-discovered by AccountFactory, so no GUI/form/template changes are needed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
myipv6 for INWX/Dynu on the native backendmyipv6 (native backend)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Important notices
Before you submit a pull request, we ask you kindly to acknowledge the following:
If AI was used, please disclose:
myipv6=URL — see Testing).Related issue
#5100 (INWX IPv6, but reported against the Perl
ddclientbackend). Also relates to the same CGNAT symptom in #2872 and to native IPv6-only change detection in #3069. This PR does not close any of them — it fixes a distinct, code-level cause on the native backend.(No separate tracking issue was opened first: per CONTRIBUTING.md that step is asked of new plugins; this is a bugfix to an existing plugin, already documented through the issues above — hence the unchecked box.)
Describe the problem
On the native backend the dyndns2 update path always sent the resolved address as
myip=, with no IP-family awareness and nomyipv6=. DynDNS2 has no formal standard and historically defines onlymyip; IPv6 support was added later by providers in two incompatible ways:myipthat accepts either family (e.g. deSEC) — the current code already works here;myipv6=withmyiptreated as IPv4-only (e.g. INWX) — the current code fails here.Against the second group, a detected IPv6 lands in
myip, is ignored for AAAA, and the provider falls back to the connection's source IP. Behind CGNAT/DS-Lite (no usable public IPv4) the result is the worst case: the A record is set to the carrier's shared CGNAT IPv4 (wrong — it routes to the carrier, not the host) and AAAA is never set.This is a client-side interoperability gap, not a provider bug: the plugin's own Perl/
ddclientbackend already handles IPv6 viausev6/myipv6; the native backend could not setmyipv6at all.The hard-coded parameter name is in
dns/ddclient/src/opnsense/scripts/ddclient/lib/account/dyndns2.py(standard path):current_addressis set once inlib/account/__init__.py::BaseAccount.execute()and reused verbatim by both the standard and custom-GET paths.Describe the proposed solution
Following the maintainer guidance on #5312 ("better to add a separate implementation when specific parameters are needed"), this keeps the generic
DynDNS2class spec-pure — it continues to send onlymyip, per the dyndns2 legacy standard — and gives INWX its own small service-specific account class, modelled on the existingdomeneshop.py/duckdns.pyproviders.inwxis removed fromDynDNS2._services(one line);DynDNS2no longer claims it.lib/account/inwx.pydefinesINWX(BaseAccount)with_services = {'inwx': 'dyndns.inwx.com'}. It is auto-discovered bypoller.py::AccountFactory(globsaccount/*.py, collectsBaseAccountsubclasses) — no registration wiring needed.INWX.execute()mirrors the generic/nic/updatestandard path, then sends the address asmyipv6=when it is IPv6 (detected with':' in str(self.current_address)— the same colon-in-string checkduckdns.pyuses) andmyip=otherwise. Everything else (URL build honouringforce_ssl,hostname/system='dyndns'/wildcard, HTTP basic auth,User-Agent) is identical to the generic class, so an IPv4 INWX request is byte-for-byte equivalent to whatDynDNS2emitted before — verified by the regression test below.myipv6myipmyipmyipThe
<inwx>INWX</inwx>GUI dropdown option and the stored service valueinwxare unchanged; only the handling class moves. No model/form/template changes are needed.Scope (deliberately narrow).
myipv6) is not touched in this PR — it stays handled byDynDNS2exactly as onmaster(its IPv6 case remains unfixed and can be a focused follow-up once reproduced).myip/myipv6), keeping the plugin's one-IP-family-per-account model (as with the existingdesec-v4/desec-v6,nsupdatev4/nsupdatev6,he-net/he-net-tunnelsplits). Full dual-stack on INWX is achieved the standard, documented way — two accounts on the same hostname, one per family — provided each uses a separate INWX DynDNS login bound to its own A/AAAA record (confirmed in production). This fix is precisely what makes the IPv6 account work, since it must sendmyipv6. It does not attempt to send both families in one request (os-ddclient - Ability to choose IPv4 or IPv6 or IPv4/IPv6 for each entry #3233/ddclient OPNsense backend: Add new account class for single call URL Update for ipv4 and ipv6 #3535).ddclienttemplate reads it) — a possible separate follow-up, untouched here.The INWX single-login footgun, surfaced via the log. The catch is that INWX scopes record deletion to the DynDNS login: a single login bound to both A and AAAA drops whichever family is omitted from an update (INWX offers no
preservetoken). So dual-stack must use a separate INWX login per family — not two OPNsense accounts sharing one INWX login, which would clobber. To make this self-diagnosable without a GUI/form change,INWX.execute()emits aLOG_NOTICEon each successful update naming the parameter/record it set (myipv6 (AAAA)ormyip (A)) and the correct dual-stack setup. Because updates only fire on an address change, this is not per-poll-cycle noise. A verbose-only line additionally logs the chosen parameter name for parameter-quirk diagnosis.Testing
Static:
python3 -m py_compilepasses for bothinwx.pyand the trimmeddyndns2.py.pycodestyle --max-line-length=125(the repo's style gate) reports no new violations.Dispatch (native factory):
AccountFactory().get({'service':'inwx', ...})now returns anINWXinstance (notDynDNS2);AccountFactory().known_services()still containsinwx(now viaINWX);'inwx' not in DynDNS2._services;dyndns2and the other services still route toDynDNS2.Unit (mocked
requests.get, no network) —test_inwx_class.py, all 15 checks pass:current_address(2001:db8::1) → params carrymyipv6and notmyip;192.0.2.1) → params carrymyipand notmyipv6;hostname/system='dyndns'/wildcard;DynDNS2output for the same settings.Live, against a real INWX account: requests were hand-crafted in two independent passes — once with the bare INWX endpoint (hostname only) and once mirroring the exact OPNsense
dyndns2.pyURL construction (system=dyndns,wildcard=NOCHG). Both passes produced identical results, and each resulting A/AAAA record was read back from the INWX control panel. All rows below were taken against a single INWX login covering both records — which is why the single-family rows drop the other record (with a separate INWX login per family, bound to its own A/AAAA record, the updates do not interfere — see Scope):myip=<IPv4>goodmyipv6=<IPv6>goodmyip=<IPv4>+myipv6=<IPv6>goodmyip=<IPv6>goodThe decisive comparison holds the address constant and changes only the parameter name:
myip=<IPv6>→ AAAA deleted, A polluted with the connection's IPv4 — exactly the reported CGNAT symptom;myipv6=<IPv6>→ AAAA set correctly.So the parameter name, not IP detection, decides whether INWX writes the AAAA record. This fix makes the native path emit that same known-good
myipv6=<address>request automatically for INWX.Note:
dyndns.inwx.comhas no AAAA record, so requests run over IPv4 transport regardless of the address family in the payload — an INWX infrastructure constraint, not a test limitation, and orthogonal to which parameter carries the address.Not yet exercised end-to-end: the new code path has not been run on a live OPNsense native backend — the production confirmation above was via hand-crafted HTTP requests to the real INWX endpoint, not through this code. Because it emits a request byte-for-byte equivalent to the confirmed
myipv6=<address>URL, the behaviour is well-evidenced; a native-backend run by a maintainer or the reporter would fully close the loop.The change adheres to 2-Clause BSD licensing and is based on the latest
master.References
preservetoken (the mechanism INWX lacks)ddclientbackend · #2872 — CGNAT A/AAAA symptom · #3069 — native IPv6-only change detection