Keep list copies on disk, start from them, and stop logging address overrides

Found by rolling the resolver out to the real cluster.

BlockList objects have no path, deliberately, so the first rollout downloaded
the list at every start. The cluster DNS that the download needs is this very
resolver, which was not up yet, so the first compile failed after 14 seconds,
held the DNS listeners closed for that long, and left the pod with no ad
blocking at all. Three changes:

* Lists from cluster objects get a copy under the runtime's own data directory
  (data/lists/<name>), chosen by the runtime from the object's name, never by the
  object.
* The copy is used as the answer only while it is younger than the list's
  refresh interval, and otherwise the list is downloaded again, with the old copy
  as the fallback if that fails. Before, any copy on disk was treated as current
  for ever, so a list with a path was frozen at its first download. A source now
  ages from its file, an operator's "refresh now" forces a download, and a
  failed download keeps the stale copy.
* Startup loads only what is on disk (RefreshOffline), opens the listeners, and
  fetches stale or missing lists in the background. The resolver answers at once
  and the network is never on its critical path.

An address override answered as a redirect and so counted as a policy action:
with the access log logging redirects, every lookup of an internal name wrote a
line. Overrides from the managed source are local answers now; a redirect a list
imposes is still logged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
matsandClaude Sonnet 5.5 committed 2026-10-16 10:41:20 +02:00
1 parent 242bcd98d9
commit 0fdc828a10
8 files changed
+341 -11

No files matched your search

+12 -4
View File
@@ -314,11 +314,19 @@ func (a *App) startBlocklist() {
if snap := a.kubeSnap.Load(); snap != nil {
a.blocks.SetManagedRPZ(kubesource.RenderRPZ(snap.Overrides))
}
// Compile once before serving so the first queries are not unprotected.
if _, err := a.blocks.RefreshAll(context.Background()); err != nil {
a.markDegraded("blocklist: " + err.Error())
a.log.Warn("initial blocklist compile failed", "error", err)
// Compile once before serving, from what is on disk, so the first queries
// are not unprotected and the listeners do not wait for the network. Lists
// that are stale or have never been downloaded are then fetched in the
// background and swapped in when they arrive.
if st, err := a.blocks.RefreshOffline(context.Background()); err != nil || len(st.Errors) > 0 {
a.log.Info("some blocklists are not on disk yet; fetching them in the background", "lists", len(st.Errors), "error", err)
}
go func() {
if _, err := a.blocks.RefreshAll(context.Background()); err != nil {
a.markDegraded("blocklist: " + err.Error())
a.log.Warn("blocklist download failed", "error", err)
}
}()
}
// startLocalZones compiles the local master files.
+2 -2
View File
@@ -240,14 +240,14 @@ func (a *App) refreshBlocklists(ctx context.Context, names []string) error {
a.invalidateBlocklistCache()
go func() {
if len(names) == 0 {
if _, err := a.blocks.RefreshAll(ctx); err != nil {
if _, err := a.blocks.ForceRefresh(ctx); err != nil {
a.log.Warn("blocklist refresh failed", "error", err)
}
return
}
// The manager compiles everything in one pass, which is cheaper than
// a pass per name and keeps the compiled set internally consistent.
if _, err := a.blocks.RefreshAll(ctx); err != nil {
if _, err := a.blocks.ForceRefresh(ctx); err != nil {
a.log.Warn("blocklist refresh failed", "error", err, "names", names)
}
}()