Label DNS policy redirects accurately
build / build-push (push) Successful in 30s

The query log classified every non-drop blocklist match as a block, even when an RPZ A or CNAME rule returned a successful redirect. Record redirect results with their own action so NOERROR address overrides are not presented or counted as blocked. Expose the new action in the Logs filter.
This commit is contained in:
mats committed 2026-09-28 15:48:07 +02:00
1 parent 87b6946ab7
commit 98b65945f6
4 files changed
+18 -4

No files matched your search

+9
View File
@@ -368,6 +368,13 @@ func (e *Engine) Handle(ctx context.Context, req Request) Response {
res.Duration = time.Since(start)
e.finish(req, res, start, decision)
return res
case blocklist.ActionRedirect, blocklist.ActionCNAME:
res.Msg = e.finalize(e.blockedMsg(q, blocked), q, req)
res.Action = qlog.ActionRedirect
res.BlockSource = blocked.Source
res.CacheHit = false
e.finish(req, res, start, decision)
return res
default:
res.Msg = e.finalize(e.blockedMsg(q, blocked), q, req)
res.Action = qlog.ActionBlock
@@ -502,6 +509,8 @@ func outcomeFor(res Response) string {
return metrics.OutcomeCache
case qlog.ActionBlock:
return metrics.OutcomeBlocked
case qlog.ActionRedirect:
return metrics.OutcomeLocal
case qlog.ActionError, qlog.ActionDrop:
return metrics.OutcomeError
case qlog.ActionRefused:
+5 -3
View File
@@ -28,6 +28,8 @@ const (
ActionAllow Action = "allow"
// ActionBlock is a blocklist hit.
ActionBlock Action = "block"
// ActionRedirect is a blocklist rewrite to an address or CNAME target.
ActionRedirect Action = "redirect"
// ActionCached is a cache hit.
ActionCached Action = "cached"
// ActionError is a SERVFAIL or other failure.
@@ -246,11 +248,11 @@ var (
)
// ShouldLog reports whether an entry with this action passes the sampling
// policy. Blocked, cached and error entries are always kept when their
// category flag is set; ordinary resolutions are sampled.
// policy. Blocked, redirected, cached and error entries are always kept when
// their category flag is set; ordinary resolutions are sampled.
func (l *Logger) ShouldLog(a Action) bool {
switch a {
case ActionBlock:
case ActionBlock, ActionRedirect:
return l.opts.LogBlocked
case ActionCached:
return l.opts.LogCached
+1 -1
View File
@@ -22,7 +22,7 @@ export type Health = 'ok' | 'degraded' | 'down'
/** One upstream server's circuit-breaker view. */
export type UpstreamHealth = 'up' | 'down' | 'degraded'
/** The decision the pipeline reached for a query (qlog.Action). */
export type LogAction = 'allow' | 'block' | 'cached' | 'error' | 'drop' | 'refused'
export type LogAction = 'allow' | 'block' | 'redirect' | 'cached' | 'error' | 'drop' | 'refused'
/** What a rule does to the name it matches (blocklist.ParseAction). */
export type DomainAction =
| 'allow'
+3
View File
@@ -54,6 +54,7 @@ const ACTION_OPTIONS = [
{ value: '', label: 'All actions' },
{ value: 'allow', label: 'Allow' },
{ value: 'cached', label: 'Cached' },
{ value: 'redirect', label: 'Redirect' },
{ value: 'block', label: 'Block' },
{ value: 'error', label: 'Error' },
{ value: 'drop', label: 'Drop' },
@@ -73,6 +74,8 @@ function actionTone(action: string): 'default' | 'ok' | 'warn' | 'danger' | 'inf
return 'default'
case 'cached':
return 'info'
case 'redirect':
return 'info'
case 'block':
return 'warn'
case 'error':