dnsmasq
dnsmasq
¶
Per-container dnsmasq lifecycle: config generation, launch, and cleanup.
dnsmasq runs inside the container's network namespace (via nsenter),
listens on 127.0.0.1:53, and uses --nftset to auto-populate nft
allow sets on every DNS resolution. This handles IP rotation that
static pre-start resolution cannot.
This module is the single owner of dnsmasq config format and CLI args.
has_nftset_support(runner)
¶
Return True if the installed dnsmasq supports --nftset.
Parses dnsmasq --version compile-time options for the nftset
feature flag. Returns False if dnsmasq is not installed or its
output contains no-nftset (explicitly disabled).
Source code in src/terok_shield/core/dnsmasq.py
nftset_entry(domain)
¶
Generate a dnsmasq nftset config line for a domain.
Maps A records to the IPv4 allow set and AAAA records to the IPv6 allow set. dnsmasq automatically matches the domain and all its subdomains.
Example::
nftset=/github.com/4#inet#terok_shield#allow_v4,6#inet#terok_shield#allow_v6
Source code in src/terok_shield/core/dnsmasq.py
generate_config(upstream_dns, domains, pid_path, *, log_path=None)
¶
Generate a complete dnsmasq configuration.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
upstream_dns
|
str
|
Upstream DNS forwarder (pasta or slirp4netns address). |
required |
domains
|
list[str]
|
Domain names for |
required |
pid_path
|
Path
|
Path for the dnsmasq PID file. |
required |
log_path
|
Path | None
|
If set, enable query logging to this file (for |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If upstream_dns is not a valid IP address. |
Source code in src/terok_shield/core/dnsmasq.py
launch(runner, pid, state_dir, upstream_dns, domains)
¶
Generate config and launch dnsmasq in the container's network namespace.
dnsmasq daemonizes and writes its PID to the state directory.
If dnsmasq lacks --nftset support, it will fail immediately with
a clear "bad command line options" error (fail-closed).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runner
|
CommandRunner
|
Command runner for subprocess calls. |
required |
pid
|
str
|
Container PID (for nsenter). |
required |
state_dir
|
Path
|
Per-container state directory. |
required |
upstream_dns
|
str
|
Upstream DNS forwarder address. |
required |
domains
|
list[str]
|
Domain names for nftset auto-population. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If dnsmasq fails to start or PID file is not written. |
Source code in src/terok_shield/core/dnsmasq.py
kill(state_dir)
¶
Kill dnsmasq for a container (best-effort cleanup).
Reads the PID from the state directory and sends SIGTERM. Silently ignores missing PID files, stale PIDs, and permission errors. Verifies PID identity before signaling to avoid killing unrelated processes.
Needed because dnsmasq runs in the host PID namespace (we only
nsenter -n into the network namespace). When the container stops,
podman kills container-PID-namespace processes, but dnsmasq is NOT
among them — it becomes an orphan with a broken network socket.
Source code in src/terok_shield/core/dnsmasq.py
reload(state_dir, upstream_dns, domains)
¶
Regenerate dnsmasq config and signal the daemon to reload.
Sends SIGHUP to the running dnsmasq, which re-reads its config file. No-op if dnsmasq is not running (PID file absent).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state_dir
|
Path
|
Per-container state directory. |
required |
upstream_dns
|
str
|
Upstream DNS forwarder address. |
required |
domains
|
list[str]
|
Updated domain names for nftset auto-population. |
required |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If dnsmasq PID exists but the process is gone (stale PID). The caller should log this — it means the container's DNS is broken. |
Source code in src/terok_shield/core/dnsmasq.py
add_domain(state_dir, domain)
¶
Append a domain to the live.domains file.
Writes to live.domains (not profile.domains) so that
runtime additions survive container restarts without overwriting
the profile-derived domain list.
Returns True if the domain was added, False if already present in the merged domain set (profile + live - denied).
Source code in src/terok_shield/core/dnsmasq.py
remove_domain(state_dir, domain)
¶
Remove a domain by adding it to the denied.domains file.
Writes to denied.domains so the denial persists across
dnsmasq reloads. Also removes from live.domains if present.
Returns True if the domain was removed, False if not found in the merged domain set.
Source code in src/terok_shield/core/dnsmasq.py
read_domains(domains_path)
¶
Read and normalize domain names from a domains file.
Validates and lowercases each entry so comparisons with
add_domain()/remove_domain() are consistent.
Invalid entries are silently skipped.
Source code in src/terok_shield/core/dnsmasq.py
read_merged_domains(state_dir)
¶
Compute effective domains: (profile + live) - denied.
Returns a deduplicated, stable-order list.
Source code in src/terok_shield/core/dnsmasq.py
write_resolv_conf(pid, nameserver=DNSMASQ_BIND)
¶
Overwrite the container's resolv.conf to point to dnsmasq.
Safety measure backing up the --dns podman flag. Writes directly
to /proc/{pid}/root/etc/resolv.conf from the host side.
Raises:
| Type | Description |
|---|---|
ValueError
|
If pid is not a numeric string or nameserver is not a valid IPv4 or IPv6 address. |