Como eu fiz o meu próprio serviço de DNS dinâmico

Já que os dois serviços de DNS dinâmico que eu usava estavam em condições incômodas, pensei que poderia ser divertido e útil aprender um pouco e configurar o meu próprio serviço de DNS dinâmico.

Recebi um e-mail de "Dyn" hello@dyn.com no dia 15/05/2013, com o assunto "SLA Update For Dyn Non-Paid Users", que dizia:

DNS SLA Update

Starting now, if you would like to maintain your free Dyn account, you must log into your account once a month. Failure to do so will result in expiration and loss of your hostname. Note that using an update client will no longer suffice for this monthly login. You will still continue to get email alerts every 30 days if your email address is current.

O dyn.com é um dos dois serviços de DNS dinâmico que eu usava até então para o meu computador de casa. O outro é o dnsdynamic.org, que no dia seguinte ao recebimento do e-mail, portanto em 16/05/2013, por coincidência ou não, parou de responder.

Pesquisando na Internet, encontrei os seguintes recursos:

Eu já administrava o meu próprio servidor DNS com endereços estáticos para o meu domínio arg.eti.br, usando o software BIND. Resolvi, então, configurá-lo para servir também como DNS dinâmico.

Configurei o cliente (meu computador de casa) e o servidor DNS para fazerem o seguinte:

  1. O cliente precisa saber qual é o seu endereço IP atual atribuído pelo provedor de Internet. Para descobrir o seu endereço IP atual, o cliente pergunta para vps1.arg.eti.br:9999, onde roda um serviço que simplesmente retorna o endereço IP do cliente que conectar nele. Você pode usar esse serviço, se quiser.

  2. O cliente pergunta ao servidor DNS qual o endereço IP que o servidor DNS tem registrado para o cliente.

  3. O cliente compara o seu endereço IP atual atribuído pelo provedor de Internet com o endereço IP informado pelo servidor DNS e, se forem diferentes, o cliente solicita ao servidor DNS que atualize o registro do endereço IP do cliente.

No cliente

No cliente, fiz o seguinte:

Gerei uma chave para ser usada pelo programa nsupdate:

$ dnssec-keygen -A HMAC-MD5 -b 512 -HOST cliente.dynamic.arg.eti.br.

Criei um shell script chamado nsupdate.sh:

#!/bin/sh

ZONE="dynamic.arg.eti.br"
HOST="cliente.$ZONE"
DNS_SERVER="ns1.arg.eti.br"
WIMI_SERVER="vps1.arg.eti.br" #WIMI=What Is My IP
WIMI_PORT=9999
SLEEP_OK=300
SLEEP_ERR=60

while true
do
    IP_DNS=$(host -t A $HOST $DNS_SERVER | egrep -o "([0-9]{1,3}\.){3}[0-9]{1,3}$")
    IP_WAN=$(nc $WIMI_SERVER $WIMI_PORT | egrep -o "^([0-9]{1,3}\.){3}[0-9]{1,3}$")
    echo "$(date) IP_DNS=$IP_DNS, IP_WAN=$IP_WAN"
    if [ -z "$IP_DNS" ]
    then
        echo "erro ao obter IP de $HOST via DNS"
        sleep $SLEEP_ERR
        continue
    fi
    if [ -z "$IP_WAN" ]
    then
        echo "erro ao obter IP da WAN"
        sleep $SLEEP_ERR
        continue
    fi
    if [ "$IP_DNS" != "$IP_WAN" ]
    then
        echo "atualizando novo IP $IP_WAN"
        echo \
"server $DNS_SERVER.
zone $ZONE.
update delete $HOST. A
update add $HOST. 900 A $IP_WAN
show
send
" | nsupdate -k K$HOST.+157+49186.private
    fi
    sleep $SLEEP_OK
done

Rodei o script nsupdate.sh (ainda preciso melhorar a chamada do script para executá-lo automaticamente ao inicializar o computador):

$ nohup ./nsupdate.sh &

No servidor

No servidor, para configurar o BIND, fiz o seguinte:

Criei o arquivo /etc/bind/keys.conf:

key cliente.dynamic.arg.eti.br. {
        algorithm HMAC-MD5;
        secret "<copie aqui a chave; ela segue o texto 'Key:' no arquivo .private gerado pelo dnssec-keygen>";
};

Incluí o arquivo /etc/bind/keys.conf no arquivo /etc/bind/named.conf.local, antes das declarações de zonas:

include "/etc/bind/keys.conf";

Incluí no arquivo /etc/bind/named.conf.local uma nova zona dynamic.arg.eti.br:

zone "dynamic.arg.eti.br" {
        type master;
        file "/var/cache/bind/db.dynamic.arg.eti.br";
        update-policy {
                grant cliente.dynamic.arg.eti.br. name cliente.dynamic.arg.eti.br. A;
        };
};

Criei um arquivo /var/cache/bind/db.dynamic.arg.eti.br para a zona dynamic.arg.eti.br:

;
; BIND data file for dynamic.arg.eti.br zone
;
$TTL    15m
@       IN      SOA     ns1.arg.eti.br. hostmaster.arg.eti.br. (
                        1               ; Serial
                        1d              ; Refresh
                        15m             ; Retry
                        1w              ; Expire
                        15m )           ; Negative Cache TTL
;
@       IN      NS      ns1.arg.eti.br.

Testei a nova configuração:

sudo -u bind named-checkzone -D dynamic.arg.eti.br /var/cache/bind/db.dynamic.arg.eti.br
sudo -u bind named-checkconf -p -z

Apliquei a nova configuração:

sudo -u bind rndc reload

Atenção: caso seja necessário editar manualmente o arquivo /var/cache/bind/db.dynamic.arg.eti.br após ter sido aplicada a nova configuração, deve-se fazer assim:

sudo -u bind rndc freeze dynamic.arg.eti.br
sudo -u bind vi /var/cache/bind/db.dynamic.arg.eti.br
sudo -u bind named-checkzone -D dynamic.arg.eti.br /var/cache/bind/db.dynamic.arg.eti.br
sudo -u bind named-checkconf -p -z
sudo -u bind rndc thaw dynamic.arg.eti.br

Também no servidor, configurei o serviço que responde o endereço IP do cliente que conectar nele:

Criei o arquivo ~/src/whatismyip/whatismyip.py:

#!/usr/bin/python

import SocketServer
import datetime
import time

class MyTCPHandler(SocketServer.BaseRequestHandler):
    def handle(self):
        print str(datetime.datetime.now()), self.client_address[0]
        self.request.sendall(self.client_address[0])
        time.sleep(1)

if __name__ == "__main__":
    HOST, PORT = "vps1.arg.eti.br", 9999
    print "servidor iniciado em %s:%d" % (HOST, PORT)
    server = SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
    server.serve_forever()

Rodei o script whatismyip.py (ainda preciso melhorar a chamada do script para executá-lo automaticamente ao inicializar o computador):

$ nohup ./whatismyip.py &

Bem, ainda não desativei os dois serviços de DNS dinâmico que eu usava, mas já estou usando o meu novo serviço "feito em casa" ;-)