Skip to content

ronicarvalho/dnset

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DNSet – DNS Dinâmico Local para Ambientes Corporativos

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.


Motivação

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 hosts local.
    • Quando o load balancer trocava o IP:
      • Comentar/remover a entrada do hosts.
      • Dar ping no hostname até descobrir o IP válido.
      • Atualizar o hosts.
      • Rodar ipconfig /flushdns.
    • 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.1 ou outro IP configurado em listen_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.

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.


Visão Geral da Aplicação

  • 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.yaml ao 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.

Fluxo básico:

  1. O cliente (Windows) faz uma consulta DNS para um domínio.
  2. O DNSet recebe a consulta em listen_addr (por exemplo 127.0.0.1:53).
  3. Se o domínio está na configuração:
    • Testa os IPs candidatos usando net.DialTimeout em porta 443.
    • Escolhe o primeiro IP que responder dentro do timeout.
    • Responde um registro A com esse IP e TTL configurado.
  4. 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.

Arquivo de Configuração (dnset.yaml)

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.6

Campos:

  • 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 em domains.
    Exemplos:

    • DNS da Rede: 192.168.0.1:53.
    • DNS público: 8.8.8.8:53.
  • 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 em porta 443 de cada IP candidato.

  • block_ipv6
    Quando true, para domínios gerenciados:

    • Consultas AAAA (IPv6) retornam NOERROR sem registros.
    • Isso força o cliente a usar apenas IPv4 para esses domínios.
  • enable_ipv6 Quando true, habilita a escuta do servidor para endereços IPv6

  • domains
    Mapa domínio → lista de IPs candidatos.
    Observações:

    • A chave pode ser escrita com ou sem ponto final (.); o código normaliza para o formato dominio.com..
    • É feita comparação por sufixo:
      • Se git-corp.encoders.com.br está configurado,
      • consultas para git-corp.encoders.com.br e subdomínios são interceptadas.

Como Funciona a Escolha do IP

Para cada consulta de tipo A (IPv4) para um domínio configurado:

  1. O DNSet pega a lista de IPs candidatos daquele domínio.
  2. Para cada IP, na ordem:
    • Tenta abrir uma conexão TCP em ip:443 usando net.DialTimeout.
    • Se a conexão funciona dentro do health_timeout_ms, esse IP é considerado saudável.
  3. O primeiro IP saudável é escolhido como IP vencedor.
  4. O servidor DNS devolve um registro A com:
    • Name = nome da consulta.
    • Address = IP vencedor.
    • TTL = ttl_seconds configurado.
  5. Se nenhum IP for saudável:
    • O servidor responde com SERVFAIL para aquela consulta.

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.


Execução no Windows

Modo Serviç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).

Modo Debug (console)

Para desenvolvimento e testes, existe o modo debug:

dnset.exe /debug

No 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.

Integração com a Rede

Existem duas formas principais de usar o DNSet:

1. DNS da placa apontando para 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.1 ou um IP local (192.168.0.x) que você configurou em listen_addr.
  • No dnset.yaml, coloque o DNS corporativo / da VPN ou da Rede como upstream:
    • Por exemplo: upstream: 192.168.0.1:53.
  • O DNSet vai:
    • Resolver dinamicamente os domínios configurados.
    • Encaminhar todas as demais consultas para 192.168.0.1:53.

2. NRPT (Name Resolution Policy Table)

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 DNS 127.0.0.1/::1”.
  • Exemplo (PowerShell como Administrador):
Add-DnsClientNrptRule -Namespace "git-corp.encoders.com.br" -NameServers 127.0.0.1,::1

Assim:

  • Apenas o domínio desejado é resolvido pelo DNSet.
  • Todos os demais continuam indo para o DNS recebido via DHCP.

Resumo do Ganho

Antes:

  • Atualização manual do hosts sempre 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 – Local Dynamic DNS for Corporate Environments

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.


Motivation

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 hosts file.
    • Whenever the load balancer switched the IP:
      • Comment/remove the hosts entry.
      • Run ping on the hostname until a working IP was found.
      • Update the hosts file.
      • Run ipconfig /flushdns.
    • 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.1 or another IP configured in listen_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.

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.


Application Overview

  • 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.yaml file 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:

  1. The client (Windows) sends a DNS query for some hostname.
  2. DNSet receives the query on listen_addr (for example 127.0.0.1:53).
  3. If the hostname is configured:
    • DNSet tests the candidate IPs using net.DialTimeout on port 443.
    • It picks the first IP that answers within the timeout.
    • It returns an A record with that IP and the configured TTL.
  4. 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.

Configuration File (dnset.yaml)

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.6

Fields:

  • 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 in domains.
    Examples:

    • VPN DNS: 192.168.0.1:53.
    • Public DNS: 8.8.8.8:53.
  • 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 port 443 for each candidate IP.

  • block_ipv6
    When true, for managed domains:

    • AAAA (IPv6) queries return NOERROR with no records.
    • This forces the client to use IPv4 only for those hostnames.
  • enable_ipv6 When true, enable the server to listen IPv6 addresses

  • domains
    Map hostname → list of candidate IPs.
    Notes:

    • Keys may be written with or without a trailing dot (.); the code normalizes them to domain.com. form.
    • DNSet performs suffix matching:
      • If git-corp.encoders.com.br is configured,
      • queries for git-corp.encoders.com.br and its subdomains are intercepted.

How IP Selection Works

For each A (IPv4) query for a configured hostname:

  1. DNSet loads the candidate IP list for that hostname.
  2. For each IP, in order:
    • It attempts a TCP connection to ip:443 using net.DialTimeout.
    • If the connection succeeds within health_timeout_ms, the IP is considered healthy.
  3. The first healthy IP becomes the winner IP.
  4. The DNS server returns an A record with:
    • Name = query name.
    • Address = winner IP.
    • TTL = ttl_seconds from the config.
  5. If no IP is healthy:
    • The server responds with SERVFAIL for that query.

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.


Running on Windows

Service Mode

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/Shutdown events.
    • 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).

Debug Mode (console)

For development and manual testing, there is a debug mode:

dnset.exe /debug

In 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.

Network Integration

There are two main ways to integrate DNSet into your environment:

1. NIC DNS pointing to DNSet

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.1 or a local IP (192.168.0.x) you configured in listen_addr.
  • In dnset.yaml, set the corporate/VPN DNS as upstream:
    • For example: upstream: 192.168.0.1:53.
  • DNSet will:
    • Dynamically resolve the configured hostnames.
    • Forward all other queries to 192.168.0.1:53.

2. NRPT (Name Resolution Policy Table)

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 DNS 127.0.0.1/::1”.
  • Example (PowerShell as Administrator):
Add-DnsClientNrptRule -Namespace "git-corp.encoders.com.br" -NameServers 127.0.0.1,::1

With this:

  • Only the desired hostname/domain is resolved via DNSet.
  • All others keep using the DNS servers delivered by DHCP.

Summary of Benefits

Before DNSet:

  • Manual hosts updates 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.

About

DNSet is a small DNS server written in Go, designed to solve a very specific but common problem in corporate VPN environments

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages