Skip to content

implement SOA serial increment logic in ReplaceRRSet - #111

Closed
fr6nco wants to merge 2 commits into
mainfrom
101-soa-serial-encodes-an-invalid-calendar-day-under-rapid-record-updates
Closed

fr6nco wants to merge 2 commits into
mainfrom
101-soa-serial-encodes-an-invalid-calendar-day-under-rapid-record-updates

Conversation

@fr6nco

@fr6nco fr6nco commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Summary

This change fixes SOA updates to always advance serials from the live PowerDNS value instead of relying on a generated value in the desired payload.
When updating an SOA record, the controller now fetches the current SOA RRSet, parses the existing serial, increments it by one, and applies that incremented serial in the PATCH request.
The behavior is intentionally limited to SOA updates, and all other record types continue using the existing update path with no extra read call.
It also adds info-level logs for serial fetch/increment/apply steps and adds focused unit tests for both SOA and non-SOA behavior.

Why

SOA serials must be monotonic for reliable zone transfer and propagation behavior, especially under rapid consecutive updates.
Incrementing from the authoritative current serial prevents invalid or stale serial progression during high-frequency changes.

Test Plan

  • Run go test ./internal/dns/pdns
  • Verify SOA updates fetch current serial and patch with serial + 1
  • Verify non-SOA updates do not trigger SOA serial fetch
  • Confirm existing PDNS package tests remain green

Notes

The SOA flow now emits informational logs for:

  • start of serial fetch
  • current serial discovered
  • incremented serial selected
  • incremented serial applied to outbound SOA value

@fr6nco

fr6nco commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@mattdjenkinson Would be great if in conjunction with this we could disable the SOA serial field on the UI. The Serial would be always "auto".

@0xmc 0xmc left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @fr6nco, two things.

  1. Without SOA-EDIT-API=NONE anything we do to the serial number with the controller is still going to also be updated by pdns. My testing shows this usually leads to the serial number being incremented by 2 on each change.
  2. As implemented there is a TOCTOU race in internal/dns/pdns/client.go, ReplaceRRSet:
    • Read at client.go:688 → nextSOASerial(...) → getPDNSRRSet(...) at client.go:739 (a HTTP GET)
    • Write at client.go:735 → applyRRSetPatch(...) → applyRRSetPatch at client.go:796 (a HTTP PATCH)

Perhaps a per-zone mutex if the critical section can be kept small enough?

@fr6nco
fr6nco requested a review from 0xmc August 27, 2026 07:46
@fr6nco

fr6nco commented Aug 27, 2026

Copy link
Copy Markdown
Contributor Author

true, with the next iteration the current SOA Serial is reused and increments by 1. However, incrementing it by more than 1 is not considered an issue since some implementation use timestamps

@mattdjenkinson

Copy link
Copy Markdown
Contributor

@mattdjenkinson Would be great if in conjunction with this we could disable the SOA serial field on the UI. The Serial would be always "auto".

So no create, edit or delete for these records?

@0xmc

0xmc commented Aug 27, 2026

Copy link
Copy Markdown

@fr6nco @mattdjenkinson we shouldn't be hiding SOA serial number, at least not for this reason. We need to bring serial number under the control plane's control and make it deterministic.

@0xmc

0xmc commented Aug 27, 2026

Copy link
Copy Markdown

@fr6nco your latest change addresses the +2 instead of +1 increment, but we're still dependent on pdns as the source of truth for the serial number, and we still have a TOCTOU around read and writing the serial number (currently around the pdns api, but the same would apply to etcd).

@vvoytovych

vvoytovych commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

@0xmc I'm picking #101 back up. Here is what I found and what we plan.

What I noticed:

  • Since v0.7.0 this change no longer reaches production. ReplaceRRSet is only called by the old per-name reconciler, which the manager no longer starts (Remove or document the unwired PowerDNS record set reconciler #86). The running path is EnsureRecordSet and buildRRSets, and it still sends today's date plus 01.
  • The drop comes from PowerDNS. When a PATCH carries an SOA, SOA-EDIT-API is applied to the serial in the request, not to the stored one, and the API does not refuse a lower serial. On 5.1.4 with LMDB, four record changes took a zone to 2026092905, and an SOA write that sent 2026092901 then stored 2026092902.
  • NONE is not a SOA-EDIT-API value in 5.1.4, and PowerDNS logs it as unknown. An empty soa_edit_api turns the edits off. PowerDNS then stores any serial it is sent, a lower one too, so the owner has to keep it moving up.

What we plan, in two steps:

  1. Now, the operator stops inventing a serial. It writes the SOA only when a field other than the serial changes, and then it sends the serial it just read, so DEFAULT moves it up. This is small, it is on the live path, and it ends the drop. It relies on every zone being DEFAULT, and all are: 125 in production, read live today, and 22 in staging, read from yesterday's snapshot.
  2. After the per-zone writer (docs: Propose one writer per zone for DNS records #201), the control plane owns the serial, as you asked. Each zone gets an empty soa_edit_api, and the per-zone pass writes the SOA with the next serial in the same PATCH as the changes. One writer per zone closes the read-then-write race without a lock.

Step 1 is your change moved onto the live path. You can carry it on this PR, or I can open one on top of main and close this one with a reference. Whichever you prefer.

Let me know if you have any major concerns. Thanks.

@vvoytovych

Copy link
Copy Markdown
Contributor

Closing in favour of #223, which stopped the serial moving backwards on the path the operator runs. It keeps the idea of this PR: a rewritten SOA keeps the serial PowerDNS holds when that is higher. The control plane owning the serial follows the per-zone writer (#201).

@vvoytovych vvoytovych closed this Oct 5, 2026
auto-merge was automatically disabled October 5, 2026 06:14

Pull request was closed

@vvoytovych
vvoytovych deleted the 101-soa-serial-encodes-an-invalid-calendar-day-under-rapid-record-updates branch October 5, 2026 06:15
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.

SOA serial encodes an invalid calendar day under rapid record updates + SOA record serial drop Issue

4 participants