desktop: route wildcard zones to a local DNS stub
A hosts file cannot express a wildcard, so '*.youtube.com' was only ever the
base domain plus the guessed labels. The patterns the server now hands over
as unexpandable are answered by a stub on 127.0.0.1, and the helper owns one
systemd-resolved drop-in that points just those zones at it:
* the stub is listening before the drop-in is written, and the drop-in is
removed before the stub stops, so a zone's queries never land on a dead
port;
* stub and route live as long as the app, not the interval: between
intervals they forward everything, instead of restarting resolved (and
dropping its cache) on every pomodoro;
* a crashed run leaves a world-readable drop-in the next start adopts, and
the stub comes back before anything else;
* masks with nothing routable after their last '*' ('*gram*') are reported
as unenforceable, not silently dropped.
The Blocker page now reports what is really in place — the hosts region, the
routed zones, and the masks this machine cannot do — rather than the whole
expansion. 39 desktop tests, including the ordering invariants.
This commit is contained in:
12 files changed
+1695
-29
No files matched your search
@@ -289,19 +289,52 @@ class Controller(QObject):
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def blockedDomains(self) -> list[str]:
|
||||
"""Every hostname the policy wants blocked, server-expanded.
|
||||
|
||||
This is the *intent*: what the rules add up to. What is actually in this
|
||||
machine's hosts file is [appliedDomains] — reporting this one as the
|
||||
state was the page lying about a machine it had not touched.
|
||||
"""
|
||||
return [str(item) for item in self._policy.get("all_domains", [])]
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def appliedDomains(self) -> list[str]:
|
||||
"""What is really inside the managed region of `/etc/hosts` right now."""
|
||||
return self._blocker.blocked_domains
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def routedZones(self) -> list[str]:
|
||||
"""DNS zones currently pointed at this machine's local resolver stub."""
|
||||
return list(self._blocker.routed_zones)
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def unroutableDomains(self) -> list[str]:
|
||||
"""Masks no machine can enforce: no DNS zone can be routed for them."""
|
||||
if not self._policy.get("active"):
|
||||
return []
|
||||
return list(self._blocker.unroutable)
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def blockedApps(self) -> list[str]:
|
||||
return [str(item) for item in self._policy.get("all_apps", [])]
|
||||
|
||||
@pyqtProperty("QVariantList", notify=blockerChanged)
|
||||
def invalidExpansions(self) -> list[str]:
|
||||
"""Subdomain expansions the server refused, e.g. `not a label`.
|
||||
|
||||
Worth a banner rather than a log line: a mistyped label silently reduces
|
||||
every `*.site` mask to the bare domain, which looks like "blocking is on
|
||||
and does nothing".
|
||||
"""
|
||||
return [str(item) for item in self._policy.get("invalid_expansions", [])]
|
||||
|
||||
@pyqtProperty(bool, notify=blockerChanged)
|
||||
def helperInstalled(self) -> bool:
|
||||
return self._blocker.helper_installed
|
||||
|
||||
@pyqtProperty(str, notify=blockerChanged)
|
||||
def blockerError(self) -> str:
|
||||
return self._blocker.last_error or ""
|
||||
return self._blocker.error or ""
|
||||
|
||||
@pyqtProperty(bool, notify=miniChanged)
|
||||
def miniVisible(self) -> bool:
|
||||
@@ -510,7 +543,8 @@ class Controller(QObject):
|
||||
elif key in ("mini_timer_corner", "mini_timer_margin"):
|
||||
self.miniPlacementChanged.emit()
|
||||
elif key == "enforce_blocking" and not value:
|
||||
self._blocker.release()
|
||||
# Takes the DNS route down with it, in the safe order.
|
||||
self._blocker.shutdown()
|
||||
self.configChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -781,14 +815,14 @@ class Controller(QObject):
|
||||
# ---- shutdown ----------------------------------------------------------
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""Hand everything back: inhibits, DND, and the hosts file."""
|
||||
"""Hand everything back: inhibits, DND, the hosts file and the DNS route."""
|
||||
self._tick.stop()
|
||||
self._idle_check.stop()
|
||||
self._screensaver.release()
|
||||
self._dnd.release()
|
||||
self._launcher.clear()
|
||||
try:
|
||||
self._blocker.release()
|
||||
self._blocker.shutdown()
|
||||
except Exception: # pragma: no cover
|
||||
log.exception("could not clear the block on exit")
|
||||
self._thread.quit()
|
||||
|
||||
Reference in new issue
Block a user