Skip to content

Add root DNS hijacker support#27

Draft
Mainkill1 wants to merge 108 commits into
betafrom
feature/root-dns-hijacker
Draft

Add root DNS hijacker support#27
Mainkill1 wants to merge 108 commits into
betafrom
feature/root-dns-hijacker

Conversation

@Mainkill1

@Mainkill1 Mainkill1 commented Jun 26, 2026

Copy link
Copy Markdown
Owner

DNS capture, daemon reliability, diagnostics, logging, blocklists, profiles, and dashboard controls. I kept this as a draft because it is large enough that it will be easier to review and test in sections instead of treating it like a small patch.

This PR brings the root DNS hijacker work into the beta branch for review. It adds a native DNS filtering daemon, root DNS traffic redirection, app-facing DNS controls, live query visibility, blocklist import/update handling, diagnostics, repair actions, profile-aware policy, per-app/per-network capture rules, cache tuning, SafeSearch enforcement, and several daemon-side performance and reliability improvements.

Recommended Review Plan

Because this is a lot of change at once, the easiest way to manage it may be to keep this as a draft and review by subsystem. If you want an even cleaner integration path, a temporary branch such as stupid-large-changes or dns-integration-pass would let the work be merged and tested there first before it is promoted into the normal beta flow.

A practical review order would be:

  1. Native daemon and root redirect integration
  2. Daemon lifecycle, reload, watchdog, repair, pause/resume, restart, and boot persistence
  3. Blocklist import and scheduled update handling
  4. Rules preferences and stored DNS policy
  5. Profile-aware DNS policy behavior
  6. Live query viewer and query-to-rule workflow
  7. Per-app and per-network capture/bypass rules
  8. Daemon cache, exact indexes, suffix tries, SafeSearch, and dynamic rule storage
  9. Diagnostics, status, health, logs, and data dump output

What Was Broken Or Missing

AFWall handled firewall rules, but DNS traffic still followed the normal resolver path. That meant the app could not reliably enforce domain-level allow/block policy, inspect DNS decisions, or prove that DNS traffic was actually passing through an AFWall-controlled root path.

The app also lacked a dedicated root daemon for DNS processing, daemon health reporting, one-click DNS repair, pause/resume/restart controls, live DNS query viewing, query-to-rule actions, scheduled DNS blocklist updates, split DNS routing, upstream benchmarking, and profile-specific DNS policy storage.

Large DNS rule sets needed stronger daemon behavior as well. The daemon now prepares rule storage, exact indexes, suffix tries, cache memory, and resolver routing before swapping the active generation. Failed reloads leave the last working DNS config running instead of partially replacing it.

Found Bugs And Reliability Fixes

Root DNS capture needed to be managed from the same firewall apply, purge, repair, and boot restore paths. The new manager flow starts or refreshes the daemon before redirects are installed, tears redirects down when DNS capture is disabled, and keeps IPv4/IPv6 redirect handling together so stale DNS interception is not left behind.

The daemon supervisor startup path was corrected so the daemon receives the required config argument and waits for the control socket before reporting readiness. This avoids a class of failures where the app thinks DNS protection is active but the native daemon is not ready to answer local control or DNS traffic.

Repair, reload, stop, restart, pause, and resume actions now route through the DNS manager instead of scattered command fragments. Pause disables DNS capture, removes redirect chains, stops the daemon, and updates scheduled blocklist behavior. Resume restores the setting, prepares daemon files, starts the supervisor, reinstalls redirects, and restores boot persistence. If a pause or resume root command fails, the previous enabled setting is restored so the UI does not drift away from device state.

The nftables fallback was added for devices where iptables redirect setup is unavailable or incomplete. DNS repair, boot restore, and purge handling account for both the daemon and redirect layer.

Accepted DNS TCP clients and daemon control clients now have explicit socket timeouts. A slow or partial client can no longer hold the daemon event loop indefinitely, and timeout counters are exposed in status and health output.

Cache reload behavior was tightened so cache entries survive rule-only reloads but are cleared when resolver-affecting settings change. That avoids unnecessary cache loss while preventing stale answers from being carried across upstream, split DNS, cache-size, or policy changes that alter resolution behavior.

Notable Performance Increases

