DNSet é um pequeno servidor DNS escrito em Go, pensado para resolver um problema bem específico, mas muito comum em ambientes corporativos: um único endereço de aplicação atrás de um load balancer que vive mudando de IP e nunca resolve direito na VPN.
Em vez de ficar editando o hosts toda vez que o IP muda, o DNSet assume o papel de DNS local, testa os IPs candidatos e sempre devolve o que estiver saudável.
English version below.
O cenário que motivou essa aplicação foi o seguinte:
- Em uma VPN corporativa, apenas um endereço específico de um aplicativo nunca era resolvido.
- Depois de investigação, descobriu‑se que:
- A aplicação estava atrás de um load balancer.
- O endereço DNS apontava para IPs que mudavam com frequência.
- A solução manual era:
- Descobrir qual IP estava respondendo.
- Adicionar esse IP no
hostslocal. - Quando o load balancer trocava o IP:
- Comentar/remover a entrada do
hosts. - Dar
pingno hostname até descobrir o IP válido. - Atualizar o
hosts. - Rodar
ipconfig /flushdns.
- Comentar/remover a entrada do
- Tudo isso, várias e várias vezes.
Para eliminar esse fluxo chato e manual, surgiu a ideia de:
- Ter um DNS local que:
- Intercepta somente o domínio problemático.
- Mantém uma lista de IPs candidatos.
- Faz health check em cada IP (TCP na porta 443 com timeout curto).
- Sempre responde com o IP que estiver online naquele momento.
- Na configuração de rede:
- Substituir o DNS padrão por um IP local (por exemplo
127.0.0.1ou outro IP configurado emlisten_addr). - O DNSet, por sua vez, tem:
- O DNS corporativo oficial ou DNS público como upstream.
- A lista de IPs candidatos para o domínio problemático.
- Substituir o DNS padrão por um IP local (por exemplo
Resultado: o sistema continua resolvendo todos os domínios normalmente via upstream, e o domínio “problemático” passa a ser resolvido dinamicamente, sem precisar tocar no hosts.
- Linguagem: Go (Golang).
- Biblioteca DNS:
github.com/miekg/dns. - Plataforma alvo: Windows.
- Execução:
- Como serviço Windows.
- Ou em modo debug via linha de comando (
/debug).
- Configuração:
- Arquivo
dnset.yamlao lado do binário. - Permite configurar:
- Endereço de escuta do DNS local.
- DNS upstream.
- TTL das respostas.
- Timeout de health check.
- Lista de domínios e IPs candidatos.
- Política de bloqueio de IPv6.
- Arquivo
Fluxo básico:
- O cliente (Windows) faz uma consulta DNS para um domínio.
- O DNSet recebe a consulta em
listen_addr(por exemplo127.0.0.1:53). - Se o domínio está na configuração:
- Testa os IPs candidatos usando
net.DialTimeoutemporta 443. - Escolhe o primeiro IP que responder dentro do timeout.
- Responde um registro A com esse IP e TTL configurado.
- Testa os IPs candidatos usando
- Se o domínio não está na configuração:
- Repassa a consulta para o DNS upstream (por exemplo o DNS da Rede ou
8.8.8.8). - Devolve a resposta do upstream para o cliente.
- Repassa a consulta para o DNS upstream (por exemplo o DNS da Rede ou
Exemplo de configuração:
listen_addr: 127.0.0.1:53
upstream: 192.168.0.1:53
ttl_seconds: 60
health_timeout_ms: 500
block_ipv6: true
enable_ipv6: false
domains:
git-corp.encoders.com.br:
- 191.21.3.3
- 191.21.3.4
- 191.21.3.5
- 191.21.3.6Campos:
-
listen_addr
Endereço e porta em que o DNSet vai escutar como servidor DNS.
Exemplos:127.0.0.1:53(loopback IPv4).0.0.0.0:53(todas as interfaces IPv4).
-
upstream
DNS para o qual o DNSet encaminha todas as consultas de domínios não configurados emdomains.
Exemplos:- DNS da Rede:
192.168.0.1:53. - DNS público:
8.8.8.8:53.
- DNS da Rede:
-
ttl_seconds
TTL dos registros A devolvidos para os domínios que o DNSet gerencia. -
health_timeout_ms
Timeout em milissegundos para o health check TCP emporta 443de cada IP candidato. -
block_ipv6
Quandotrue, para domínios gerenciados:- Consultas
AAAA(IPv6) retornamNOERRORsem registros. - Isso força o cliente a usar apenas IPv4 para esses domínios.
- Consultas
-
enable_ipv6Quandotrue, habilita a escuta do servidor para endereços IPv6 -
domains
Mapadomínio→ lista de IPs candidatos.
Observações:- A chave pode ser escrita com ou sem ponto final (
.); o código normaliza para o formatodominio.com.. - É feita comparação por sufixo:
- Se
git-corp.encoders.com.brestá configurado, - consultas para
git-corp.encoders.com.bre subdomínios são interceptadas.
- Se
- A chave pode ser escrita com ou sem ponto final (
Para cada consulta de tipo A (IPv4) para um domínio configurado:
- O DNSet pega a lista de IPs candidatos daquele domínio.
- Para cada IP, na ordem:
- Tenta abrir uma conexão TCP em
ip:443usandonet.DialTimeout. - Se a conexão funciona dentro do
health_timeout_ms, esse IP é considerado saudável.
- Tenta abrir uma conexão TCP em
- O primeiro IP saudável é escolhido como IP vencedor.
- O servidor DNS devolve um registro A com:
Name= nome da consulta.Address= IP vencedor.TTL=ttl_secondsconfigurado.
- Se nenhum IP for saudável:
- O servidor responde com
SERVFAILpara aquela consulta.
- O servidor responde com
Consultas de outros tipos (por exemplo A de domínios não configurados, MX, etc.) são encaminhadas para o DNS upstream e a resposta é devolvida sem alteração.
O DNSet está preparado para rodar como Windows Service:
- Ao iniciar via Service Control Manager, o processo detecta que está em ambiente de serviço e:
- Inicializa o servidor DNS (UDP/TCP em IPv4 e IPv6 loopback).
- Fica ouvindo eventos de
Stop/Shutdown. - Encerra os listeners de forma limpa.
A instalação/registro do serviço pode ser feita usando ferramentas padrão do Windows ou código adicional específico (não incluso aqui para manter o projeto simples).
Para desenvolvimento e testes, existe o modo debug:
dnset.exe /debugNo modo debug:
- O servidor DNS é iniciado exatamente da mesma forma que no serviço.
- A aplicação fica em foreground.
- Para encerrar, basta
CTRL + C.
Existem duas formas principais de usar o DNSet:
Se você puder mudar o DNS manualmente:
- Configure o DNS da interface de rede para o IP onde o DNSet escuta:
- Por exemplo:
127.0.0.1ou um IP local (192.168.0.x) que você configurou emlisten_addr.
- Por exemplo:
- No
dnset.yaml, coloque o DNS corporativo / da VPN ou da Rede comoupstream:- Por exemplo:
upstream: 192.168.0.1:53.
- Por exemplo:
- O DNSet vai:
- Resolver dinamicamente os domínios configurados.
- Encaminhar todas as demais consultas para
192.168.0.1:53.
Se o DNS vem via DHCP e você não pode trocar o DNS da placa:
- É possível usar a NRPT do Windows para dizer:
- “Para o domínio
git-corp.encoders.com.br, use o DNS127.0.0.1/::1”.
- “Para o domínio
- Exemplo (PowerShell como Administrador):
Add-DnsClientNrptRule -Namespace "git-corp.encoders.com.br" -NameServers 127.0.0.1,::1Assim:
- Apenas o domínio desejado é resolvido pelo DNSet.
- Todos os demais continuam indo para o DNS recebido via DHCP.
Antes:
- Atualização manual do
hostssempre que o load balancer trocava de IP. - Testes manuais (ping, flush de cache DNS).
- Processo repetitivo, sujeito a erro e perda de tempo.
Depois, com o DNSet:
- Um único ponto de configuração (
dnset.yaml) com:- Lista de IPs candidatos para o domínio problemático.
- DNS upstream correto para o ambiente.
- O DNS local:
- Faz health check dos IPs automaticamente.
- Sempre devolve o IP que está respondendo.
- Integra com o DNS da VPN/corporativo sem quebrar outros domínios.
Você continua usando a VPN normalmente, mas nunca mais precisa mexer no hosts para aquele domínio específico.
DNSet is a small DNS server written in Go, designed to solve a very specific but common problem in corporate VPN environments: a single application hostname behind a load balancer that keeps changing IPs and never resolves reliably.
Instead of constantly editing your hosts file every time the IP changes, DNSet acts as a local DNS server, tests candidate IPs, and always returns the one that is currently healthy.
The real-world scenario that inspired this project:
- In a corporate VPN, one specific application hostname would never resolve correctly.
- After some investigation, the root cause was:
- The application was behind a load balancer.
- The DNS entry pointed to IPs that changed frequently.
- The manual workaround was:
- Find which IP was currently responding.
- Add that IP to the local
hostsfile. - Whenever the load balancer switched the IP:
- Comment/remove the
hostsentry. - Run
pingon the hostname until a working IP was found. - Update the
hostsfile. - Run
ipconfig /flushdns.
- Comment/remove the
- Repeat, over and over again.
To get rid of this painful, manual process, the idea was:
- Have a local DNS server that:
- Intercepts only the problematic hostname/domain.
- Holds a list of candidate IPs.
- Performs a health check on each IP (TCP on port 443 with a short timeout).
- Always returns the IP that is currently online.
- In the network configuration:
- Replace the standard DNS with a local IP (for example
127.0.0.1or another IP configured inlisten_addr). - DNSet itself is configured with:
- The official corporate/VPN DNS or a public DNS as an upstream resolver.
- The list of candidate IPs for the problematic hostname.
- Replace the standard DNS with a local IP (for example
Result: the system keeps resolving all domains normally through the upstream DNS, and the “problem” hostname is resolved dynamically, with no need to touch the hosts file.
- Language: Go (Golang).
- DNS library:
github.com/miekg/dns. - Target platform: Windows.
- Execution modes:
- As a Windows Service.
- Or in debug mode via command line (
/debug).
- Configuration:
dnset.yamlfile next to the binary.- Lets you configure:
- Local DNS listen address.
- Upstream DNS.
- TTL for responses.
- Health check timeout.
- List of domains and candidate IPs.
- IPv6 blocking policy.
Basic flow:
- The client (Windows) sends a DNS query for some hostname.
- DNSet receives the query on
listen_addr(for example127.0.0.1:53). - If the hostname is configured:
- DNSet tests the candidate IPs using
net.DialTimeouton port443. - It picks the first IP that answers within the timeout.
- It returns an A record with that IP and the configured TTL.
- DNSet tests the candidate IPs using
- If the hostname is not configured:
- DNSet forwards the query to the upstream DNS (for example the VPN DNS or
8.8.8.8). - DNSet relays the upstream response back to the client.
- DNSet forwards the query to the upstream DNS (for example the VPN DNS or
Example configuration:
listen_addr: 127.0.0.1:53
upstream: 192.168.0.1:53
ttl_seconds: 60
health_timeout_ms: 500
block_ipv6: true
enable_ipv6: false
domains:
git-corp.encoders.com.br:
- 191.21.3.3
- 191.21.3.4
- 191.21.3.5
- 191.21.3.6Fields:
-
listen_addr
Address and port where DNSet listens as a DNS server.
Examples:127.0.0.1:53(IPv4 loopback).0.0.0.0:53(all IPv4 interfaces).
-
upstream
DNS server to which DNSet forwards all queries for hostnames not configured indomains.
Examples:- VPN DNS:
192.168.0.1:53. - Public DNS:
8.8.8.8:53.
- VPN DNS:
-
ttl_seconds
TTL of the A records returned for hostnames managed by DNSet. -
health_timeout_ms
Timeout in milliseconds for the TCP health check on port443for each candidate IP. -
block_ipv6
Whentrue, for managed domains:AAAA(IPv6) queries returnNOERRORwith no records.- This forces the client to use IPv4 only for those hostnames.
-
enable_ipv6Whentrue, enable the server to listen IPv6 addresses -
domains
Maphostname→ list of candidate IPs.
Notes:- Keys may be written with or without a trailing dot (
.); the code normalizes them todomain.com.form. - DNSet performs suffix matching:
- If
git-corp.encoders.com.bris configured, - queries for
git-corp.encoders.com.brand its subdomains are intercepted.
- If
- Keys may be written with or without a trailing dot (
For each A (IPv4) query for a configured hostname:
- DNSet loads the candidate IP list for that hostname.
- For each IP, in order:
- It attempts a TCP connection to
ip:443usingnet.DialTimeout. - If the connection succeeds within
health_timeout_ms, the IP is considered healthy.
- It attempts a TCP connection to
- The first healthy IP becomes the winner IP.
- The DNS server returns an A record with:
Name= query name.Address= winner IP.TTL=ttl_secondsfrom the config.
- If no IP is healthy:
- The server responds with
SERVFAILfor that query.
- The server responds with
Queries of other types (for example A for non-configured domains, MX, etc.) are simply forwarded to the upstream DNS and the response is relayed.
DNSet is built to run as a Windows Service:
- When started by the Service Control Manager, it detects that it is running as a service and:
- Initializes the DNS server (UDP/TCP on IPv4 and IPv6 loopback).
- Listens for
Stop/Shutdownevents. - Shuts down listeners cleanly.
Service installation/registration can be done using standard Windows tools or extra helper code (not included here to keep the project focused).
For development and manual testing, there is a debug mode:
dnset.exe /debugIn debug mode:
- The DNS server is started exactly like in service mode.
- The application stays in the foreground.
- To stop it, just press
CTRL + C.
There are two main ways to integrate DNSet into your environment:
If you are allowed to change DNS settings on your network adapter:
- Set the DNS server on the interface to the IP where DNSet listens:
- For example:
127.0.0.1or a local IP (192.168.0.x) you configured inlisten_addr.
- For example:
- In
dnset.yaml, set the corporate/VPN DNS asupstream:- For example:
upstream: 192.168.0.1:53.
- For example:
- DNSet will:
- Dynamically resolve the configured hostnames.
- Forward all other queries to
192.168.0.1:53.
If DNS comes from DHCP and you cannot change the NIC DNS:
- You can use Windows NRPT to say:
- “For
git-corp.encoders.com.br, use DNS127.0.0.1/::1”.
- “For
- Example (PowerShell as Administrator):
Add-DnsClientNrptRule -Namespace "git-corp.encoders.com.br" -NameServers 127.0.0.1,::1With this:
- Only the desired hostname/domain is resolved via DNSet.
- All others keep using the DNS servers delivered by DHCP.
Before DNSet:
- Manual
hostsupdates every time the load balancer changed IP. - Manual testing (ping, flush DNS cache).
- Repetitive, error-prone, and time-consuming.
After DNSet:
- A single configuration point (
dnset.yaml) with:- Candidate IP list for the problematic hostname.
- Correct upstream DNS for the environment.
- The local DNS:
- Performs health checks automatically.
- Always returns a working IP.
- Integrates with VPN/corporate DNS without breaking other domains.
You keep using the VPN as usual, but you never need to touch the hosts file again for that specific hostname.