-
Notifications
You must be signed in to change notification settings - Fork 302
Description
Dear developers,
Currently, dnsproxy can only use IP sockets for upstream and downstream communication. For many use cases where dnsproxy runs on same machine as client or an upstream resolver (like a local Unbound), this is not optimal. Using TCP loopback (127.0.0.1) for local communication creates unnecessary network overhead from the TCP/IP stack that we could avoid.
I would like to propose adding a possibility for dnsproxy to use Unix Domain Sockets (UDS). This would be for both upstream connections and for listening sockets.
The desired behavior would be to allow configurations like this:
unix:/path/to/dns.sockfor plain DNS over a datagram socket.tls+unix:/path/to/dns.sockfor DNS-over-TLS over a stream socket.
To achieve this, dnsproxy would need to use Go's net.Dial("unixgram", ...) for UDP-like protocols and net.Dial("unix", ...) for TCP-like protocols.
The only alternative is to continue using TCP loopback. As I will explain below, this is less performant and has a less robust security model for local IPC.
Additional context
I have done a small research on this topic, and I believe this feature would be very valuable.
1. Performance Benefit
The performance benefit is the main reason for this request. For small packets like DNS queries, UDS is much faster than TCP loopback.
- Throughput: Benchmarks show UDS can handle over 2x more messages per second (one test showed ~22,000 msgs/sec for UDS vs ~10,000 for TCP).
- Latency: Latency can be 3 times lower (e.g., 2µs for UDS vs 6µs for TCP).
This is because UDS is a direct communication channel through the kernel and it completely bypasses the network stack. There is no TCP three-way handshake, no packet encapsulation, and no checksum calculation.
2. Security Improvement
Also, security is better with UDS. A socket is a file in the filesystem, so we can use standard Unix file permissions (chmod, chown) to control exactly which user or process can access it. This is much more granular and simple than managing access to a TCP port, where any local process can try to connect by default.
3. Practical Use Case: Reverse Proxy Integration
A very good use case is integration with a reverse proxy like Nginx. Nginx can be configured to handle the TLS termination for DoT or DoQ and then forward the plain DNS requests to dnsproxy over a very fast UDS connection.
4. Known Limitations & Challenges
Of course, I understand there are some complications.
- Windows Limitation: The biggest problem is Windows. As I understand, Go's
netpackage on Windows supportsunix(stream sockets) but does not supportunixgram(datagram sockets). This is a big limitation because it means proxying plain DNS-over-UDP would not work on Windows. For this platform, maybe the feature can be disabled or it must fallback to TCP. This should be clearly documented. - Socket File Management: The
dnsproxyprocess would be responsible for the socket file. If the program crashes, the stale socket file can remain and cause an error on restart. The program must have logic to check for and remove the old socket file when it starts.
Even with these limitations, the performance and security benefits on Unix-like systems (Linux, BSD, macOS) where most servers run are very significant. Thank you for considering this feature.
P.S.
It's a followup of my previous FR in Adguard Home repo