Exact allow and block rules now use daemon-side indexes built during config load. The exact-rule index is allocated per loaded generation and sized from the loaded rule count, so large rule sets are no longer capped by one fixed static table.

Suffix allow and block rules are compiled into reverse-label tries. Runtime suffix matching walks the domain by label instead of scanning every suffix rule, keeping wildcard checks bounded by domain depth rather than list size.

Exact allow, suffix allow, exact block, and suffix block rule storage now grows dynamically while config is loaded. Large compiled blocklists are limited by available memory and reload validation instead of fixed array limits. Allocation failures reject the new generation and keep the previous daemon config active.

Cache memory is dynamically sized from configuration, supports disabled-cache mode, and reports hit/miss/store/expiry/eviction counters. Cached responses age their TTLs before reuse, zero-TTL answers are not cached, and the cache evicts least-recently-used entries instead of walking a fixed slot order.

Upstream forwarding reuses connected UDP sockets per config generation, validates response IDs, tracks stale upstream replies, retries truncated UDP replies over TCP, precompiles numeric upstream addresses during config load, and exposes upstream benchmarking to compare resolver behavior from the daemon side.

The DNS UDP read path drains bursts more effectively so queued DNS packets are handled without waiting for another event-loop wakeup. This should reduce latency spikes during short bursts of DNS traffic.

App Improvements

The Log Hub now includes a DNS Protection dashboard with daemon status, redirect status, health details, query access, blocklist update, pause/resume, restart, and repair controls. The pause/resume button follows the current DNS capture setting and refreshes the dashboard after each action.

A DNS query viewer was added to show recent daemon decisions, support history search/export, and let a user turn observed domains directly into exact, suffix, or temporary allow/block rules.

Rules preferences now include controls for enabling DNS hijacking, choosing upstream DNS servers, selecting upstream protocol behavior, defining split DNS routes, configuring strict/fail behavior, tuning cache size, controlling SafeSearch, and managing exact/suffix/temporary allow and block rules.

Per-app capture and bypass controls were added with UID-based root rules. Per-network capture and bypass controls were added with interface-pattern matching such as wlan+, rmnet+, and tun+. This lets DNS interception be scoped in the root redirect layer instead of requiring the app to inspect traffic after the fact.

DNS policy can follow the active firewall profile. Profile overrides can carry upstreams, split routes, app/network capture policy, cache size, fail behavior, strict mode, timeout, rules, temporary rules, SafeSearch behavior, blocklist URLs, and scheduled update settings.

Blocklists And Imports

DnsBlocklistManager adds staged import and compilation for common DNS list formats: hosts files, domain-only lists, Pi-hole style lists, basic AdBlock-style entries, pasted text, JSON backup bundles, and configured remote URLs.

Imports are staged before activation. If parsing or writing fails, the previous compiled list stays in place. Compiled exact and suffix rule files are referenced by the daemon config so request-time DNS evaluation does not repeatedly parse source list formats.

Scheduled blocklist updates use Android's alarm path. The receiver runs the same staged update flow as the manual update action, marks rules stale on success, reloads the daemon when DNS capture is enabled, and records failures without replacing the last working compiled list.

Diagnostics And Logging

Diagnostics and data dump output now include daemon status, daemon health, upstream probe results, cache/resource stats, suffix trie stats, dynamic exact index sizes, temporary rule counts, profile DNS state, split route count, blocklist update metadata, UID/interface capture policy counts, control socket responses, recent daemon logs, redirect rule checks, boot script state, and root boot hook checks.

Daemon query logging now records richer metadata, including decision/action details, upstream information, protocol, cache status, latency, and UID/interface context where available. Persistent query logging was moved off the DNS request loop so file writes do not happen synchronously in the hot path.

Logging controls were added so live and persistent query logs can be managed from the app instead of being always-on behavior.

