# How Does amnezia-client Handle DNS Resolution? A Deep Dive into the Cross-Platform VPN DNS Architecture

> Discover how amnezia-client handles DNS resolution by routing queries through platform-specific APIs and restoring system DNS upon disconnection. Learn about its secure VPN DNS architecture.

- Repository: [Amnezia VPN/amnezia-client](https://github.com/amnezia-vpn/amnezia-client)
- Tags: deep-dive
- Published: 2026-07-29

---

**amnezia-client isolates DNS traffic inside the VPN tunnel by routing all queries through platform-specific resolver APIs, then restores the system DNS configuration when the tunnel disconnects.**

DNS resolution in the amnezia-vpn/amnezia-client follows a strict lifecycle: flush cached entries, push VPN-specific resolvers, enforce traffic isolation via firewall rules, and finally restore the original system state. The implementation spans a layered architecture that abstracts operating system differences behind a common **Router** façade while delegating actual resolver manipulation to platform-specific **DnsUtils** classes.

## Architecture Overview: Router and DnsUtils Layers

The DNS handling strategy splits responsibilities between two layers defined in [`service/server/router.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/router.cpp) and [`client/daemon/dnsutils.h`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/daemon/dnsutils.h).

- **Router** – Acts as a high-level façade that forwards DNS operations (`flushDns()`, `updateResolvers()`, `restoreResolvers()`) to OS-specific implementations.
- **DnsUtils** – Directly interfaces with the system resolver (systemd-resolved, mDNSResponder, or Win32 DNS API) to modify DNS server assignments for the VPN interface.

This separation allows `VpnConnection` in [`client/vpnConnection.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/vpnConnection.cpp) to orchestrate tunnel state changes without managing platform-specific quirks.

## Flushing the DNS Cache

When the VPN disconnects, amnezia-client clears the OS DNS cache to prevent stale entries from persisting after the tunnel closes.

### Linux DNS Cache Flushing (nscd and systemd-resolved)

On Linux, `RouterLinux::flushDns()` in [`service/server/router_linux.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/router_linux.cpp) (lines 63-78) detects which caching service is active. If `nscd.service` is running, it restarts that service; otherwise, it falls back to restarting `systemd-resolved.service`.

```cpp
// service/server/router_linux.cpp
bool RouterLinux::flushDns()
{
    QProcess p;
    p.setProcessChannelMode(QProcess::MergedChannels);

    if (isServiceActive("nscd.service")) {
        p.start("systemctl", { "restart", "nscd" });
    } else if (isServiceActive("systemd-resolved.service")) {
        p.start("systemctl", { "restart", "systemd-resolved" });
    } else {
        return false; // No known DNS cache manager
    }
    p.waitForFinished();
    return true;
}

```

### macOS DNS Cache Flushing (mDNSResponder)

On macOS, `RouterMac::flushDns()` in [`service/server/router_mac.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/router_mac.cpp) (lines 62-73) sends a `SIGHUP` to `mDNSResponder`, forcing the daemon to reread [`/etc/resolv.conf`](https://github.com/amnezia-vpn/amnezia-client/blob/main//etc/resolv.conf).

```cpp
// service/server/router_mac.cpp
bool RouterMac::flushDns()
{
    QProcess p;
    p.start("killall", QStringList() << "-HUP" << "mDNSResponder");
    p.waitForFinished();
    return true;
}

```

### Windows DNS Cache Flushing

Windows delegates to `RouterWin::flushDns()` in [`service/server/router_win.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/router_win.cpp), which invokes the Windows DNS API to clear the resolver cache.

## Updating DNS Servers for the VPN Interface

When the tunnel activates, amnezia-client pushes the desired DNS servers—either the default AmneziaDNS or custom user-defined resolvers—to the OS and blocks external DNS queries through the kill-switch.

### Linux Implementation (systemd-resolve D-Bus API)

`DnsUtilsLinux` in [`client/platforms/linux/daemon/dnsutilslinux.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/platforms/linux/daemon/dnsutilslinux.cpp) uses the systemd-resolved D-Bus interface. The `updateResolvers()` method (lines 53-64) obtains the interface index via `if_nametoindex()`, then calls three helpers:

1. `setLinkDNS()` – Sets DNS addresses via the `SetLinkDNS` D-Bus method (lines 98-122).
2. `setLinkDefaultRoute()` – Marks the interface as the default route using `SetLinkDefaultRoute` (lines 146-156).
3. `updateLinkDomains()` – Preserves search domains via `SetLinkDomains` (lines 158-174).

```cpp
// client/platforms/linux/daemon/dnsutilslinux.cpp
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
                                    const QList<QHostAddress>& resolvers)
{
    m_ifindex = if_nametoindex(qPrintable(ifname));
    if (m_ifindex <= 0) return false;

    setLinkDNS(m_ifindex, resolvers);            // Set DNS addresses
    setLinkDefaultRoute(m_ifindex, true);       // Make them the default route
    updateLinkDomains();                        // Preserve search domains
    return true;
}

```

All three platforms expose the identical public interface `bool updateResolvers(const QString&, const QList<QHostAddress>&)`, called from `Router::updateResolvers()` in [`service/server/router.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/router.cpp) (lines 92-100).

### macOS Implementation (scutil)

`DnsUtilsMacos` in [`client/platforms/macos/daemon/dnsutilsmacos.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/platforms/macos/daemon/dnsutilsmacos.cpp) interfaces with the macOS system resolver using the `scutil` command-line utility. It saves the current DNS configuration before overwriting it, enabling clean restoration later.

### Windows Implementation (Win32 DNS API)

`DnsUtilsWindows` in [`client/platforms/windows/daemon/dnsutilswindows.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/platforms/windows/daemon/dnsutilswindows.cpp) utilizes the `SetAdapterDnsAddresses` Win32 API to configure DNS servers for the VPN adapter.

## Restoring Original DNS Configuration

When the VPN stops, `Router::restoreResolvers()` delegates to the platform-specific `DnsUtils::restoreResolvers()` to revert changes.

### Linux Restoration (RevertLink)

`DnsUtilsLinux::restoreResolvers()` in [`client/platforms/linux/daemon/dnsutilslinux.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/client/platforms/linux/daemon/dnsutilslinux.cpp) (lines 67-88) first reapplies any saved search domains, then calls the `RevertLink` D-Bus method to return the interface to its pre-VPN state.

```cpp
// client/platforms/linux/daemon/dnsutilslinux.cpp
bool DnsUtilsLinux::restoreResolvers()
{
    // Re‑apply saved search domains
    for (auto it = m_linkDomains.constBegin(); it != m_linkDomains.constEnd(); ++it) {
        setLinkDomains(it.key(), it.value());
    }
    m_linkDomains.clear();

    // Ask systemd‑resolve to revert the interface’s DNS configuration
    QList<QVariant> args = { QVariant::fromValue(m_ifindex) };
    QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
        QStringLiteral("RevertLink"), args);
    // Watch for completion …
    return true;
}

```

### macOS and Windows Restoration

On macOS, `DnsUtilsMacos` restores the saved DNS settings from the tunnel initialization phase. On Windows, `DnsUtilsWindows` resets the adapter's DNS servers using the Win32 API.

## Integration with the Kill-Switch

The kill-switch ensures DNS traffic cannot leak outside the VPN tunnel. It blocks external DNS queries by installing **PF** rules on macOS or **iptables** rules on Linux, defined in [`service/server/killswitch.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/killswitch.cpp) (lines 348-388).

After configuring DNS servers, the firewall enables the "allow DNS" anchor (`310.blockDNS` / `320.allowDNS`), creating a whitelist that permits queries only to the VPN-assigned DNS servers.

## Summary

- **Layered Architecture**: The **Router** class abstracts DNS operations while **DnsUtils** handles platform-specific resolver APIs.
- **Cache Management**: Linux restarts `nscd` or `systemd-resolved`; macOS signals `mDNSResponder`; Windows uses native DNS APIs.
- **DNS Push**: Linux uses D-Bus (`SetLinkDNS`, `SetLinkDefaultRoute`); macOS uses `scutil`; Windows uses `SetAdapterDnsAddresses`.
- **Clean Teardown**: Linux calls `RevertLink` via D-Bus; all platforms restore original settings via `restoreResolvers()`.
- **Leak Prevention**: The kill-switch blocks external DNS traffic while the VPN is active, allowing only tunneled queries.

## Frequently Asked Questions

### How does amnezia-client prevent DNS leaks when the VPN is connected?

The client implements a kill-switch in [`service/server/killswitch.cpp`](https://github.com/amnezia-vpn/amnezia-client/blob/main/service/server/killswitch.cpp) that installs firewall rules (iptables on Linux, PF on macOS) to block all DNS traffic outside the VPN tunnel. Only the DNS servers pushed via `updateResolvers()` are whitelisted, ensuring queries cannot bypass the encrypted tunnel.

### What happens to my system DNS settings when I disconnect from amnezia-client?

When the VPN disconnects, `Router::restoreResolvers()` triggers platform-specific restoration logic. On Linux, this calls the `RevertLink` D-Bus method to return the interface to its original configuration; on macOS and Windows, saved settings are reapplied from the pre-connection state.

### Does amnezia-client support custom DNS servers?

Yes. The `updateResolvers()` method accepts a `QList<QHostAddress>` parameter that can contain either the default AmneziaDNS servers or user-specified custom resolvers. These are pushed to the system resolver regardless of whether they use the systemd-resolved D-Bus API, macOS scutil, or the Windows DNS API.

### Why does amnezia-client flush the DNS cache when disconnecting?

Flushing the DNS cache via `Router::flushDns()` ensures that cached entries from the VPN's internal DNS servers do not persist after the tunnel closes. This prevents stale resolution data from interfering with subsequent queries that should use the restored system resolvers.