Skip to content

Align the UI's shared-IP view with the runtime blocking decision (#759) - #776

Merged
kasnder merged 1 commit into
masterfrom
fix/shared-ip-ui-alive-rows
Aug 22, 2026
Merged

Align the UI's shared-IP view with the runtime blocking decision (#759)#776
kasnder merged 1 commit into
masterfrom
fix/shared-ip-ui-alive-rows

Conversation

@kasnder

@kasnder kasnder commented Aug 22, 2026

Copy link
Copy Markdown
Member

Closes #759. Implements option 1 from the triage: the runtime stays alive-only and the UI aligns to it.

ServiceSinkhole.log() called getQAName(uid, daddr, false) — full DNS history — while blockKnownTracker() called it with true, which applies the alive-row filter. Both are asking the same question ("is this IP shared, and whose is it?") and were answering it from different row sets, so the * shared-IP marker and the ALLOWED/BLOCKED text could disagree with what the blocker actually did.

Why this direction

Aligning the UI to the runtime keeps the log truthful about the decision that was actually taken, and leaves blocking strength untouched. The alternative — widening the runtime to full history — would treat more IPs as shared and therefore let more tracker traffic through to avoid collateral breakage. That is the wrong direction for a tracker blocker, and it would have been a silent weakening rather than a fix.

It also removes an inconsistency inside the UI itself: getAccessDns(), which backs the access list, is already unconditionally alive-filtered. log() was the outlier.

The parameter is gone

With both call sites passing the same value, boolean alive is dead configurability of exactly the kind that produced this bug, so it is dropped and the filter is unconditional. That makes the divergence impossible to reintroduce.

Behaviour change worth naming

When every DNS row for an IP has expired, log() now records no dname instead of a stale one, so that entry shows the raw IP. This is the intended consequence — the blocker saw nothing either — and the window is narrow: insertDns floors TTLs at the ttl preference (3 days) and cleanupDns() deletes expired rows every 12h, so "expired but not yet cleaned" is at most a 12h tail on a ≥3-day record, and any IP still being contacted has been re-resolved.

Deliberately out of scope

Two nearby lookups still read full history. Both are per-qname questions rather than shared-IP judgements, so they are left alone rather than folded in silently:

  • getDecloakedTracker(qname, dh)getAName(qname, false), reached only from the opt-in SNI research branch.
  • usage()getQName(uid, daddr), reached only when the non-default track_usage is on.

Verification

  • Full JVM suite: 280 tests, 0 failures across 40 suites.
  • New regression test expiredQnameDoesNotMakeIpLookShared: an IP with one alive and one expired qname must yield one row, so it cannot be marked shared.
  • Negative control — with the alive filter forced back to "" and the tests unchanged, exactly two fail: the new test with expected:<1> but was:<2> (the spurious shared-IP marker itself) and aliveFilterAppliesBeforeDedup. The three pure dedup/ordering tests pass either way, as they should.

The three pre-existing dedup tests moved from 1970-epoch timestamps to now-relative ones, since the filter is no longer optional.

🤖 Generated with Claude Code

ServiceSinkhole.log() classified connections from getQAName(..., false),
the full DNS history, while blockKnownTracker() used getQAName(..., true),
alive rows only. The two sides answered "is this IP shared, and whose is
it?" from different row sets, so the shared-IP marker and the
ALLOWED/BLOCKED text could contradict what the blocker actually did.

Align the UI to the runtime rather than the other way round: the log then
describes the decision that was actually taken, and blocking strength is
unchanged. Widening the runtime to full history would instead have treated
more IPs as shared, letting more tracker traffic through -- the wrong
direction for a tracker blocker.

With both callers passing the same value, the parameter is dropped so the
divergence cannot be reintroduced.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kasnder
kasnder merged commit b008a69 into master Aug 22, 2026
2 checks passed
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.

Shared-IP allowance differs between the UI (full DNS history) and runtime blocking (alive rows only)

1 participant