Anti-Phishing Engine – DNS Cache Layer

1. Overview

The DNSCache component provides a lightweight, in-memory DNS resolution cache used by the Anti-Phishing engine to reduce repeated resolver calls and to stabilize routing decisions during short execution windows. It stores resolved IP address lists for hostnames and expires entries using a time-to-live (TTL) policy.

The implementation is designed to be:

  • Thread-safe via a single internal mutex guarding the map
  • Deterministic via TTL-based expiration checks performed on lookup
  • Low-overhead using an in-process singleton instance

2. Responsibilities

  • Cache lookup – return stored A/AAAA results if present and not expired.
  • Cache insertion – store resolved IP lists with a computed expiration timestamp.
  • TTL policy – enforce a default TTL when none is supplied and clamp invalid TTL values.
  • Cache maintenance – allow explicit clearing and expose current cache size (diagnostics).

3. Public API Behavior

3.1 Singleton Access

The cache is provided as a process-wide singleton:

  • DNSCache::instance() returns a static function-local instance.

This pattern is appropriate here because the cache is purely in-memory, has no external dependencies, and is intended to be shared across threads within the daemon/process.


3.2 Lookup (Bulk)

bool lookup(const std::string& host, std::vector<std::string>& out) performs the core lookup logic:

  1. Acquire the internal mutex (mu_).
  2. Find the host entry in map_.
  3. If missing, return false.
  4. Compare current time (steady_clock::now()) to the entry expiration time.
  5. If expired, erase the entry and return false.
  6. If valid, copy cached addresses into out and return true.

Notable design detail: expiration is enforced lazily on access (no background sweeper thread), which is sufficient for small cache sizes and avoids extra complexity.


3.3 Lookup (Single)

std::string lookup_one(const std::string& host) is a convenience wrapper:

  • Calls lookup() internally.
  • Returns the first address if available, otherwise an empty string.

This supports call sites that only require a single deterministic target address (e.g., fastest path routing), while still allowing the full vector for more advanced policies.


3.4 Store

store(host, addrs, ttl_seconds) inserts/updates an entry:

  • Rejects invalid input early:
    • Empty host → no-op
    • Empty addrs → no-op
  • Selects an effective TTL:
    • If ttl_seconds > 0, uses it
    • Otherwise uses default_ttl_
  • Computes expiry as: steady_clock::now() + seconds(ttl)
  • Acquires the mutex and updates map_[host]

Overwrite semantics: storing the same host replaces previous values and refreshes TTL, which is typically correct for DNS because new resolutions should supersede old ones.


3.5 TTL Management

set_default_ttl(int seconds) updates the default TTL with clamping:

  • If seconds > 0, it is used as the new default
  • Otherwise it falls back to 60 seconds

This provides a safe baseline and prevents accidental configuration that would create non-expiring entries or an immediate-expiration storm.


3.6 Cache Reset and Diagnostics

  • clear() removes all entries (mutex-protected).
  • size() const returns current entry count (mutex-protected).

These functions are typically used for operator diagnostics, testing, or for explicit reset behavior after phishing list refreshes (depending on your integration strategy).


4. Internal Data Model

The cache stores values as a DNSResult structure containing:

  • std::vector<std::string> addrs – cached IP addresses for the hostname
  • std::chrono::steady_clock::time_point expires – expiration timestamp

A key design choice is using std::chrono::steady_clock rather than system_clock. This avoids correctness issues if the system time changes (NTP adjustments, manual clock changes), ensuring expiration remains monotonic and reliable.


5. Concurrency and Performance Considerations

  • Thread safety: a single mutex guards all operations; this is simple and safe, but can become a bottleneck at high concurrency.
  • Copy cost: lookups copy the vector of addresses into out; address lists are usually small, making this acceptable.
  • No background eviction: entries are removed only when accessed and found expired; stale-but-unused entries may remain until touched or cleared.

For typical Anti-Phishing workloads (short-lifetime host checks, low cardinality cache), this is a practical trade-off. If you later introduce very high throughput URL scanning, a sharded mutex map or lock-free strategy could be considered.


6. Security Considerations

  • Cache poisoning scope: results are only as trustworthy as the resolver path feeding store(). The cache itself does not validate IP authenticity.
  • TTL discipline: short TTLs reduce the window of incorrect routing decisions if upstream DNS changes or if an injected resolution occurs.
  • Monotonic expiration: using steady_clock prevents time-jump manipulation from extending cached lifetimes unintentionally.