WebProbe (Dnsmasq Backend)

1. Overview

The WebProbe module provides lightweight, local-only URL evaluation for the dnsmasq edition of BastionGuard. Its role is to decide—quickly and deterministically—whether a given URL should be:

  • Allowed unchanged (normal navigation), or
  • Redirected to a local warning page hosted by LocalWarningServer

In this simplified build, WebProbe relies on:

  • A local host blocklist (file-based)
  • A short-lived hot cache for verdict reuse
  • A bank-domain allow policy via Backend::instance().isBankDomain()
  • Optional DNS-based signaling via AsyncDNSResolver::resolve_cached() (e.g., dnsmasq mapping to 127.0.0.2)

Remote reputation checks (e.g., Google Safe Browsing) are intentionally out of scope for this edition.


2. Responsibilities and Scope

This module is responsible for:

  • Extracting and normalizing the host component from a raw URL or host string
  • Loading a persistent blocklist from known locations
  • Evaluating hosts against explicit rules and wildcard subdomain patterns
  • Maintaining a short-lived in-memory cache to reduce repeated work
  • Building a deterministic redirect URL to a local warning server
  • Exposing a callback registration hook for GUI notification integration

3. Internal Data Model and Caching

3.1 Blocklist Storage

Blocked hosts are stored in-memory as:

  • static std::unordered_set<std::string> g_blocklist

Entries are normalized to lowercase and converted to ASCII (Punycode) when applicable (IDNA), ensuring consistent matching across internationalized domains.


3.2 Hot Verdict Cache (TTL)

To reduce repeated evaluation work during bursts of navigation requests, WebProbe uses a “hot cache” with a short TTL:

  • static std::unordered_map<std::string, std::pair<bool,uint64_t>> g_hot
  • static constexpr uint64_t TTL_MS = 15000 (15 seconds)

The cache key is the normalized host. The cached value contains:

  • blocked flag (true = redirect to warning)
  • expiration timestamp in milliseconds

Concurrency is handled using a shared mutex:

  • static std::shared_mutex g_hot_mtx

Reads use std::shared_lock, writes use std::unique_lock.


4. Time Base Utility

Time is computed using a monotonic clock to avoid wall-clock issues:

  • now_ms() uses std::chrono::steady_clock

This makes TTL expiration robust against system time changes.


5. Path Expansion and IDNA Normalization

5.1 User Path Expansion

Blocklist lookup supports user-relative paths through expand_user_path(rel), which prefixes the user home directory from:

  • Glib::get_home_dir()

5.2 IDNA ASCII Conversion

To properly support internationalized domain names (IDN), WebProbe converts hosts to ASCII using libidn2:

  • to_ascii_idna(host) uses idn2_lookup_ul()

If conversion fails, the original host string is returned unchanged.


6. Blocklist Loading and Matching

6.1 Blocklist File Locations

On initialization (and on demand reload), WebProbe loads blocked host patterns from the first available path among:

  1. Optional hint passed to the constructor
  2. User path: ~/.local/share/BastionGuard/blocklist_hosts.txt
  3. System path: /etc/BastionGuard/blocklist_hosts.txt

On success, the module logs the chosen source and the number of domains loaded. If no file is found, it logs that no blocklist is available.


6.2 Entry Parsing

While loading:

  • Whitespace is removed from each line
  • Empty lines are ignored
  • Domains are normalized:
    • IDNA ASCII conversion (Punycode)
    • Lowercasing

6.3 Wildcard Pattern Matching

WebProbe supports a limited wildcard syntax for subdomains:

*.example.com

Matching rules:

  • host == example.com matches
  • sub.example.com matches
  • Only the *. prefix form is recognized (no full glob patterns)

If a pattern does not start with *., matching falls back to exact equality.


7. URL Host Extraction

The extract_host(raw) routine normalizes the input and extracts a host using:

  • Whitespace trimming
  • Scheme stripping (http://, https://, ftp://)
  • Credential stripping (user@host)
  • Removal of path/query/fragment suffix
  • Numeric port stripping (:443)
  • Trailing dot removal
  • IDNA ASCII conversion and lowercasing

This ensures that the evaluation pipeline operates on a stable canonical host string.


8. Local Warning Redirect Builder

8.1 Redirect URL Format

When a URL is blocked, WebProbe redirects the navigation to a local warning endpoint:

http://127.0.0.2:<warning_port>/?target=<original_url>

The warning port is supplied via constructor (warning_port_). The original URL is minimally encoded (spaces mapped to %20).


9. Core Decision Logic

9.1 Main Entry Point

The decision engine is implemented by:

  • check_url(url)

It returns:

  • The original url if allowed, or
  • The local warning redirect URL if blocked

9.2 Evaluation Order

The evaluation pipeline is deliberately ordered for performance and policy correctness:

  1. Input validation (empty URL / empty host → allow)
  2. Hot cache lookup (return cached allow/block decision if not expired)
  3. Bank-domain allow (always allow via Backend::instance().isBankDomain(host))
  4. Blocklist match (explicit patterns → block)
  5. DNS signal check (cached DNS resolution returns 127.0.0.2 → block)
  6. Default allow (store allow verdict in hot cache)

9.3 DNS-Based Blocking Signal

Even if the host is not explicitly in the blocklist, WebProbe checks the local resolver cache:

AsyncDNSResolver::instance().resolve_cached(host)

If the cached IP equals 127.0.0.2, the URL is treated as blocked and redirected to the warning page. This enables an enforcement strategy where dnsmasq maps undesirable domains to a local sink address that triggers the UI warning page flow.


10. GUI Notification Integration

10.1 Callback Registration

The module exposes a callback hook for UI integration:

  • register_callback(NotifyCb cb)

The callback assignment is protected by an instance-level shared mutex:

  • std::shared_mutex mutex_ (member)

This allows GUI layers to register for notifications in a thread-safe manner. The simplified excerpt provided defines the registration mechanism; callback invocation is expected elsewhere in the full class implementation.


11. Blocklist Reload

Blocklists can be reloaded at runtime using:

  • reload_blocklist(path)

This re-executes the same multi-path lookup logic and repopulates the in-memory g_blocklist set.


12. Runtime and Security Considerations

  • Local-only enforcement: no remote reputation queries are executed in this edition.
  • Performance: the 15-second hot cache minimizes repeated matching and resolver lookups during navigation bursts.
  • Policy hardening: bank domains are explicitly allowed before any blocking check to avoid accidental checkout disruption.
  • IDN safety: IDNA conversion reduces homograph and normalization inconsistencies in matching logic.
  • Deterministic behavior: fixed evaluation order ensures predictable results across modules.
  • Encoding limitation: redirect encoding replaces spaces only; if full URL encoding is required, a dedicated encoder should be introduced.
  • Thread safety: shared mutexes protect hot cache access; blocklist access is protected by a standard mutex.