Skip to content

DNSControl v5 Release - #4414

Merged
TomOnTime merged 280 commits into
mainfrom
release_candidate_v5
Aug 27, 2026
Merged

TomOnTime merged 280 commits into
mainfrom
release_candidate_v5

Conversation

@TomOnTime

@TomOnTime TomOnTime commented Jul 2, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #4748

Hello DNS fans!

v5.0.0 is released! Thanks to everyone for all their help testing and fixing bugs. This was a big community effort!

Thanks to all the people that helped test and fix bugs including @labrown @fm @cafferata @chicks @androw @AsifNawaz-cnic @blackshadev @bytemain @chicks-net @cylonchau @das7pad @dnscale-ops @DustyRah @eliheady @haylinmoore @huihuimoe @huskyistaken @imlonghao @jbelien @jochristian @kallsyms @KlettIT @kordianbruck @labrown @masterzen @meghanakudua02 @morozov-alexey @mtmn @patschi @pgaskin @ppmathis @rblenkinsopp @riku22 @riyadhalnur @SimenBai @SukkaW @ttkzw @vojtad @willpower232 @xddxdd @yzqzss @zupolgec (it's impossible to make such a list without forgetting someone. Apologies in advance to anyone I missed!)

v5.0.0 is all about EXTENSIBILITY!

  • When IETF standarizes a new record type: DNS Support is automatic!
    • When they appear in codeberg.org/miekg/dns, DNS control supports them (almost) automatically!
    • Individual provider support requires very little work, often none!
  • Non-standard record types like R53_ALIAS act just like standard types!
    • We no longer need to special-case them all over the code. They even appear in zonefiles!
  • “Builders” are easier to create, less brittle!
    • Builder can now be written in Go, which has much better testing facilities.
    • Legacy providers (SPF_BUILDER, DKIM_BUILDER, DMARC_BUILDER) are still brittle, but will soon be upgraded.
  • And…
    • Better internationalized domain name (IDN) support. Fields are now consistently processed at input, not later in the pipeline. This simplifies code and reduces potential errors. As a result, all providers now have 100% IDN support “for free”.
    • DNSControl should use significantly less memory for large DNS zones.
    • Writing new providers is significantly easier.
    • Processing large zones is faster, with new potential optimizations coming!
    • Zero compatibility issues. Your old dnsconfig.js should “just work”.

How did this happen? We upgraded from github.com/miekg/dns to codeberg.org/miekg/dns (a.k.a. “DNS version 2”) and took advantage of the new features. Specifically, models.RecordConfig no longer contains fields for each record type, most of which go unused. Instead it stores a DNS version 2 RDATA interface that contains the fields needed for that specific record type. This also allowed us to create new factories (NewRecordConfig() and NewRecordConfigParse()) which replace many lines of code in providers. As a result, every file that touches models.RecordConfig had to change. Thanks to @miekg for collaborating on the RDATA features which make much of this possible.

What does this mean to you?

  • IGNORE() is now reliable for all providers!
  • SOA records are no longer special. They work like all other records.
  • DS ordering works: A DS record is created after its target NS exists, and deleted only after its target NS is removed
  • dnscontrol foo now errors “command not found” instead of carping that no help for “foo” exists.
  • Documentation bugfix: Parameters for various types and builders now in sync with code.
  • VULTR now supports concurrency and AUTODNSSEC
  • Custom record types for ADGUARDHOME, AKAMAIDNS, AZURE_DNS, ROUTE52, BUNNYDNS, CLOUDFLAREAPI, CLOUDNS, LUADNS, MIKROTIK, NETLIFY, and PORKBUN have been converted to the new custom record type system. They are no longer special and even appear in zonefile backups.

Some potential regressions: (These record types may not have worked previously.)

  • deSEC no longer supports DS records
  • Exoscale no longer supports PTR records
  • Tencent no longer supports ALIAS or PTR records

Dev notes:

Developers might be interested in the code changes, which are described here in modernizingproviders.md

  • Code for standard record types (models/record_astype.go) and custom types (pkg/privatetypes/) are generated.
  • Record type is stored as an integer, because string comparisons are slow. (.Type is still available for now but please use .TypeNum)
  • Go 1.27 is now the minimum Go compiler version. The struct literal change is particularly helpful.
  • “Golden files” record API input/outputs for off-line testing, eliminating the need for API access in many cases.
  • All providers have been upgraded to “diff2”. The diff1 compatibility code has been removed.
    There is a new “cookbook” which explains common code patterns: Cookbook.
  • It is now significantly easier to write providers. The translation from native to models.RecordConfig is much easier thanks to new factory functions.
  • I'm now maintaining a list of REFACTORING PROJECTS in case anyone wants to grab one. It's a good way to learn the code base or get more experience with the Go language.
  • You may be interested that during the development of this release, both the old and new RecordConfig were supported with a very slick bidirectional conversion process. As the legacy fields were eliminated, the conversionn process got closer and closer to being a no-op until suddenly the and n

Stats:

  • 467 files changed!
  • 283 new files added!
  • 34 files deleted! (my favorite statistic!)

Testing notes

The following providers received extensive testing:

AKAMAIEDGEDNS ALIDNS AXFRDDNS AZUREDNS AZUREPRIVATEDNS BIND CLOUDFLARE CLOUDNS
CNR DESEC DIGITALOCEAN DNSCALE DNSIMPLE DOMAINNAMESHOP DYNU GANDIV5 GCLOUD
GIDINET HEDNS HUAWEICLOUD INFOMANIAK INWX LINODE LUADNS MYTHICBEASTS NAMECHEAP
NAMEDOTCOM NETBIRD NETLIFY NETNOD NEXDNS NS1 OPENWRT ORACLE OVH PORKBUN POWERDNS
ROUTE53 SAKURACLOUD TRANSIP UNIFI VERCEL VULTR WEBSUPPORT

The following providers lacked testing. Please use with caution:

ADGUARDHOME AUTODNS BUNNYDNS CSCGLOBAL DNSMADEEASY EXOSCALE FORTIGATE GCORE
GIGAHOST HETZNERV2 HOSTINGDE JOKER LOOPIA MIKROTIK NETCUP PACKETFRAME
REALTIMEREGISTER RWTH SCALEWAY SOFTLAYER TENCENTDNS

Users of the IMPORT_TRANSFORM() and IMPORT_TRANSFORM_STRIP() functions should test carefully before using in production.

Changes

The list is too long to include all commits, but here are the highlights:

  • Add factory for models.DomainConfig: models.NewDomainConfig(zone).
  • Add factories for models.RecordConfig, replace old code where we can.
  • Down-casing, canonicalization, IDN conversion, stutter checking, and normalizing fields is now done when making the RecordConfig, not at the validation/normalization step later in the pipeline. As a result, errors are reported sooner and more accurately. pkg/normalize/validate.go still exists and is used, but is slowly being deprecated.
  • Remove the rtypecontrol module. In the few places it was used, replace with the new RDATA functionality.
  • DNS types RP and DS were reimplemented using the new RDATA system.
  • Replace RecordConfig.Comparable with RecordConfig.ComparableV3 (name change to find stragglers).
  • Custom types are now described in YAML with code generated automatically (pkg/privatetypes/types_generate.yaml)
  • TLSA comparison is now done on ToUpper, not ToLower, strings.
  • Added a "cookbook" of how to use new factories: Cookbook.
  • Integration tests: Test cfworkers and cfredirect by default.
  • Integration tests: Improve SVCB/HTTPS tests, especially for ech=IGNORE
  • LOC() is now a "builder" that outputs LOC records.
  • LOC floating point rounding error fixed
  • Builders are registered using models.RegisterBuilder()
  • No longer store the "Raw" domain names (the name as the user input them). They were never used.
  • D_EXTEND() refactored. Implementation is simplier and faster.
  • RecordConfig now stores .TypeNum which is the numeric value for the type. Eventually we'll remove .Type.
  • Change github.com/miekg/dns to codeberg.org/miekg/dns (and related packages) where possible, including new helper functions in pkg/dnsrr/dnsrr.go to help migrate away from dnsv1
  • pkg/js/helpers.js: Improved rawRecordBuilder() to be feature-compatible with recordBuilder()
  • pkg/js/helpers.js: Convert to "the new way" for all record types.
  • pkg/js/parse_tests update fixtures due to new JSON fields. .json files no longer have Unicode chars.
  • Zonefiles now include "real" data for custom types instead of comments.
  • New package: mustbe for converting raw data to the types we need.
  • BIND: Zonefile generator produces better files.
  • BIND: Refactor SOA serial number handling.
  • CLOUDFLAREAPI: Update SINGLE_REDIRECT, CF_REDIRECT, CF_TMP_REDIRECT, CF_WORKER_ROUTE to comply with the new way to do custom record types.
  • New functions ZoneifyQuoted, Zoneify, etc. are standard ways to create zonefile-compatible strings.

@TomOnTime
TomOnTime requested a review from cafferata as a code owner July 2, 2026 18:31
@TomOnTime TomOnTime changed the title REFACTOR: Base RecordConfig on codeberg.org/miekg/dns instead of bespoke fields REFACTOR: Adopt codeberg.org/miekg/dns instead of bespoke fields in RecordConfig Jul 2, 2026
@TomOnTime
TomOnTime marked this pull request as draft July 2, 2026 19:30
@imlonghao

Copy link
Copy Markdown
Member

I tested this with PORKBUN and breaking.

Can we remove PORKBUN_URLFWD since it's an alias to URL and URL301 since #3951 and the new version is v5?

=== RUN   TestDNSProviders
Testing Profile="PORKBUN" (TYPE="PORKBUN")
=== RUN   TestDNSProviders/122323.xyz
=== RUN   TestDNSProviders/122323.xyz/Clean_Slate:Empty
    helpers_integration_test.go:246: 
        - DELETE final.122323.xyz TXT "TestDNSProviders was successful!" ttl=600, porkbun ID: 560927484
=== RUN   TestDNSProviders/122323.xyz/99:PORKBUN_URLFWD_tests:Add_a_urlfwd
WARNING: `PORKBUN_URLFWD` is deprecated. Please use `URL` or `URL301` instead.
    helpers_integration_test.go:246: 
        + CREATE urlfwd1.122323.xyz URL "http://example.com" "" "" "" includePath=no wildcard=yes ttl=0
    helpers_integration_test.go:251: failed create url forwarding record (porkbun): porkbun API error: Invalid value for location. URL:api.porkbun.com/api/json/v3/domain/addUrlForward/122323.xyz 
--- FAIL: TestDNSProviders (10.23s)
    --- FAIL: TestDNSProviders/122323.xyz (10.23s)
        --- PASS: TestDNSProviders/122323.xyz/Clean_Slate:Empty (7.00s)
        --- FAIL: TestDNSProviders/122323.xyz/99:PORKBUN_URLFWD_tests:Add_a_urlfwd (3.21s)
FAIL
exit status 1
FAIL    github.com/DNSControl/dnscontrol/v4/integrationTest     11.085s
failed to wait for command termination: exit status 1

TomOnTime and others added 30 commits August 13, 2026 18:14
…ords (#4758)

## The bug

`makeChanges()` passed `dom.Records` to `AuditRecords()`. `dom` is a
copy of the `DomainConfig` built by `getDomainConfigWithNameservers()`,
which calls `nameservers.AddNSRecords()` to synthesize one apex `NS`
record per delegated nameserver. Those records are present in every
single test case, so a provider whose auditor rejects apex `NS` records
rejects *every* test case in the suite — including the record-less
`Clean Slate` ones.

This was latent until #4707 made `rejectif.NsAtApex` actually match the
apex label (it had compared against `""` instead of `"@"`, so it never
fired). DOMAINNAMESHOP registers that rule, so its entire integration
run silently became a no-op:

| | before #4707 | after #4707 |
|---|---|---|
| subtests executed | 230 (229 pass, 1 fail) | **0** |
| skipped by the audit | 0 | **230** |
| skipped by group filters | 72 | 72 |

Zero API calls were made against the provider, and the run still
reported `ok`. WEBSUPPORT has the same problem via its `rejectNS` rule,
which rejects `NS` records unconditionally.

## Why production is unaffected

That asymmetry is the point. `AuditRecords()` runs in
`pkg/normalize/validate.go` on the user's records, whereas
`AddNSRecords()` only runs later, in `generateDelegationCorrections()`
(`commands/ppreviewPush.go`). In production the audit never sees
synthesized apex NS records — only `NS()` records the user wrote
themselves, which is exactly what these rules are meant to catch.

So this is a test-harness bug, not a provider bug, and `NsAtApex` itself
is correct as fixed in #4707.

## The fix

Audit only the records the test case contributes, so the harness matches
production ordering. Two details worth noting for review:

- The audited slice shares its pointers with `dom.Records`, so it holds
the same record objects the test goes on to push.
- `Auditor.Audit()` only ever appends to a named return, so it yields
`nil` for the empty slice a `Clean Slate` case produces. Those cases run
rather than skipping on an empty error list.

## Verification

Full run against DOMAINNAMESHOP (bdns.no): **230 subtests run and pass,
0 failures.** The 72 group-level filter skips are unchanged.

The only tests this rule still skips are the two in `27:NS only APEX`:

```
***SKIPPED(PROVIDER DOES NOT SUPPORT '[NS records not supported at apex]' ::"27:NS only APEX")
***SKIPPED(PROVIDER DOES NOT SUPPORT '[NS records not supported at apex NS records not supported at apex]' ::"27:NS only APEX")
```

Those are the only test cases in the entire suite carrying an apex NS
record of their own (`ns("@", ...)` appears exactly twice, at
`integration_test.go:511-512`), and they are precisely the tests #4707
set out to make skip.

## Note for WEBSUPPORT (@mtmn)

This un-skips WEBSUPPORT's suite too, but its `26:NS` group will then
genuinely fail rather than skip, since `rejectNS` rejects all NS records
and WEBSUPPORT isn't in that group's `not()` list. Left alone here —
needs a separate look from someone who can run against that provider.

Related to #4748.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01WoVVephnrZDEmU4fzzWUzJ

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#4664 (commit:
d5268bb) introduced a regression
preventing successful integration tests harness run against
release_candidate_v5.

This PR addresses the regression.

```
--- FAIL: TestDNSProviders (370.74s)
    --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com (370.40s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/04:CNAME:Create_a_CNAME (0.55s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/05:CNAME-short:Create_a_CNAME (0.83s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/17:TypeChangeHard:Create_a_CNAME (0.49s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/22:CNAME:Record_pointing_to_@ (0.97s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/26:NS:NS_for_subdomain (0.44s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/33:IDNA:Internationalized_CNAME_Target (0.43s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/34:IDNAs_in_CNAME_targets:IDN_CNAME_AND_Target (0.46s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/43:PTR:Create_PTR_record (0.47s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/55:ALIAS_on_apex:ALIAS_at_root (0.41s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/56:ALIAS_to_nonfqdn:ALIAS_at_root (0.87s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/57:ALIAS_on_subdomain:ALIAS_at_subdomain (0.41s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/91:IGNORE_main:Create_some_records (2.29s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/92:IGNORE_apex:Create_some_records (2.24s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/94:IGNORE_wilds:Create_some_records (2.17s)
        --- FAIL: TestDNSProviders/dnscontrol-test-zone-001.com/96:IGNORE_TARGET_b2285:Create_some_records (0.52s)
FAIL
FAIL    github.com/DNSControl/dnscontrol/v5/integrationTest     372.969s
```
Skip NullMX tests for Vercel and save us some headroom within rate
limits.
Hi @TomOnTime here is tencentdns integration tests fix


```
=== RUN   TestMakeTests
--- PASS: TestMakeTests (0.03s)
=== RUN   TestDualProviders
Testing Profile="TENCENTDNS" (TYPE="TENCENTDNS")
    provider_test.go:50: Clearing everything
    provider_test.go:44: #1:
        - DELETE final.drdm88.com TXT "TestDNSProviders was successful!" line_id=0 ttl=600
    provider_test.go:62: Adding test nameservers
    provider_test.go:44: #1:
        + CREATE drdm88.com NS ns1.example.com. line_id=0 ttl=600
    provider_test.go:44: #2:
        + CREATE drdm88.com NS ns2.example.com. line_id=0 ttl=600
    provider_test.go:65: Running again to ensure stability
    provider_test.go:81: Removing test nameservers
    provider_test.go:44: #1:
        - DELETE drdm88.com NS ns1.example.com. line_id=0 ttl=600
    provider_test.go:44: #2:
        - DELETE drdm88.com NS ns2.example.com. line_id=0 ttl=600
--- PASS: TestDualProviders (9.05s)
=== RUN   TestNameserverDots
Testing Profile="TENCENTDNS" (TYPE="TENCENTDNS")
=== RUN   TestNameserverDots/No_trailing_dot_in_nameserver
--- PASS: TestNameserverDots (2.86s)
    --- PASS: TestNameserverDots/No_trailing_dot_in_nameserver (0.00s)
=== RUN   TestDuplicateNameservers
Testing Profile="TENCENTDNS" (TYPE="TENCENTDNS")
    provider_test.go:150: Skipping. Deduplication logic is not implemented for this provider.
--- SKIP: TestDuplicateNameservers (1.10s)
=== RUN   TestARecordingIsNamedAfterTheProvidersPackageNotItsType
--- PASS: TestARecordingIsNamedAfterTheProvidersPackageNotItsType (0.00s)
=== RUN   TestRecordingDirResolvesRecordDirFromTheModuleRoot
--- PASS: TestRecordingDirResolvesRecordDirFromTheModuleRoot (0.00s)
PASS
ok      github.com/DNSControl/dnscontrol/v5/integrationTest     726.367s
```


### 1. ALIAS Record Failures (Tests 55: `ALIAS on apex`, 56: `ALIAS to
nonfqdn`, 57: `ALIAS on subdomain`)
* **Original Error log**: 

```
        --- FAIL: TestDNSProviders/drdm88.com/55:ALIAS_on_apex:ALIAS_at_root (2.91s)
        --- FAIL: TestDNSProviders/drdm88.com/56:ALIAS_to_nonfqdn:ALIAS_at_root (3.21s)
        --- FAIL: TestDNSProviders/drdm88.com/57:ALIAS_on_subdomain:ALIAS_at_subdomain (2.77s)
```

* **Root Cause**: 
Tencent Cloud DNSPod does not have a native `ALIAS` record type. The
driver previously faked ALIAS support by converting them into CNAME
records and declared `CanUseAlias: Can()`.
* **Why this fix**: 
In accordance with DNSControl conventions and aligned with other
providers like `alidns` and `huaweicloud`, pseudo-ALIAS conversions
should not be implemented in the provider. Setting `CanUseAlias:
Cannot()` accurately reflects native capabilities and causes the
integration test suite to automatically skip these tests.
* **Changes Made**:
- `providers/tencentdns/tencentdnsProvider.go`: Set
`providers.CanUseAlias: providers.Cannot()`.
- `providers/tencentdns/convert.go`: Removed all fake `ALIAS` conversion
logic in `nativeToRecord`, `recordToCreateRequest`, and
`recordToModifyRequest`.

### 2. TXT Special Character & Escaping Failures (Tests 28: `complex
TXT`, 29: `TXT backslashes`)

* **Original Error log**:

```
=== RUN   TestDNSProviders/drdm88.com/28:complex_TXT:TXT_with_1_dq-1interior
    helpers_integration_test.go:246: 
        + CREATE foodq.drdm88.com TXT "in\"side" line_id=0 ttl=600
    helpers_integration_test.go:251: [TencentCloudSDKError] Code=InvalidParameter.RecordValueInvalid, Message=记录的值不正确。, RequestId=201968b0-ecc9-4fa2-81bc-7790d7de45fe
--- FAIL: TestDNSProviders/drdm88.com/28:complex_TXT:TXT_with_1_dq-1interior (2.45s)

=== RUN   TestDNSProviders/drdm88.com/28:complex_TXT:TXT_trailing_ws
    helpers_integration_test.go:246: 
        + CREATE foows1.drdm88.com TXT "trailingws " line_id=0 ttl=600
--- FAIL: TestDNSProviders/drdm88.com/28:complex_TXT:TXT_trailing_ws (3.12s)

=== RUN   TestDNSProviders/drdm88.com/29:TXT_backslashes:TXT_with_backslashs
    helpers_integration_test.go:246: 
        + CREATE foobs.drdm88.com TXT "1back\\slash" line_id=0 ttl=600
--- FAIL: TestDNSProviders/drdm88.com/29:TXT_backslashes:TXT_with_backslashs (3.24s)
```

* **Root Cause**: 
Tencent Cloud DNSPod API enforces strict sanitation/rejection on special
characters in TXT record values.
* **Why this fix**: 
Per DNSControl's design guidelines, providers should use `AuditRecords`
with `rejectif` helpers to explicitly reject unsupported TXT character
patterns rather than attempting fragile escaping hacks. The integration
test runner automatically skips rejected record types.
* **Changes Made**:
- `providers/tencentdns/auditrecords.go`: Added
`rejectif.TxtHasSingleQuotes`, `rejectif.TxtHasDoubleQuotes`,
`rejectif.TxtHasBackslash`, and `rejectif.TxtHasTrailingSpace`.
- `providers/tencentdns/auditrecords_test.go`: Added unit tests covering
all TXT rejection constraints.

### 3. Non-Chinese Internationalized Domain Name (IDN) Failures (Tests
33: `IDNA`, 34: `IDNAs in CNAME targets`)

* **Original Error log**: 

```
=== RUN   TestDNSProviders/drdm88.com/33:IDNA:Create_an_IDNA
    helpers_integration_test.go:246: 
        + CREATE xn--55qx5d.drdm88.com A 1.2.3.4 line_id=0 ttl=600
        + CREATE xn--ndaaa.drdm88.com A 1.2.3.4 line_id=0 ttl=600
    helpers_integration_test.go:251: [TencentCloudSDKError] Code=InvalidParameter.SubdomainInvalid, Message=主机记录不正确。, RequestId=...
--- FAIL: TestDNSProviders/drdm88.com/33:IDNA (2.89s)
```

* **Root Cause**: 
Tencent Cloud DNSPod (like Alibaba DNS) only permits ASCII and Chinese
characters (CJK Unified Ideographs `U+4E00..U+9FFF` and Extension A
`U+3400..U+4DBF`), rejecting other Unicode scripts.
* **Why this fix**: 
Aligned with `alidns` implementation: implemented `labelConstraint` (for
record labels) and `targetConstraint` (decoding Punycode targets for
CNAME/MX/NS/SRV) in `AuditRecords`.
* **Changes Made**:
- `providers/tencentdns/auditrecords.go`: Added
`isValidTencentDNSString`, `labelConstraint`, and `targetConstraint`.
- `providers/tencentdns/auditrecords_test.go`: Added unit tests for IDN
label and target constraints.


### 4. Line Routing & Weight Metadata on Free account package /
International Site (Test 71: `TENCENTDNS_LINE_WEIGHT`)

* **Original Error log**: 
```
=== RUN   TestDNSProviders/drdm88.com/72:TENCENTDNS_LINE_WEIGHT_INTL:create_records_on_the_default_and_regional_lines
    helpers_integration_test.go:246: 
        + CREATE tencent-line.drdm88.com A 1.2.3.4 line=China Unicom ttl=600
    helpers_integration_test.go:251: [TencentCloudSDKError] Code=InvalidParameter.RecordLineInvalid, Message=记录线路不正确。, RequestId=b6370ad3-7c66-4e01-af90-5dabde5412d7
--- FAIL: TestDNSProviders/drdm88.com/72:TENCENTDNS_LINE_WEIGHT_INTL:create_records_on_the_default_and_regional_lines (1.63s)
```

or change default or “默认”

```
```text
=== RUN   TestDNSProviders/drdm88.com/55:ALIAS_on_apex:ALIAS_at_root
    helpers_integration_test.go:246: 
        + CREATE @.drdm88.com CNAME foo.com. line_id=0 ttl=600
helpers_integration_test.go:251: [TencentCloudSDKError]
Code=InvalidParameter.ConflictRecord, Message=与已有默认线路记录冲突,无法添加。,
RequestId=...
--- FAIL: TestDNSProviders/drdm88.com/55:ALIAS_on_apex:ALIAS_at_root
(2.41s)

=== RUN   TestDNSProviders/drdm88.com/56:ALIAS_to_nonfqdn:ALIAS_at_root
    helpers_integration_test.go:246: 
        + CREATE foo.drdm88.com A 1.2.3.4 line_id=0 ttl=600
        + CREATE @.drdm88.com CNAME foo.drdm88.com. line_id=0 ttl=600
helpers_integration_test.go:251: [TencentCloudSDKError]
Code=InvalidParameter.ConflictRecord, Message=与已有默认线路记录冲突,无法添加。,
RequestId=...
--- FAIL: TestDNSProviders/drdm88.com/56:ALIAS_to_nonfqdn:ALIAS_at_root
(2.35s)

=== RUN
TestDNSProviders/drdm88.com/57:ALIAS_on_subdomain:ALIAS_at_subdomain
    helpers_integration_test.go:246: 
        + CREATE sub.drdm88.com CNAME foo.com. line_id=0 ttl=600
--- FAIL:
TestDNSProviders/drdm88.com/57:ALIAS_on_subdomain:ALIAS_at_subdomain
(2.52s)
```

* **Root Cause**: 
  1. Line names differ between the China site (ISP lines: `电信`, `联通`) and the International site (`Asia`, `Europe`).
  2. More importantly, custom line routing (split DNS) is a paid feature in Tencent Cloud DNSPod. Free-tier domains (common in CI/integration testing) only support the default line (`0` / `默认`).
* **Why this fix**: 
  Preserved the China site test group (`TENCENTDNS_LINE_WEIGHT`) but dynamically checked `globalCfg["site"]` (loaded from `profiles.json`) using `alltrue(!strings.EqualFold(globalCfg["site"], "intl"))` so that international free-tier test runs automatically skip unsupported line tests.
* **Changes Made**:
  - `integrationTest/helpers_integration_test.go`: Added `globalCfg` populated from `origConfig` in `runTests()`.
  - `integrationTest/integration_test.go`: Added `alltrue(!strings.EqualFold(globalCfg["site"], "intl"))` to guard `TENCENTDNS_LINE_WEIGHT`.



### 5. MX Preference Value 100 Failure (Test `manyTypesAtOnce`)
* **Original Error log**: 

```
=== RUN
TestDNSProviders/drdm88.com/manyTypesAtOnce:CreateManyTypesAtLabel
    helpers_integration_test.go:246: 
        + CREATE testmx.drdm88.com MX 100 bar.com. line_id=0 ttl=600
helpers_integration_test.go:251: [TencentCloudSDKError]
Code=InvalidParameter.RecordValueInvalid, Message=记录的值不正确。,
RequestId=...
--- FAIL:
TestDNSProviders/drdm88.com/manyTypesAtOnce:CreateManyTypesAtLabel
```

  Creating an MX record with priority 100 failed during integration tests.
* **Root Cause**: 
  Tencent Cloud DNSPod restricts MX priority to `1 ~ 50` for free-tier accounts (while paid plans support `0..65535`).
* **Why this fix**: 
  Adjusted the integration test record from priority 100 to 50 so tests pass on free-tier test accounts while avoiding hardcoding a 1..50 restriction in the provider's `AuditRecords` that would block paid plan users.
* **Changes Made**:
  - `integrationTest/integration_test.go`: Changed `mx("testmx", 100, "bar.com.")` to `mx("testmx", 50, "bar.com.")` in `manyTypesAtOnce`.



### 6. Blacklisted Public IP `5.5.5.5` Failure (Tests 02: `Protocol-Wildcard`, 92: `IGNORE_main`, 93: `IGNORE_apex`, 95: `IGNORE_wilds`)

* **Original Error log**: 

```
=== RUN TestDNSProviders/drdm88.com/02:Protocol-Wildcard:Create_wildcard
    helpers_integration_test.go:246: 
        + CREATE www.drdm88.com A 5.5.5.5 line_id=0 ttl=600
helpers_integration_test.go:251: [TencentCloudSDKError]
Code=OperationDenied.IPInBlacklistNotAllowed, Message=抱歉,不允许添加黑名单中的IP。,
RequestId=82fc016f-a8ec-4d99-811e-716f479986e1
--- FAIL:
TestDNSProviders/drdm88.com/02:Protocol-Wildcard:Create_wildcard (2.44s)
```

* **Root Cause**: 
  Tencent Cloud DNSPod security policy blocks adding `5.5.5.5` as a DNS record target.
* **Why this fix**: 
  The integration test only tests wildcard and ignore mechanics; the IP address itself is arbitrary. Replaced `5.5.5.5` with `5.4.5.4` across the affected tests.
* **Changes Made**:
  - `integrationTest/integration_test.go`: Replaced `5.5.5.5` with `5.4.5.4` in wildcard and ignore test suites.
Fixes #4759

Also adds a missing file pkg/prettyzone/sorting_test.go
…ases (#4786)

## What

Fixes #4780, a bug found by an LLM inspecting the code. The
`hasResolvedLastRound` must indeed be moved one up. I let an LLM
generate a testcase for it and loo and behold: it failed to sort the
most basic of record set without a good reason. Moving the
`hasResolvedLastRound` to the "round" loop and it sorted it no problem.

TLDR: good bot.

## Release changelog section

BUGFIX: Fix dnssort issue not resolving all records in certain edgecases
Fixes #4781.

## 1. NAPTR — the reported bug

The LLM was right. `toReq()` built the Dynu API `replacement` field from
the record's `Service` rather than its `Replacement`:

```go
naptrTarget := f.Service
```

`rdata.NAPTR` has both a `Service` and a `Replacement` field, so this
compiled cleanly and sent the service string as the replacement on every
create and update. The read path in `toRc()` was already correct, which
is probably why it reads as fine at a glance — only writes were
affected.

It isn't a silent-corruption bug. Dynu validates `replacement` as a
hostname, so a service value like `E2U+sip` is rejected outright and
NAPTR records could not be pushed at all. Reverting just this line and
running the NAPTR group against a live account:

```
=== RUN   TestDNSProviders/claudecode.com/42:NAPTR:NAPTR_record
    provider Dynu API error 505: Invalid host.
--- FAIL: TestDNSProviders/claudecode.com/42:NAPTR:NAPTR_record (0.11s)
```

It fails on the first NAPTR case. With the fix, all 10 pass.

## 2. RP — a second bug found while verifying

Running the full suite to check the NAPTR output surfaced an unrelated
failure in both RP groups, `505: Invalid content.`. `toReq()` passed the
RP mailbox and TXT domain name through unchanged, where every other
name-valued field in that switch strips the trailing dot first. Dynu
validates both as hostnames and rejects the trailing dot.

Confirmed directly against the API:

```
mailBox=user.example.com.  ->  505 Invalid content.
mailBox=user.example.com   ->  200 OK
```

DNSControl fully qualifies relative names before they reach the
provider, so both fields always arrive with a trailing dot — `rp("foo",
"user", "server")` arrives as `user.example.com.` and
`server.example.com.`. That makes it unconditional rather than an edge
case. The read path already runs both through `ensureTrailingDot()`, so
the round trip is symmetric once the write path strips it.

I've kept this as a separate commit so it can be dropped independently
if you'd rather keep the PR scoped to the reported issue.

## Testing

Verified against a live Dynu account and zone.

| Check | Result |
| --- | --- |
| `gofmt -l providers/dynu/` | clean |
| `go build ./...` | pass |
| `go vet ./providers/dynu/...` | pass |
| `go test ./providers/dynu/...` | pass |
| `go test ./integrationTest/ -provider DYNU` | **305 passed, 0 failed,
112 skipped** |

The suite is fully green with both fixes. Before them it failed on NAPTR
and RP.

New regression tests, each of which fails without its corresponding fix:

- `TestToReqNAPTR` — write path, FQDN and null replacement, and asserts
the other five NAPTR fields land in the right place
- `TestToReqRP` — write path, absolute and relative names
- Two NAPTR cases added to the existing `TestToRc` table — read path,
including the null replacement round trip where Dynu represents `.` as
an empty string

No documentation change: `documentation/provider/dynu.md` already lists
NAPTR and RP as supported, which is now actually true.

---------

Co-authored-by: Rah Sharma <rah.sharma@dynu.systems>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
The labelConstraint function checked rc.GetLabel() directly, which for
punycode labels like xn--ndaaa (ööö) returned pure ASCII and passed
isValidAliDNSString. The AliDNS API however rejects non-Chinese IDN
punycode labels with SubDomainInvalid.RR.

Mirror the same idna.ToUnicode decode that targetConstraint already
uses, so non-Chinese punycode labels are correctly rejected by
AuditRecords and the IDNA integration test
(33:IDNA:Internationalized_name) is skipped instead of hitting the live
API.

Also add unit tests: TestLabelConstraint and
TestAuditRecordsRejectsNonChineseIDNLabel.

<!--
## Before submiting a pull request

Please make sure you've run the following commands from the root
directory.

    bin/generate-all.sh

(this runs commands like "go generate", fixes formatting, and so on)

## Release changelog section

Help keep the release changelog clear by pre-naming the proper section
in the GitHub pull request title.

Some examples:
* CICD: Add required GHA permissions for goreleaser
* DOCS: Fixed providers with "contributor support" table
* ROUTE53: Allow R53_ALIAS records to enable target health evaluation

More examples/context can be found in the file .goreleaser.yml under the
'build' > 'changelog' key.
!-->

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Tom Limoncelli <tal@whatexit.org>
…ord handling (#4791)

Fixes #4761

The API key we use to test OVH does not have access to the "list all
zones" API call. Therefore we need to be able to work without that call.

When used, ListZones() returns an empty list and the systems that rely
(verifying that a zone exists) assume the zone is valid.

This mode is activated by the secret flag `preview --disable-list-zones`
(works with `push` too) or when running integration tests,
`-disablelistzones`.

Example usage:

```
# Testing:
go test -timeout 1h -failfast -v -args -verbose -profile OVH -disablelistzones
# preview/push:
dnscontrol preview --disable-list-zones
```

This issue was raised in
#4761

Now that OVH integration tests work, we discovered that TXT records
don't parse correctly. Fixed.
Work-around for bug in staticcheck no longer needed.
… verb (#4802)

`fmt.Errorf` with no format directive is equivalent to `errors.New` but
does a needless `Sprintf` pass (staticcheck **S1028**). **No behavior
change.**

### Changes
- Rewrite the 36 verb-less `fmt.Errorf("...")` call sites to
`errors.New("...")` across 19 files, adjusting imports (`errors` in /
`fmt` out) as needed.
- Two auditors declared a local `var errors []error` that shadowed the
`errors` package; renamed to `errs` so the package call resolves
(`providers/openwrt/auditrecords.go`,
`providers/unifi/auditrecords.go`).

### Verification
`gofmt -l` clean · `go vet ./...` clean · `go build ./...` clean ·
`golangci-lint run` (v2.13.1) → 0 issues.
…4801)

Small, mechanical cleanups from a repo-wide Go style pass. **No behavior
change.**

### Changes
- **Boolean returns** — collapse `if cond { return true }; return false`
into `return cond` (`models/stutter.go`, `pkg/mustbe/hosts.go`,
`providers/gcloud/gcloudProvider.go`).
- **Regexp hoisting** — move constant `regexp.MustCompile(...)` calls
out of frequently-called functions into package-level vars so they
compile once (`commands/ppreviewPush.go`, `providers/desec/protocol.go`,
`providers/softlayer/softlayerProvider.go`).
- **Error paths** — drop a redundant `else` after a terminating branch,
lowercase/depunctuate a few error strings, and fix a missing space in an
autodns error message (`providers/autodns/api.go`,
`providers/route53/route53Provider.go`, `providers/openwrt/api.go`).

### Verification
`gofmt -l` clean · `go vet ./...` clean · `go build ./...` clean ·
`golangci-lint run` (v2.13.1) → 0 issues.
* Bump the `go` directive in `go.mod` from `1.26` to `1.27`.
* Update types_generate.go to generate Go 1.27 constructs
* Update non-generated files to Go 1.27 constructs
…4805)

Fixes #4804, and extends the fix into a full audit of the
domain-modifier docs.

`commands/types/dnscontrol.d.ts` is generated by `build/generate` from
each doc's front-matter (`parameters` + `parameter_types`). The LOC bug
in #4804 turned out to be one instance of a broader class, so I
cross-checked **every** doc in
`documentation/language-reference/domain-modifiers/` against the real
signatures (`models.Make*` arg counts, `pkg/js/helpers.js`, and
`pkg/privatetypes/types_generate.yaml`).

### LOC (#4804)
Added the four missing params — `name`, `ns`, `ew`, `...modifiers`:
```ts
declare function LOC(name: string, deg1: number, min1: number, sec1: number, ns: "N" | "S" | "n" | "s", deg2: number, min2: number, sec2: number, ew: "E" | "W" | "e" | "w", altitude: number, size: number, horizontal_precision: number, vertical_precision: number, ...modifiers: RecordModifier[]): DomainModifier;
```

### Additional mismatches found & fixed
**Missing trailing `...modifiers`** (all are `rawrecordBuilder` records
that accept trailing `RecordModifier`s, same as LOC):
- `NAPTR`
- `ADGUARDHOME_A_PASSTHROUGH`
- `ADGUARDHOME_AAAA_PASSTHROUGH`
- `CF_WORKER_ROUTE`

**Wrong/stray params:**
- `NAMESERVER` — removed a bogus `...modifiers`; the JS function throws
if given more than one argument (`helpers.js:766`).
- `NAMESERVER_TTL` — removed stray `target`/`modifiers...` entries from
`parameter_types` (not real parameters).

**Missing object properties:**
- `DMARC_BUILDER` — added `percent` (`pct=`), `failureFormat` (`rf=`),
and `reportInterval` (`ri=`), which the builder reads
(`helpers.js:1711/1749/1755`) but the docs omitted.
Fixes #4796

* Bug 1: `R53_ALIAS`: Always sets the target to the apex domain.
* Bug 2: `R53_EVALUATE_TARGET_HEALTH`: Always sets to "false".

Changes:

* MakeR53ALIAS was defined in two places (luckily the extra one was
unused)
* The JS r53AliasOptions function ignored the 3rd argument, which held
the non-apex string.
* evaluate_target_health was set to "false" no matter what.
* Added test cases to 019-r53-alias.js to cover both bugs.

This branch was previously deployed

1 inactive deployment
github-pages — 8ebbb1fc Deployed Aug 27, 2026 by TomOnTime via deploy #1879
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Development

Successfully merging this pull request may close these issues.

Testing for Release candidate 5.0.0-rc9 (due by 25-Aug-2026)