Files Of Interest

  • dnsdaemon/jni/afwall_dnsd.c adds the native daemon, DNS parsing/forwarding, cache, config reload, dynamic rule storage, exact-rule indexes, suffix tries, temporary rules, split routes, query logs, stats, health, and benchmark handling.
  • app/src/main/assets/dnsd/ contains the packaged daemon binaries.
  • app/src/main/java/dev/ukanth/ufirewall/dns/DnsHijackManager.java manages config generation, daemon lifecycle, root redirects, nft fallback, boot restore, repair, pause/resume, health/status parsing, profile DNS policy, query parsing, and diagnostics.
  • app/src/main/java/dev/ukanth/ufirewall/dns/DnsBlocklistManager.java handles blocklist imports, staged writes, compiled files, URL updates, and profile-specific blocklist storage.
  • app/src/main/java/dev/ukanth/ufirewall/activity/DnsQueriesActivity.java adds the live query viewer and query-to-rule actions.
  • app/src/main/java/dev/ukanth/ufirewall/activity/LogHubActivity.java exposes the DNS dashboard actions from Log Hub.
  • app/src/main/java/dev/ukanth/ufirewall/activity/DiagnosticsActivity.java and DataDumpActivity.java expose DNS daemon diagnostics and repair details.
  • app/src/main/java/dev/ukanth/ufirewall/preferences/RulesPreferenceFragment.java, app/src/main/java/dev/ukanth/ufirewall/util/G.java, and app/src/main/res/xml/rules_preferences.xml add the app-facing DNS settings.
  • app/src/main/java/dev/ukanth/ufirewall/broadcast/DnsBlocklistUpdateReceiver.java and OnBootReceiver.java handle scheduled blocklist refresh and reboot restoration.

Notes

This branch is intentionally draft-only for now. It is broad enough that review will be more productive by subsystem or commit sequence rather than as one flat diff.

Copy link
Copy Markdown
Owner Author

Follow-up update for fb09f4f8:

DNS Daemon Watchdog Recovery

This hardens the root-side DNS daemon supervisor so crash recovery is easier to trust and easier to inspect.

What was weak before:

  • The supervisor did restart the daemon in a loop, but repeated start calls could race with a sleeping watchdog and risk duplicate watchdog loops.
  • There was no persistent watchdog PID, restart counter, or last-exit record for diagnostics.
  • status only reported a simple running/stopped result, which made it hard to tell whether the daemon was healthy, stopped, or being watched for restart.

What changed:

  • The generated supervisor script now writes afwall_dnsd_supervisor.pid and uses it to avoid starting duplicate watchdog loops.
  • The watchdog loop restarts the daemon while the enabled marker exists, records daemon exits into afwall_dnsd_last_exit, and increments afwall_dnsd_restart_count when it has to recover from a daemon exit.
  • stop now clears the enabled marker, terminates both the daemon PID and watchdog PID when present, and removes the watchdog PID file.
  • status now reports daemon state, watchdog state, restart count, and last exit details.
  • Local DNS diagnostics now include the watchdog PID, restart count, and last-exit metadata so recovery behavior is visible in support dumps.

This moves the DNS filtering subsystem closer to the intended model where the Android app is only the management interface and the root service can continue supervising DNS filtering independently.

Copy link
Copy Markdown
Owner Author

Follow-up update for 6c25dae5:

DNS Daemon Resource Stats

This adds native daemon resource accounting to the control socket responses used by Diagnostics.

What was missing:

  • The DNS daemon already reported query counts, cache counters, latency, rule counts, health, and uptime.
  • The requested DNS subsystem also needs memory and CPU visibility so long-running daemon behavior can be inspected without relying on the Android app lifecycle.

What changed:

  • The daemon now reads its own /proc/self/status on demand and reports memory_rss_kb plus memory_hwm_kb.
  • The daemon now reads /proc/self/stat on demand and reports cpu_user_ms, cpu_system_ms, and cpu_total_ms.
  • These values are included in both status/stats and health control socket responses.
  • The counters are collected only when management commands are requested, so DNS request handling stays out of the proc-reading path.
  • Rebuilt packaged daemon binaries for all app asset ABIs so the Android package ships the updated native service.

This fills in the memory/CPU statistics portion of the service health requirements and gives diagnostics a better view of daemon resource usage over time.

Copy link
Copy Markdown
Owner Author

Follow-up update for d59fd9c1:

DNS Query History And Search

This adds persisted DNS query history access on top of the existing live daemon ring buffer.

What was missing:

  • The DNS query viewer only showed the daemon's in-memory recent ring through the logs control command.
  • The daemon already persisted query logs asynchronously to its log file, but there was no control-socket path or UI mode for reading that historical log back.
  • Searching historical DNS decisions was not available from the DNS query viewer.

