Local Warning Server (DNS Block Page)

1. Overview

The LocalWarningServer module implements a lightweight local HTTP/HTTPS server used to display a user-facing warning page when BastionGuard blocks access to a domain at the DNS layer (dnsmasq edition). It is designed to serve an HTML “block page” on a dedicated loopback IP and ports, while selectively allowing trusted banking/payment domains through a whitelist-based redirect.

The server is built on libsoup and runs its own GLib main loop in a dedicated thread. This ensures the application UI remains responsive while the warning endpoint is always available.


2. Responsibilities and Scope

This module is responsible for:

  • Binding a local HTTP listener (and optionally HTTPS) on a loopback address
  • Handling requests to the root path (/) and generating a block page dynamically
  • Extracting and normalizing the requested host/domain
  • Checking the requested host against a trusted whitelist
  • Redirecting trusted domains to external HTTPS targets (307)
  • Serving a localized HTML warning page for blocked domains

3. Default Network Configuration

Default settings are provided via compile-time macros (with fallback values):

  • BastionGuard_WARNING_DEFAULT_HTTP – default HTTP port: 81
  • BastionGuard_WARNING_DEFAULT_HTTPS – default HTTPS port: 444
  • BastionGuard_LOCAL_WARNING_IP – bind address: 127.0.0.2

The dedicated loopback IP avoids collisions with common local services bound to 127.0.0.1 and allows deterministic routing for the Secure Browser / DNS interception layer.


4. Domain and String Utilities

4.1 Normalization

The module uses simple normalization helpers to ensure stable matching and safe output:

  • to_lower() – lowercases all characters
  • clean_domain() – normalizes a domain by:
    • lowercasing
    • removing a leading dot
    • stripping www. prefix
    • removing explicit port suffix (e.g., :8080)
    • removing trailing dot

4.2 Host Extraction from Headers / URLs

To reliably derive the target domain, extract_host(raw) performs:

  • trim of leading/trailing whitespace
  • scheme stripping (http://, https://, ftp://)
  • credential stripping (user:pass@hosthost)
  • removal of path/query/fragment suffix
  • removal of numeric port suffix
  • trailing dot removal

This is used primarily to process the Host header, but also supports an alternate routing mode (see section 8.2).


4.3 HTML Escaping

The helper html_escape() performs minimal escaping to prevent HTML injection in the warning page:

  • &, <, >, ", ' are escaped
  • newlines are converted to <br/>

This is applied when rendering the blocked host within the HTML response.


5. Trusted Domain Whitelist

5.1 Data Sources

The trusted allowlist is loaded from two sources:

  • Official list: /usr/share/BastionGuard/data/bank/banks.json
  • User list: ~/.config/BastionGuard/whitelist.json

Both files are parsed using nlohmann/json. The loader supports multiple JSON schemas:

  • Top-level array of strings
  • Objects containing arrays under:
    • list
    • domains
    • whitelist

All loaded domains are normalized via clean_domain() before storage.


5.2 Cache and Lookup Semantics

The whitelist is cached in-memory using:

  • static std::unordered_set<std::string> g_trusted
  • static bool g_trusted_loaded

Lookups are performed by is_trusted_domain(host) and support both:

  • Exact matches (bank.example)
  • Parent domain matches (sub.bank.examplebank.example)

This enables trust inheritance for common banking subdomain structures.


6. Listener Setup and Port Binding

6.1 libsoup Server Creation

The server is created using:

  • soup_server_new()
  • g_object_set(..., "server-header", "BastionGuard-warning-server")
  • soup_server_add_handler(server_, "/", on_request, this, ...)

The handler is registered only for the root path (/), simplifying routing and ensuring all requests are served by a single policy engine.


6.2 IPv4 Bind Helper and Availability Handling

Binding is performed through listen_one(), which:

  • Creates a GInetAddress and GSocketAddress for the requested bind address
  • Calls soup_server_listen() with either HTTP or HTTPS options
  • On failure, logs a diagnostic message and clears the GError

Startup succeeds if at least one of the ports (HTTP or HTTPS) is available.


7. Optional HTTPS Support

7.1 TLS Certificate Loading

HTTPS is enabled only if both certificate and key file paths are provided. The module attempts to load TLS material via:

g_tls_certificate_new_from_files(cert_file, key_file, &err)

If TLS initialization fails, the module logs the error and continues with HTTP-only mode.


8. Runtime Model and Threading

8.1 Dedicated Main Loop

Once at least one listener is active, the module creates a GLib main loop:

  • g_main_loop_new()
  • g_main_loop_run() executed in loop_thread_

This isolates network I/O and request handling from the application’s UI thread(s).


8.2 Secure Browser Callback Mode (Path-Based Host)

The request handler supports an alternate invocation pattern where the Secure Browser calls:

http://127.0.0.2/<host>

When the incoming request host matches the bind IP (or is empty), the module derives the intended host from the request path by stripping the leading slash.


9. Request Handling and Policy

9.1 Host Resolution

The module resolves the requested domain using the Host header, normalized through extract_host(). If unavailable, a localized fallback label is used.


9.2 Trusted Domain Redirect

If the domain is trusted (whitelist hit), the server issues an external redirect to the HTTPS version of the domain:

  • Location header: https://<domain>/
  • HTTP status: 307 Temporary Redirect

This behavior ensures payment/banking targets are not interrupted by the DNS block-page layer while preserving a preference for HTTPS.


9.3 Block Page Rendering

If the domain is not trusted, the server generates and returns a localized HTML warning page. The page:

  • Uses an embedded CSS layout with a high-visibility warning theme
  • Displays the blocked domain (HTML-escaped)
  • Provides a “return to safety” action link
  • Indicates the DNS protection layer is active (dnsmasq)

Responses are sent as:

  • Content-Type: text/html
  • Status: 200 OK

10. Logging and Diagnostics

The module logs key events to standard output/error:

  • Whitelist load result and trusted domain count
  • Port binding failures (port unavailable)
  • Server start/stop lifecycle messages
  • Trusted redirects and blocked domain notifications

A utility function read_last_lines(path, max_lines) exists to retrieve the last log lines from a file. While not currently used in the UI, it enables future integration for “recent activity” panels.


11. Runtime and Security Considerations

  • Privilege model: binds to non-privileged ports by default (81/444) and uses loopback-only addressing.
  • Injection safety: blocked domains are escaped via html_escape() before being rendered.
  • Trust controls: trusted redirect decisions are based on a merged allowlist (official + user).
  • Fail-open for HTTPS: if TLS assets are missing or invalid, the server remains available over HTTP.
  • Isolation: the server runs in its own GLib loop thread and can be stopped cleanly via stop().
  • Availability behavior: startup succeeds if at least one port is available; otherwise it aborts with diagnostics.