Skip to content

feat(infoblox): create PTR records for AAAA records - #70

Open
MaxRink wants to merge 3 commits into
AbsaOSS:mainfrom
MaxRink:feat/ipv6-ptr-support
Open

MaxRink wants to merge 3 commits into
AbsaOSS:mainfrom
MaxRink:feat/ipv6-ptr-support

Conversation

@MaxRink

@MaxRink MaxRink commented Aug 6, 2026

Copy link
Copy Markdown

Follow-up to #69, which added AAAA records but deliberately restricted CreatePTR to A records. That restriction turned out to be more conservative than necessary — most of the reverse-zone machinery is already family-agnostic:

  • rfc2317.CidrToInAddr already derives ip6.arpa nibble names for IPv6 CIDRs (verified: 2001:db8::/64 → 0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa, 2001::/16 → 1.0.0.2.ip6.arpa), so Records() needed no change to fetch PTRs from an IPv6 reverse zone.
  • findReverseZone uses net.ParseCIDR + net.IPNet.Contains, both already IPv6-correct.

So no new nibble helper was written — it would have been dead code.

What actually changed

  • ChangesByZone derives the companion PTR change for AAAA endpoints as well as A; AdjustEndpoints/Records() track infoblox-ptr-record-exists for AAAA too.
  • recordSet() addresses a PTR through ipv6addr rather than ipv4addr when the target is IPv6, both when building the object and when looking up the existing one. record:ptr carries its target in exactly one of the two fields (RecordPTR has both Ipv4Addr and Ipv6Addr; NewEmptyRecordPTR's returnFields lists both).
  • ToPTRResponseMap() falls back to Ipv6Addr when Ipv4Addr is empty, so PTRs read back from an ip6.arpa zone yield a target instead of an empty string.

CreatePTR still gates all of this — no behaviour change for users who haven't opted in. As before, the reverse zone must be in DOMAIN_FILTER; the README now documents the IPv6 CIDR form.

Testing

Nibble-name derivation (table test: compressed, expanded, ULA and host forms), ip6.arpa reverse-zone matching, RecordPTR field-and-query-param assertions, and AAAA+PTR create/update/delete through ApplyChanges — including a case asserting no PTR is created when CreatePTR is disabled.

go build, go vet, go test -count=1 ./..., gofmt -l and golangci-lint run ./... (0 issues) all pass.

The tests were mutation-checked. Worth calling out one result: an early version of the ApplyChanges tests passed with the wrong WAPI field being written, because the mock reads whichever field is set. Since a wrong field means broken reverse records against a real Grid, TestInfobloxRecordSetPTR was added to assert the concrete RecordPTR fields (ipv4addr empty, ipv6addr set) and the lookup query parameter. Forcing all PTRs to write Ipv4Addr now fails TestInfobloxRecordSetPTR/ipv6 and /ipv6_expanded.

Found but deliberately left alone

findReverseZone has a pre-existing longest-prefix-match bug that affects IPv4 too: it keys networks[mask] on the second return of rZoneNet.Mask.Size() (total bits — always 32 or 128) rather than the prefix length. With both 10.0.0.0/8 and 10.0.0.0/24 present, 10.0.0.5 resolves to whichever zone comes last rather than the more specific one. Out of scope here to keep the diff focused; happy to send a separate PR if useful.

Depends on #69.

MaxRink added a commit to MaxRink/external-dns-infoblox-webhook that referenced this pull request Aug 6, 2026
…ranch

GitHub only registers a workflow, and therefore only evaluates its
push-tag trigger, once the file has been seen on the repository default
branch. tcaas-image.yaml existed solely on tcaas-main, so it was never
registered: actions/workflows lists only the four upstream workflows and
three -telekom.N tag pushes produced zero runs.

Add the file to main so the trigger registers. Also add workflow_dispatch
with an explicit ref input for on-demand rebuilds, and a guard that
refuses to publish unless the resolved ref is a v*-telekom.* tag, so a
dispatch from main cannot publish a misleading image tag.

This file is fork-only and is deleted together with the rest of the
bridge once upstream ships PRs AbsaOSS#69/AbsaOSS#70.
@MaxRink
MaxRink force-pushed the feat/ipv6-ptr-support branch from 290da42 to e285794 Compare August 15, 2026 11:15
The provider previously fetched and reconciled only A, CNAME, TXT, NS,
host and PTR records, so IPv6 services managed through ExternalDNS could
never be reconciled against Infoblox: AAAA records already present in a
zone were invisible to Records() and any AAAA endpoint in a plan was
silently dropped by recordSet() because the switch had no matching case.

This adds AAAA end-to-end, following the existing patterns for A records:

- ToAAAAResponseMap() maps []ibclient.RecordAAAA onto the shared
  ResponseMap using the ibclient.AaaaRecord type constant.
- Records() fetches record:aaaa per zone through PagingGetObject, the
  same way A records are fetched.
- ToHostAAAAResponseMap() surfaces the Ipv6Addrs of host records as AAAA
  endpoints, mirroring how Ipv4Addrs are already surfaced as A endpoints.
  Both host mappers now skip records that carry no address of the
  respective family, so an IPv4-only or IPv6-only host record no longer
  yields an endpoint with an empty target.
- recordSet() builds a RecordAAAA for create/update/delete and looks the
  existing object up by name and ipv6addr.
- getRefID() resolves the object reference and log fields for RecordAAAA.

Infoblox can return the same IPv6 address more than once for a name (the
same address in a different but equivalent notation). Such duplicates
would produce a permanently dirty plan, so ToAAAAResponseMap de-duplicates
targets via the new ResponseDetails.Contains helper.

PTR records for AAAA are deliberately not created: the reverse zone
lookup path is built around IPv4 (rfc2317.CidrToInAddr) and ip6.arpa
delegation would need separate handling. CreatePTR therefore keeps
applying to A records only.
ToAAAAResponseMap skips a target that the response map already holds,
to avoid a permanent diff in the plan. The comment says that this also
covers the same address in a different notation, but the comparison was
a plain string comparison, so it did not.

Compare the canonical form of an IP target instead. Targets that are
not IP addresses keep the plain string comparison.
PR AbsaOSS#69 added AAAA records but deliberately left PTR creation to A records
only, on the assumption that the reverse zone lookup was IPv4 specific.
It turns out most of that machinery is already family agnostic:

- rfc2317.CidrToInAddr already derives ip6.arpa nibble names for IPv6
  CIDRs ("2001:db8::/64" -> "0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"),
  so Records() needs no change to fetch PTR records from an IPv6 reverse
  zone.
- findReverseZone uses net.ParseCIDR and net.IPNet.Contains, both of
  which already match IPv6 addresses against IPv6 reverse zones.

What actually had to change:

- ChangesByZone now derives the companion PTR change for AAAA endpoints
  as well as A endpoints, and AdjustEndpoints / Records() track the
  infoblox-ptr-record-exists property for AAAA endpoints too.
- recordSet() addresses a PTR record through ipv6addr instead of
  ipv4addr when the target is an IPv6 address, both when building the
  object and when looking the existing object up. A record:ptr carries
  its target in exactly one of the two fields, never in both.
- ToPTRResponseMap() falls back to Ipv6Addr when Ipv4Addr is empty, so
  PTR records read back from an ip6.arpa zone yield a target instead of
  an empty string.

CreatePTR still gates all of this, so nothing changes for users who do
not opt in. As before, the reverse zone has to be part of the
DOMAIN_FILTER; the README documents the IPv6 CIDR form.
@MaxRink
MaxRink force-pushed the feat/ipv6-ptr-support branch from e285794 to 2a2273d Compare August 28, 2026 00:16
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