What changed:

  • Added a native daemon history control command that tails the persisted daemon query log and returns the most recent matching entries.
  • Added history <filter> support in the daemon for case-insensitive substring filtering across persisted log rows.
  • The history command keeps only a bounded tail in memory while reading, so it does not need to load the entire log file into memory.
  • Added app-side parsing through DnsHijackManager.getHistoricalQueries() with command sanitization for history filters.
  • Updated the DNS query viewer with Live/History mode switching.
  • Added Search in the DNS query viewer; searching switches to History mode and requests filtered historical entries from the daemon.
  • Copy still exports the currently displayed query set, whether that is live data or filtered history.
  • Rebuilt packaged daemon binaries for all app asset ABIs so the Android package ships the updated native control command.

This moves the logging side closer to the requested live plus historical query workflow while keeping DNS processing inside the native service and keeping the Android app as the management interface.

Copy link
Copy Markdown
Owner Author

Follow-up update for f379738e:

DNS Query Export From Viewer

This fills in the export piece for the DNS query viewer itself.

What was missing:

  • The viewer could copy the current DNS query list to the clipboard.
  • It could show live queries, and now historical/search results, but there was no direct save-to-disk action from that screen.
  • Saving filtered history required leaving the query view and using broader log export behavior.

What changed:

  • Added an Export action to the DNS query viewer.
  • Export uses Android's ACTION_CREATE_DOCUMENT picker, so the user chooses where to save and the app does not need broad storage access.
  • The export writes the currently displayed query set, whether that is live data, historical data, or filtered historical search results.
  • The default filename includes a timestamp, for example afwall-dns-queries-YYYY-MM-DD-HH-mm-ss.log.
  • If no document picker is available, the viewer falls back to copying the same query dump to the clipboard and tells the user what happened.

This completes the live/history/search/export loop for DNS query logs in the management UI while keeping DNS processing and persisted query data owned by the native daemon.

Copy link
Copy Markdown
Owner Author

Follow-up update for ed41abbf:

DNS Blocklist Paste Import

This adds clipboard/manual paste as another DNS blocklist source.

What was missing:

  • Blocklists could be imported from files and configured URLs.
  • The requested blocklist workflow also calls for clipboard paste support, which is useful for quick copied hosts, domain, Pi-hole, or AdBlock-style snippets.

What changed:

  • Added DnsBlocklistManager.importFromText() so pasted text uses the same parser as file and URL imports.
  • Pasted entries go through the same normalize, deduplicate, validate, staged activation, metadata, backup, and rollback-compatible pipeline as other blocklist sources.
  • Added a Paste DNS blocklist action to Rules preferences under Root DNS Filtering.
  • The paste dialog preloads current clipboard text when available, then activates the compiled list and reloads the daemon through the existing blocklist result flow.

This fills in the clipboard paste import source while preserving the existing atomic blocklist activation behavior.

Copy link
Copy Markdown
Owner Author

Improved DNS daemon listener reliability for Android IPv4/IPv6 capture.

The daemon previously used a single IPv6 listener with IPV6_V6ONLY disabled, relying on the device to accept IPv4-mapped traffic on that socket. That is a compact approach, but it can create a false-positive readiness state on Android builds that do not honor dual-stack behavior consistently: the daemon can report a listener while IPv4 redirected DNS traffic still cannot reach it.

What changed:

  • The daemon now binds explicit IPv4 UDP and TCP listeners.
  • It also binds explicit IPv6 UDP and TCP listeners when the device allows it.
  • IPv4 UDP/TCP listeners are required for startup because IPv4 DNS capture is always part of the redirect path.
  • IPv6 listener state is reported separately instead of being hidden behind a dual-stack assumption.
  • The control socket status/health/validate responses now expose udp_listener_v4, udp_listener_v6, tcp_listener_v4, and tcp_listener_v6.
  • The Log Hub DNS dashboard now shows UDP/TCP v4 and v6 listener state separately.
  • Dashboard readiness requires IPv4 listeners, and also requires IPv6 listeners when AFWall IPv6 handling is enabled.
  • Rebuilt and repackaged the daemon binaries for arm64-v8a, armeabi-v7a, x86, and x86_64 so the APK contains the listener fix.

This makes readiness closer to the actual Android packet path: IPv4 redirects no longer depend on IPv6 dual-stack socket behavior, and missing IPv6 listener support is visible when IPv6 capture is expected.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for the DNS service branch:

