How to Troubleshoot DNS Hijacking Issues with the Local DNS Service in MasterDnsVPN
Enable LOCAL_DNS_ENABLED = true in client_config.toml, ensure the service binds to 127.0.0.1:53 without port conflicts, and verify that your OS resolver points to the loopback address to prevent external DNS hijacking.
MasterDnsVPN includes a built-in local DNS resolver that encapsulates all DNS queries through the VPN tunnel, effectively bypassing ISP-level DNS hijacking. When configured correctly, this client-side service intercepts queries on 127.0.0.1:53 and forwards them securely through the encrypted tunnel to upstream servers. This article provides a complete troubleshooting guide based on the actual source code implementation in the masterking32/MasterDnsVPN repository.
Understanding the Local DNS Architecture
The local DNS service in MasterDnsVPN operates as a client-side DNS proxy that tunnels queries through the VPN connection rather than sending them directly to external resolvers. According to the source code in internal/udpserver/server.go, the system comprises several key components working together.
Core Components
internal/config/client.go– Defines theClientConfigstruct containingLOCAL_DNS_ENABLED,LOCAL_DNS_IP,LOCAL_DNS_PORT, and cache settings that control the resolver behavior.internal/udpserver/dns_tunnel.go– Implements the local DNS server logic that listens for incoming queries and encapsulates them into the VPN tunnel protocol.internal/dnscache/store.go– Manages the in-memory DNS cache to avoid repeated upstream lookups and survive short-term network outages.internal/udpserver/server_deferred.go– Handles reassembly of fragmented UDP packets carrying DNS responses back from the server.internal/udpserver/server.go– Routes tunnel traffic and local DNS service, spawning workers that process queries through the DNS tunnel code.
Data Flow
When the local resolver functions correctly, DNS queries follow this path:
- Application to Local DNS – Applications send DNS requests to
127.0.0.1:53(or your configuredLOCAL_DNS_IP:LOCAL_DNS_PORT). - Cache Lookup – The resolver checks the in-memory cache (
dnsCache). Cache hits return immediately without tunnel overhead. - Tunnel Encapsulation – Cache misses trigger the
dns_tunnel.gologic to encode and compress the request, sending it as a DNS-tunnel packet to the server. - Upstream Resolution – The server forwards the query to
DNS_UPSTREAM_SERVERSdefined inserver_config.toml. - Fragmented Response – Large responses split into UDP fragments stored temporarily in
dnsFragmentsbefore transmission. - Reassembly and Reply –
dns_tunnel.goreassembles fragments, caches the result, and replies to the original local client.
Common DNS Hijacking Symptoms and Root Causes
When the local DNS service fails or is misconfigured, traffic may leak to external resolvers, exposing you to DNS hijacking. The following table maps specific symptoms to their likely causes within the MasterDnsVPN architecture.
| Symptom | Likely Cause | Verification Method |
|---|---|---|
| No DNS resolution despite VPN connection | LOCAL_DNS_ENABLED is false or the client listens on a non-standard IP/port |
Check client_config.toml for LOCAL_DNS_ENABLED = true and verify LOCAL_DNS_IP/LOCAL_DNS_PORT values |
| "Connection refused" on port 53 | Port conflict with systemd-resolved, dnsmasq, or another local DNS daemon |
Run sudo ss -ulpn | grep ':53' to identify the occupying process |
| Slow or intermittent resolution | Fragment assembly timeout (DNS_FRAGMENT_ASSEMBLY_TIMEOUT) too low for your network latency |
Increase timeout value and monitor logs for dns: fragment timeout messages |
| Repeated "dns-fragment-timeout" errors | Upstream resolver unreachable or server UDP queue overloaded | Verify DNS_UPSTREAM_SERVERS in server_config.toml and check server CPU metrics |
| Cache never populates | LOCAL_DNS_CACHE_MAX_RECORDS set to 0 or unusually low value |
Inspect client logs for Cache size: X/Y entries at DEBUG level |
| Queries still hijacked after enabling local resolver | OS resolver configuration (/etc/resolv.conf) bypasses the loopback address |
Confirm nameserver 127.0.0.1 exists and is prioritized in the resolver order |
Step-by-Step Troubleshooting Guide
Follow this systematic checklist to diagnose and resolve DNS hijacking issues in MasterDnsVPN.
1. Verify Local DNS Activation
Confirm the feature is enabled in client_config.toml:
LOCAL_DNS_ENABLED = true
LOCAL_DNS_IP = "127.0.0.1"
LOCAL_DNS_PORT = 53
The ClientConfig struct in internal/config/client.go parses these values at startup. If LOCAL_DNS_ENABLED remains false, the client will not spawn the DNS worker goroutine defined in internal/udpserver/server.go.
2. Check for Port Conflicts
Port 53 is privileged and often occupied by system services. Identify conflicts using:
sudo ss -ulpn | grep ':53'
If systemd-resolve or another process holds the port, either stop the conflicting service (systemctl stop systemd-resolved) or change LOCAL_DNS_PORT to an alternative like 5353, updating your OS resolver accordingly.
3. Optimize Cache and Timeout Settings
For networks with high latency or packet loss, tune these parameters in client_config.toml:
LOCAL_DNS_CACHE_MAX_RECORDS = 5000
LOCAL_DNS_CACHE_TTL_SECONDS = 28800.0
DNS_FRAGMENT_ASSEMBLY_TIMEOUT = 600
LOCAL_DNS_CACHE_PERSIST_TO_FILE = true
The DNS_FRAGMENT_ASSEMBLY_TIMEOUT setting (default typically lower) controls how long internal/udpserver/server_deferred.go waits for all fragments of a large DNS response. Increase this value if logs show frequent fragment timeouts.
4. Inspect Client Logs
Set LOG_LEVEL = "DEBUG" in your configuration and restart the client. Monitor for these specific log prefixes:
dns: fragment timeout– Indicates incomplete UDP reassembly or packet lossdns: cache write error– Points to memory pressure or disk I/O issues if persistence is enableddns: upstream timeout– Suggests the server cannot reach its configured upstream resolvers
5. Validate the Resolver Endpoint
Test the local DNS service directly using dig:
dig @127.0.0.1 example.com +short
Successful responses should return IP addresses with minimal latency (10-30ms for cached entries). If the command hangs, the DNS tunnel worker in internal/udpserver/dns_tunnel.go is not receiving or processing queries correctly.
6. Verify Server-Side Upstream Health
On the server host, confirm that DNS_UPSTREAM_SERVERS in server_config.toml contains reachable IP addresses. Test connectivity directly:
dig @1.1.1.1 example.com
If the server cannot reach upstream resolvers, the client will experience DNS resolution failures regardless of local configuration.
Configuration Examples
Enabling the Local DNS Resolver
This complete client_config.toml snippet activates the local DNS with production-ready cache settings:
# client_config.toml
LOCAL_DNS_ENABLED = true
LOCAL_DNS_IP = "127.0.0.1"
LOCAL_DNS_PORT = 53
LOCAL_DNS_CACHE_MAX_RECORDS = 5000
LOCAL_DNS_CACHE_TTL_SECONDS = 28800.0
LOCAL_DNS_CACHE_PERSIST_TO_FILE = true
DNS_FRAGMENT_ASSEMBLY_TIMEOUT = 600
These fields correspond to the ClientConfig struct defined in internal/config/client.go.
Detecting Port Conflicts on Linux
Use ss to find processes blocking port 53:
sudo ss -ulpn | grep ':53'
# Example output:
# udp UNCONN 0 0 0.0.0.0:53 0.0.0.0:* users:(("systemd-resolve",pid=657,fd=13))
If another process occupies the port, stop it or modify LOCAL_DNS_PORT.
Monitoring Cache State Programmatically
While debugging, you can inspect the cache size through the internal/dnscache/store.go implementation:
// Debug logging snippet (from internal/udpserver/server.go context)
log.Printf("dns: cache contains %d entries (max %d)",
dnsCache.Size(), cfg.LocalDNSCacheMaxRecords)
This output appears in logs when the cache state changes, helping verify that records are being stored correctly.
Summary
- Enable the local resolver by setting
LOCAL_DNS_ENABLED = trueinclient_config.tomlto ensure DNS queries route through the VPN tunnel rather than external resolvers. - Resolve port conflicts by checking for competing services on port 53 with
ss -ulpnand either stopping them or changingLOCAL_DNS_PORT. - Tune timeout values by increasing
DNS_FRAGMENT_ASSEMBLY_TIMEOUTif you encounter fragment reassembly errors in high-latency networks. - Verify cache configuration with
LOCAL_DNS_CACHE_MAX_RECORDSset to at least 500 entries andLOCAL_DNS_CACHE_PERSIST_TO_FILEenabled for resilience across restarts. - Monitor debug logs for entries prefixed with
dns:to identify whether failures occur at the cache, tunnel, or upstream level according to the logic ininternal/udpserver/dns_tunnel.go.
Frequently Asked Questions
Why do I still see DNS hijacking after enabling LOCAL_DNS_ENABLED?
If LOCAL_DNS_ENABLED is true but queries are still hijacked, your operating system is likely bypassing the local resolver. Check /etc/resolv.conf to ensure it contains nameserver 127.0.0.1 and that no other DNS configuration (like systemd-resolved stub listeners) is overriding this setting. Also verify that no port conflict exists preventing the MasterDnsVPN DNS service from binding to the configured address.
How do I fix "dns: fragment timeout" errors in the logs?
Fragment timeouts occur when internal/udpserver/server_deferred.go cannot reassemble all UDP packets of a large DNS response within the DNS_FRAGMENT_ASSEMBLY_TIMEOUT window. Increase this value in client_config.toml (e.g., to 600 seconds) to accommodate high-latency or lossy networks. If timeouts persist, check that the server-side DNS_UPSTREAM_SERVERS can resolve the domain and that the server has sufficient CPU to process fragment storage.
Can I run the local DNS service on a port other than 53?
Yes. Set LOCAL_DNS_PORT to any available port (such as 5353) in client_config.toml. However, you must configure your operating system's resolver to use this alternate port. Most applications expect DNS on port 53, so changing this typically requires updating your OS network configuration or using a local DNS forwarder that points to the custom port.
What causes the local DNS cache to remain empty?
An empty cache usually indicates that LOCAL_DNS_CACHE_MAX_RECORDS is set to 0 or too low, or that the persistence layer in internal/dnscache/store.go is failing to initialize. Verify your configuration includes LOCAL_DNS_CACHE_MAX_RECORDS = 500 or higher, and check logs for disk permission errors if LOCAL_DNS_CACHE_PERSIST_TO_FILE is enabled.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →