Skip to content

feat(p/CONSTELLIX): add Constellix DNS provider - #5029

Merged
TomOnTime merged 1 commit into
DNSControl:mainfrom
ZeroClover:feat/constellix-provider
Oct 21, 2026
Merged

TomOnTime merged 1 commit into
DNSControl:mainfrom
ZeroClover:feat/constellix-provider

Conversation

@ZeroClover

Copy link
Copy Markdown
Contributor

Adds a DNS provider for Constellix (CONSTELLIX), using their DNS v4 API. Closes #842 ("Constellix" on the requested-providers list).

Please create the GitHub label 'provider-CONSTELLIX'

What it does

  • diff2.ByRecordSet: a Constellix record object is one name + type with a list of values, so each change is one POST (create), PUT (complete value list + TTL; the API's PUT is partial) or DELETE, addressed by the record's ID.
  • Supported types: Basic8, ALIAS (Constellix ANAME, sent with skipLookup so a target created in the same push is accepted), PTR in reverse zones only (Constellix refuses PTR names in forward zones: The name is not valid). SOA is declared unimplemented.
  • get-zones / ListZones and create-domains / auto-create on push (EnsureZoneExists), with pkg/zonecache like CLOUDFLAREAPI. A zone whose creation or first read fails is remembered with the error, so the same run cannot compute a delegation without Constellix.
  • Apex NS: Constellix serves its own nameservers without listing them as records. Configured apex NS records that point at them are dropped (like CLOUDFLAREAPI's checkNSModifications, but matched against the zone's actual nameserver list); any other apex NS record is managed normally, so dual hosting works (TestDualProviders passes).
  • Records that use features DNSControl does not model are refused on read, with an error naming the record, rather than being overwritten later: failover / pools / round-robin-failover modes, GTD regions, IP filters, GeoProximity, disabled records or values, empty records, other types (HTTP redirects, NAPTR, ...), and template-linked zones.
  • TXT is written in presentation format, with the 255-octet strings back to back ("..."".."), the form the API stores; read back from the joined text, because the API re-splits long values by their escaped length.
  • AuditRecords: empty TXT, null MX, null SRV target, CAA tags other than issue/issuewild/iodef, and NS records whose target is the zone apex (the API answers those with HTTP 500). TTLs above 2147483647 are lowered before the diff.

Rate limit

The API allows 180 units per 60 seconds for the whole account (one request can use several units) and answers 429 without Retry-After; X-RateLimit-Reset says when the window resets. The client has one gate per provider instance: after a 429, or a response with X-RateLimit-Remaining: 0, every request of that instance waits until the reset (+1 s; 60 s if the header is missing), and the rejected request is sent again with a fresh token (POST included: a POST applied after all would answer "already exists", never create a duplicate). A single request gives up after 5 minutes of waiting in total. No other status or transport error is retried.

Testing

Integration tests

go test -timeout 120m -v -args -verbose -record -profile CONSTELLIX against the made-up test zone dnscontrol-constellix-test.com: PASS in 789 s (13 min 15 s wall time), 0 failures.

  • Opt-out: PTR → not("CONSTELLIX") (the group creates a PTR in a forward zone; Constellix: The name is not valid). PTR in reverse zones was verified by hand, below.
  • Audit-rule skips: NullMX, NullMXApex, a 0-byte TXT, and NS Record pointing to @ (foo NS <zone>. answered HTTP 500 reproducibly, even with the apex A in place).
  • pager101 passes; all other skipped groups are capability-gated or only(...) groups for other providers.

Full output: https://gist.github.com/ZeroClover/217e5b72d4ac21604d31c37159339d4b

TXT procedure (testing-txt-records.md)

t0–t3 were created in the Constellix portal with the values test0, test1, "test2", "test3". The portal's TXT field is read like zone-file text (the same as the API), so "test2" is stored as the string test2: the API returned "test0", "test1", "test2", "test3" and the authoritative server answered the same. preview (with DefaultTTL(1800), the portal's default) therefore reported changes for t1 and t3, not t1 and t2. After push, preview is clean and the authoritative answers (dig @ns11.constellix.com and @ns41.constellix.net) equal the strings in dnsconfig.js:

Record Answer
t0 "test0"
t1 "\"test1\""
t2 "test2"
t3 "\"test3\""

PTR in reverse zones

2.0.192.in-addr.arpa and 8.b.d.0.1.0.0.2.ip6.arpa: push created single- and multi-value PTR sets (including a 24-nibble IPv6 name and a relative target), changed a target and deleted sets; dig @ns11.constellix.com showed each state (IPv6 answers lag a few seconds after a write), and preview was clean after every push.

Concurrency

go build -race, then dnscontrol preview --cmode all over four test zones, twice: no race report, so CanConcur stays Can(). A unit test also reads six zones concurrently with a 429 on one of them under go test -race.

Other checks

  • go generate ./...: generated files up to date.
  • golangci-lint run ./...: 0 issues. staticcheck ./...: clean. modernize -fix ./...: no changes. go vet ./...: clean.
  • go test ./...: passes (except two pkg/spflib tests that look up spf.mtasv.net, which does not resolve from my machine; unrelated).

Notes

  • Constellix is a paid-only service, so I can't donate an account for the CI integration tests; the CI env entries are added like for every provider and the job skips the provider without them. I have already contacted DigiCert (Constellix) to ask whether they might be able to provide a sandbox account for integration testing.
  • I (@ZeroClover) will maintain the provider.

Add a DNS provider for Constellix (CONSTELLIX) using the DNS v4 API.

- Record sets are diffed with diff2.ByRecordSet and applied with one
  POST, PUT or DELETE per set, addressed by record ID.
- Supports Basic8, ALIAS (as ANAME with skipLookup) and PTR in reverse
  zones; SOA is declared unimplemented.
- Lists and creates zones through pkg/zonecache.
- Drops configured apex NS records that point at the zone's own
  Constellix nameservers; other apex NS records are managed, so dual
  hosting works.
- Refuses on read any record that uses a feature DNSControl does not
  model (failover, pools, GTD regions, IP filters, GeoProximity,
  disabled records or values, other types, template-linked zones).
- Honours the account-wide rate limit: on 429 or X-RateLimit-Remaining
  0 all requests wait until X-RateLimit-Reset, and a 429 is resent.
- Writes TXT strings back to back and reads them back from the joined
  text, because the API re-splits long values itself.

Refs DNSControl#842
@TomOnTime

Copy link
Copy Markdown
Collaborator

Thank you for contributing to this new provider, @ZeroClover !

An action item before we can merge this PR:

  1. @fm: Faisal Misle is our “liaison to maintainers”. Please send him an email from your preferred address to dnscontrol so we can stay in touch. Your email address will not be shared, only used for DNSControl communication. Please email f at faisal dot fm and CC tal at what exit dot org

When I receive the email, I’ll merge this PR.

Thanks again!
Tom

@ZeroClover

Copy link
Copy Markdown
Contributor Author

Hi @TomOnTime

I've sent the email.

Thanks.

@TomOnTime
TomOnTime merged commit 71c0081 into DNSControl:main Oct 21, 2026
13 checks passed
@ZeroClover
ZeroClover deleted the feat/constellix-provider branch October 21, 2026 18:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Provider request: Constellix (DNSMadeEasy)

2 participants