resolver
resolver
¶
DNS resolution with timestamp-based caching.
Resolves allowlist domains via dig (getent fallback) and caches
the IPs so containers do not block on DNS at every start.
Two cache layers:
- per-container file (
cache_path): the view the nft ruleset reads. Scoped to one container, so a fresh container never reuses another's. - host cache (
host_cache_dir): shared across containers, keyed by the allowlist's content hash. The first task resolves; the rest read.
Domains resolve concurrently with a per-lookup timeout, so a batch costs about one lookup and one dead domain cannot stall startup. A batch that does run says so on stderr, before and after: the launcher is waiting on it, and a silent wait reads as a hang.
Every tier but dnsmasq-live uses this module at launch. On that tier
domains resolve on-demand at runtime via --nftset; this module then
handles raw IPs only.
logger = logging.getLogger(__name__)
module-attribute
¶
RESOLVE_TIMEOUT = 2
module-attribute
¶
Per-subprocess DNS budget in seconds.
Best-effort: an answer slower than this counts as no answer. The old 10s budget let one dead domain (times the getent retry) stall a start by ~20s.
MAX_RESOLVE_WORKERS = 16
module-attribute
¶
Upper bound on concurrent resolver subprocesses per batch.
DnsResolver(*, runner, host_cache_dir=None)
¶
Stateless DNS resolver — all persistence lives in the cache files.
Depends on a CommandRunner
for lookup-tool (dig/drill) and getent subprocess calls
and a host-level cache directory shared across containers.
Inject the command runner and the shared cache location.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
CommandRunner
|
Command runner used for all DNS subprocess calls. |
required |
host_cache_dir
|
Path | None
|
Cross-container cache directory; |
None
|
Source code in src/terok_shield/dns/resolver.py
resolve_and_cache(entries, cache_path, *, force=False, source_mtime=0.0)
¶
Resolve profile entries and cache the result.
Reads the per-container file first, then the shared host cache (materializing a hit into the per-container file), and only resolves when both miss.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
entries
|
list[str]
|
Domain names and/or raw IPs from composed profiles. |
required |
cache_path
|
Path
|
Per-container file the nft ruleset reads. |
required |
force
|
bool
|
Re-resolve even when a cache is younger than the one-hour freshness window. |
False
|
source_mtime
|
float
|
mtime of the authored policy; a per-container cache older than it is re-resolved even within the freshness window, so an edited allowlist takes effect on the next task start. The host cache ignores this — its content-hash key already makes it edit-aware. |
0.0
|
Returns:
| Type | Description |
|---|---|
list[str]
|
Resolved IPv4/IPv6 addresses combined with raw IPs/CIDRs. |
Source code in src/terok_shield/dns/resolver.py
resolve_domains(domains)
¶
Resolve domain names to IPs (A + AAAA), best-effort and concurrent.
Probes for a lookup tool once, then resolves every domain on a small thread pool — a batch costs about its slowest single lookup. Unresolvable domains are skipped with a warning; results are deduplicated in first-seen (input) order.