Fixed nftables fallback restore generation. The previous fallback built each nft statement as a single quoted shell argument, and the chain definitions include braces and semicolons that need to be parsed by nft instead of the Android shell. On devices where iptables NAT is unavailable and the app falls back to nftables, that could prevent DNS redirect rules from being restored even though the service lifecycle work continued.

The restore path now builds the nft statements as a small script and feeds them through nft -f -. The table cleanup remains separate, then nft receives the table, chain, bypass, and UDP/TCP redirect rules in its own syntax. This also makes service-side failures more useful: boot/supervisor restore output now captures real nft parser/runtime errors, and the app-side service log bridge can pull those lines into the application diagnostics instead of leaving the fallback as a silent no-op.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for Android power handling around the DNS service:

Added a DNS service power-settings row in the Rules DNS section. The dashboard/diagnostics already report that the root daemon watchdog runs outside the normal app process, but app-side work can still be affected by Android battery optimization. That includes scheduled blocklist updates and syncing root-side service logs back into the app diagnostics.

The new row shows the current app power state when Android exposes it:

  • Battery optimized: warns that the root daemon can keep running, but scheduled DNS updates and app log sync may be delayed.
  • Not optimized: confirms that scheduled updates/log sync are less likely to be delayed.
  • Unsupported/unknown: reports that clearly instead of implying the app can verify it.

Clicking the row opens Android battery optimization settings, with a fallback to this app's settings page when the device does not expose the direct battery optimization screen. Each action path writes an application log entry, including unavailable settings, so this is visible later in diagnostics.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for Android Private DNS compatibility:

Added a DNS-section action to review Android Private DNS settings. The app already detected and logged that Private DNS/DoT can bypass root DNS capture because the redirect rules capture UDP/TCP port 53, not encrypted DNS transports. The missing piece was an actionable path from the DNS service controls.

What changed:

  • Added a Review Android Private DNS row under the experimental DNS filtering controls.
  • The row summarizes whether Private DNS is currently a bypass risk, clear, unsupported, or unreadable.
  • Clicking it tries the direct Private DNS settings action first, then falls back to Android's internet connectivity panel, then general wireless settings.
  • Each path logs the current Private DNS mode/specifier/bypass state into the application log, including unavailable settings, so diagnostics show what the user attempted and what Android reported.

This does not claim that encrypted DNS can be captured by the root redirect. It makes that Android limitation visible and gives the user a direct route to disable or review Private DNS when they need full port-53 DNS capture through the daemon.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for DNS redirect backend reliability:

Tightened the install and repair path so DNS capture is not treated as usable until one redirect backend is actually complete.

What was wrong:

  • The nftables fallback only checked whether the custom iptables chain existed. A partial iptables install could create the chain but miss parent OUTPUT/PREROUTING jumps or miss UDP/TCP redirect targets, which meant nft fallback would not run even though traffic was not fully captured.
  • The direct service repair path used raw iptables commands. On devices with unavailable or partially broken iptables NAT, a failure could stop the repair batch before nft fallback had a chance to install rules.
  • Stale nftables DNS tables could remain after a later iptables install became healthy, creating confusing mixed-backend state.

How it works now:

  • The fallback condition checks the same complete redirect shape the dashboard expects: OUTPUT UDP/TCP jumps, PREROUTING UDP/TCP jumps, and UDP/TCP REDIRECT targets in both custom chains.
  • nft fallback runs when that iptables shape is incomplete, not merely when a chain is missing.
  • When iptables is complete, the fallback command removes the stale nft DNS table for that address family.
  • The repair path now tolerates iptables setup failures long enough to attempt nft fallback.
  • After normal apply and manual repair, a verification command fails the operation if neither iptables nor nft has a complete DNS redirect backend installed.
  • Boot restore now uses the same completeness check before deciding whether to restore nft fallback, and also cleans stale nft tables when iptables restore is complete.

This makes the app less likely to report DNS protection as installed when traffic still cannot reach the daemon, especially on Android builds where iptables exists but NAT/REDIRECT behavior is incomplete or backed by nftables.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for DNS boot restore behavior:

