• DcRouter v34.3.0
    Release / build-and-release (push) Successful in 16m44s
    Stable

    jkunz released this 2026-10-26 10:47:38 +00:00 | 12 commits to main since this release

    2026-10-26 - 34.3.0

    Features

    • A health list of stored DNS records dcrouter cannot encode (ops API, AGL-1008). getInvalidDnsRecords (dns-records:read) reads every DnsRecordDoc in every domain and returns { records: IDnsInvalidRecord[] }, sorted by domain, name and id: each entry names the record (id, domainId, name, type), the stored value as it is, the refused field (TDnsRecordValueField: type, value, address, target, preference, exchange, or an SOA field) and the reason. A record of a dcrouter-hosted zone in the list is not served; a provider zone record in it is a local mirror that a write or a migration refuses. dcrouter never rewrites them: fix or delete each one.
    • Refused record values are typed (ops API). createDnsRecord and updateDnsRecord answer a refused value with success: false, a message naming the record and the field, and invalidValue: IDnsRecordValueRefusal (id for an existing record, name, type, field, reason); migrateDomain answers a refused zone with invalidRecords. Nothing is written or journaled for such a refusal; for automation it is a non-retryable refusal: 'invalid-value'.
    • Hand one operator record back to route DNS (ops API, AGL-1009). handBackDnsRecordToRouteDns (admin, dns-records:write; recordId, gatewayClientId, reason) makes an unowned A or AAAA record route-owned for that client's claim at its hostname, keeping its DNS data, and writes an assign entry to the DNS mutation journal (new operation, DnsManager.assignRecordManagement: a compare-and-set on the record still being the unowned record that was checked; every automation actor is refused not-owned). It is refused with a typed refusal and nothing written (record-not-found, not-operator-record, not-address-record, protected-hostname, hostname-held, route-inventory-unavailable, no-active-claim, target-unavailable, value-mismatch, zone-mismatch, other-records, ownership-changed, mutation-failed, with retryable) unless the record is the hostname's only A, AAAA or CNAME record and its type, value and declared proxy state equal route DNS's target for that claim. Route DNS still never adopts an operator record on its own, createdBy included.
    • Route DNS's desired target per claimed hostname, read only (ops API, AGL-1009). getGatewayRouteDnsTargets (dns-records:read, optional hostname) answers { targets: IGatewayRouteDnsTarget[] }: per hostname and claiming gateway client the claim owner id, the route external keys, recordType and targetIp (absent when route DNS has no usable target) and the declared proxied state, from the reconciler's own rule; a retryable route-inventory-unavailable error until the route inventory is loaded.

    Fixes

    • One owner for the meaning of a DNS record value (DNS, AGL-1008). ts/dns/record-value.ts parses a value into its RDATA fields or refuses it naming the field, and every path uses it: the embedded server registration of stored and configured records, createRecord, updateRecord, the Cloudflare write and import composition, the provider migration plan and its value comparison, migrateDomain, and the mail DNS MX check. Fields are split on runs of spaces and tabs the same way on write and on load. MX is <0-65535> <exchange>, with the RFC 7505 null MX 0 .; SOA is two names and five numbers from 0 to 4294967295; A is a dotted IPv4 address; AAAA is exactly what smartdns 11.1.0 encode_aaaa encodes: eight groups of 1 to 4 hex digits, or at most one :: that fills them up to eight (also next to eight written groups), with no mixed IPv4 tail (::ffff:192.0.2.1) and no zone id, which smartdns does not encode; CNAME and NS are one name. A name may end with a dot and has no empty label, no label over 63 octets and no more than 253 octets; its characters are not restricted. TXT is taken as it is, CAA as non-empty text. Any other type (SRV included) is refused by its type. Up to 34.2.0 a write stored any value string in a dcrouter-hosted zone, and the runtime split an MX on one space: an MX stored as a bare exchange became { preference: NaN, exchange: undefined }, smartdns refused it, and the whole RRset answered SERVFAIL.
    • updateRecord checks the whole record as it would be after the update (DNS): a new value against the stored type, a new type against the stored value, and a stored bad value on an update that does not change it.
    • A stored record the runtime cannot encode is not served, and is named (DNS): at start and on every RRset refresh it is left out with one error log line per record id, and the rest of its RRset answers NOERROR (an RRset of only such records is NODATA), never SERVFAIL for all of it. It is in getInvalidDnsRecords. A record with TTL 1 still loads and answers with TTL 1. The startup line counts only the records it registered and names the refused ones apart (registered N dcrouter-hosted DNS record(s) from DB; not serving M record(s) …); up to 34.2.0 it counted every stored row.
    • The Cloudflare import refuses a record value the shared owner refuses, for every type (DNS); up to 34.2.0 it checked only the MX shape. It never refuses a value smartdns encodes. One refused record fails the whole listing of its zone, naming the record (… has invalid <type> <field> for <name> (record <provider id>): <reason>), and with it everything that lists that zone at Cloudflare: syncDomain answers Provider list failed: …, a domain import keeps the domain without its records (a warn line), a migration into that zone, ACME DNS-01 challenge checks and testing DNS writes there fail. Fix the record at Cloudflare.
    • The mail DNS MX check uses the shared owner (email, AGL-1008). An observed null MX 0 . and an exchange with characters outside [a-z0-9-] (such as _) are now conflict (an unexpected value) instead of invalid; a value the owner refuses (a bare exchange) stays invalid.
    • migrateDomain refuses, in both directions, a zone with a record whose value dcrouter cannot encode, naming each one, before it reads or writes the target (DNS); the provider migration plan refuses such a record as a conflict by name, and the provider-to-dcrouter commit checks the records it re-reads in its transaction again.
    Downloads