Skip to content

Attribute shared-IP DNS evidence by recency, not alphabetically - #690

Merged
kasnder merged 1 commit into
masterfrom
claude/trackercontrol-android-655-9oowxd
Jul 28, 2026
Merged

Attribute shared-IP DNS evidence by recency, not alphabetically#690
kasnder merged 1 commit into
masterfrom
claude/trackercontrol-android-655-9oowxd

Conversation

@kasnder

@kasnder kasnder commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

Addresses part of #655 (DNS-based tracker blocking ambiguity).

DatabaseHelper.getQAName() — used by ServiceSinkhole.blockKnownTracker() and ServiceSinkhole.log() to attribute a shared IP to a hostname/tracker — deduped and ordered candidate qnames alphabetically (GROUP BY d.qname / ORDER BY d.qname). This meant the attributed hostname for an ambiguous shared IP was always whichever qname happened to sort first lexically, with no relation to which DNS resolution actually caused the current connection.

This PR changes the query to dedupe and order by recency instead: for each qname sharing a resource IP, only the most recently observed row is kept, and qnames are returned most-recent-first. The freshest DNS evidence is the one most likely tied to the connection actually being made right now, so this should make attribution (and therefore the derived tracker category / block decision) meaningfully more accurate without any schema change, migration, or new knob.

As covered in the issue discussion, this is intentionally the "low-risk partial improvement" half of #655 — it does not attempt genuine per-app DNS evidence (option 2 in the issue), since DNS queries are resolved through netd (uid 0) rather than the requesting app's uid, and netd's resolver cache means a same-network app can get a cache hit with no query at all — so uid-scoped DNS evidence would under-block rather than fix attribution. The uid-global limitation documented in AGENTS.md §4.4 stands.

Changes

  • DatabaseHelper.getQAName(): replaced the alphabetical GROUP BY/ORDER BY d.qname with a recency-based dedup (freshest row per qname via a correlated subquery) and ORDER BY d.time DESC. The alive TTL filter is preserved and applied consistently to both the outer query and the inner "pick freshest" subquery.
  • Added DatabaseHelperDnsAttributionTest covering: (1) a more-recently-resolved qname is returned ahead of an alphabetically-earlier one, and (2) repeated observations of the same qname (e.g. distinct CNAME targets) collapse to a single, freshest row.

Test plan

  • Verified the new SQL query's behavior against Python's bundled sqlite3 (recency ordering, dedup-to-freshest, and the alive filter all behave as intended) since this sandbox has no Android SDK to run Robolectric.
  • ./gradlew :app:testGithubDebugUnitTest --tests 'eu.faircode.netguard.DatabaseHelperDnsAttributionTest' (needs to run in CI/an environment with the Android SDK)

Generated by Claude Code

getQAName() previously deduped and ordered candidate qnames for a shared
IP alphabetically, so blockKnownTracker() and the traffic-log attribution
in ServiceSinkhole.log() always locked onto whichever qname sorted first
lexically, regardless of when it was actually resolved. Order by most
recent observation instead, so the freshest DNS evidence — the one most
likely to correspond to the connection actually being made — wins.
@kasnder
kasnder marked this pull request as ready for review July 28, 2026 07:48
@kasnder
kasnder merged commit 6c34ce1 into master Jul 28, 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.

2 participants