Fixed boot persistence so it honors the app's IPv6 setting. The generated root boot script previously restored IPv4 and IPv6 DNS redirects every time it ran. That could re-install IPv6 DNS capture at boot even when AFWall+ IPv6 handling was disabled in the app, making boot-time behavior diverge from normal apply/repair behavior.

What changed:

  • The boot script now captures the current app IPv6 setting when it is generated.
  • IPv4 DNS redirects still restore as before.
  • IPv6 DNS redirects restore only when AFWall+ IPv6 handling is enabled.
  • When IPv6 restore is skipped, the boot log records that choice so diagnostics explain why only IPv4 was restored.
  • DNS local diagnostics now include boot_restore_ipv6_enabled so exported diagnostics show which IPv6 behavior the installed boot script was generated with.

Cleanup remains defensive and can still remove stale IPv6 DNS redirect state; this only prevents disabled IPv6 capture from being reinstalled at boot.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for DNS setting changes taking effect immediately:

Fixed the active-service behavior when users change settings that affect root redirect rules. Previously, changes such as the local daemon port, captured/bypassed UIDs, or captured/bypassed interfaces only marked firewall rules stale. That meant the saved UI value could differ from the running daemon and installed NAT redirects until the user manually repaired or reapplied rules.

What changed:

  • Redirect-affecting DNS preferences now trigger a DNS service repair/reinstall when DNS protection is enabled.
  • The affected preferences are daemon port, capture UIDs, bypass UIDs, capture interfaces, and bypass interfaces.
  • IPv6 enable/disable changes also trigger DNS redirect repair when DNS protection is enabled, so IPv6 DNS redirects are installed or removed to match the current app setting.
  • The repair path now restarts the daemon before reinstalling redirects, so daemon listener config and redirect target port stay aligned.
  • When IPv6 is disabled, repair removes stale IPv6 DNS redirect chains and the IPv6 nft fallback table.
  • Users get a specific success/failure toast for DNS redirect setting application, and the application log records the setting key that caused the reinstall.

Daemon-only settings still use the lighter reload path. Redirect-affecting settings now use the stronger root repair path because they change NAT behavior and cannot be made effective by a daemon reload alone.

Copy link
Copy Markdown
Owner Author

Update pushed for the root DNS service branch.

This adds a more reliable communication path from the root-owned DNS service back into AFWall's logs:

  • The app now seeds the supervisor, boot, and cleanup event log files during daemon preparation so AFWall has stable files to read back from.
  • Root lifecycle commands now repair those event log file permissions before supervisor actions, repairs, removals, and normal DNS protection apply flows. This covers cases where root recreates a service log file after the app was closed or after a service restart.
  • The log bridge now formats root-side epoch timestamps into readable timestamps, writes bridged service events into Application Errors, and also routes them through AFWall's in-process application logger so they appear in Application Log.
  • Opening Application Errors, opening Application Log, and exporting logs now all sync the DNS service event bridge first, so the latest supervisor/boot/cleanup events are included without needing a separate DNS-only refresh.
  • Query traffic is intentionally not copied into the base application log because it can be high volume and already has the DNS query view/export path.

This push also fixes another redirect-policy edge case found during the audit:

  • DNS profile presets and profile-policy save/clear actions can change effective capture and bypass UID/interface rules, so they now run the repair/reinstall path while DNS protection is enabled instead of only asking the daemon to reload.
  • The Use profile policy toggle is now treated as redirect-affecting for the same reason, keeping the daemon config, root redirect rules, and boot restore script aligned after bulk profile changes.

Copy link
Copy Markdown
Owner Author

Update pushed for DNS capture routing.

This changes the daemon recursion bypass from a broad root UID bypass to a daemon socket mark path:

  • The root DNS daemon now sets SO_MARK on its upstream sockets before connecting to resolvers.
  • AFWall DNS OUTPUT redirect chains now install a mark RETURN rule for that daemon mark first, so the daemon can reach upstream DNS without capturing its own packets.
  • If the device/kernel does not support the mark matcher, the installer falls back to the previous UID 0 RETURN rule so the service remains usable instead of breaking DNS with recursion.
  • Boot restore, direct repair, normal rule apply, iptables status checks, nft fallback rules, and diagnostics were updated to use the same mark-first behavior.
  • Dashboard readiness now only warns about root/system DNS bypass when status checks show the UID 0 fallback is actually installed.
  • The packaged daemon assets were rebuilt for armeabi-v7a, arm64-v8a, x86, and x86_64 from the updated native source.

Why this matters: the prior UID 0 bypass protected the daemon from recursive capture, but it could also allow other root-owned/system DNS traffic to skip the DNS service. The mark-based bypass narrows that exception to the daemon's upstream sockets, which is closer to the intended Android behavior of capturing normal UDP/TCP port 53 DNS while still keeping the daemon functional.

Copy link
Copy Markdown
Owner Author

Follow-up pushed for the DNS mark routing path.

The previous mark-based bypass narrowed the daemon recursion exception, but there was still one important usability gap: root rule install could test whether iptables accepted the mark matcher, but it could not prove the daemon had successfully applied SO_MARK to its own upstream sockets. If the matcher installed but SO_MARK failed inside the daemon, the daemon's upstream DNS could still recurse back into the local redirect.

This update adds a daemon-to-installer status contract:

  • The daemon now writes afwall_dnsd_mark.status after config load/reload with either supported mark=... or failed mark=... errno=....
  • Normal apply, repair, and boot restore now read that daemon status file before installing the recursion bypass.
  • If the daemon reports mark support, rules install the daemon mark RETURN first and still fall back to UID 0 if the mark matcher itself fails.
  • If the daemon reports mark failure or the status file is missing, rules install the UID 0 fallback immediately so DNS remains usable instead of risking recursive capture.
  • Dashboard details now show daemon mark support/failure counts, and diagnostics include the mark status file and control-socket mark fields.
  • The daemon control responses now expose daemon_socket_mark, socket_mark_supported, socket_mark_errno, and socket_mark_failures.
  • Rebuilt and packaged the daemon assets for all four supported ABIs again.

This keeps the tighter mark-only routing when the device actually supports it, while preserving the previous compatibility fallback on devices where daemon-side socket marking is not available.

Copy link
Copy Markdown
Owner Author

Updated the root DNS service branch with a service-boundary fix for daemon upstream traffic and diagnostics.

What changed:

  • Added a daemon-mark filter-table bypass for the root DNS daemon's upstream sockets. The daemon already marks its own upstream sockets to avoid NAT redirect recursion, but AFWall's normal OUTPUT filter path could still reject those marked packets. The new rule allows only packets carrying the daemon's 0xaf053 mark through the filter table, leaving normal app DNS capture unchanged.
  • Wired the daemon-mark filter bypass into full firewall apply, manual DNS repair, boot restore, service removal, and lifecycle cleanup paths so install/uninstall/reboot behavior stays consistent and stale rules are cleaned up.
  • Extended root redirect status checks and dashboard readiness so a missing daemon upstream filter bypass is reported directly instead of looking like a generic redirect failure.
  • Added root diagnostics output for the IPv4/IPv6 filter bypass rules, alongside the existing NAT chain output.
  • Updated app base-log messages for DNS enable/repair/resume/apply paths so they mention when daemon filter bypass work is queued.

Why this was needed:

The service runs outside the Android app process as a root-owned daemon. Redirect recursion prevention alone was not enough: once the daemon sent DNS to an upstream resolver, those packets returned from the NAT bypass into AFWall's normal OUTPUT filtering. In whitelist or strict configurations, root-owned daemon packets could still be blocked. The fix makes daemon upstream traffic identifiable by the daemon socket mark and allows that marked traffic through the filter layer without widening capture for regular applications.

Copy link
Copy Markdown
Owner Author

Added another service-usability fix for root command failure visibility.

What changed:

  • Root command stderr is now captured for both IPv4 and IPv6 root shell execution paths instead of being discarded by the libsuperuser callback.
  • Nonzero root command exits now write a concise failure record to the application error log, including the failing command, exit code, and a compact stdout/stderr tail when available.
  • Callback consumers such as DNS repair, apply, enable/disable, and redirect verification now receive stderr in lastCommandResult, which makes existing DNS lifecycle failure messages more useful.

Why this matters:

The DNS service depends on root operations outside the normal app process. If a cached root grant is revoked, an iptables/nft command fails, a generated script is not executable, or redirect verification fails, the app needs enough detail to explain what happened. Previously stderr could be lost, which made some failures look like a generic apply error. This keeps those failures visible in the base app log and in the existing DNS lifecycle error reporting without adding new UI surface.

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.

1 participant