quinta-feira, 23 de fevereiro de 2006

Script Ruby para detectar o IP e actualizar o DNS

O IP atribuído pela Netcabo ao meu computador de casa, e onde está alojado este blog, é dinâmico. De tempos a tempos o nº do endereço muda. Para ter sempre o meu domínio (eduardoramalho.com) correctamente mapeado para o IP actual da máquina (DNS), tenho que recorrer a uma script Perl que arranjei na net.

O seu modo de funcionamento é simples mas engenhoso. A script está configurada para correr a cada hora. De cada vez, acede a uma página web interna de configuração do meu router. Analisa essa página, retira de lá o IP actual e compara com o IP actualmente mapeado. Se é diferente, então é porque o IP mudou, logo actualiza de imediato os servidores de DNS.

Acontece que com o recente update do firmware do meu router, para o DD-WRT, essa página deixou de existir. A informação do IP passou a estar noutra mas com uma estrutura de texto diferente, a qual a script Perl não conseguia interpretar.

Estava aqui o pretexto ideal para voltar a pegar na linguagem Ruby. Em vez de tentar corrigir a script original, vou a reimplementar em Ruby.

E o resultado foi este - ip_update.rb. Provavelmente ainda não estou a aproveitar ao máximo as facilidades disponibilizadas pela linguagem, mas estes últimos dias deu para aprender um pouco mais sobre as suas bibliotecas e potencialidades.

Se tiveres (sugestões|dicas|criticas) de forma a melhorar esta script, fico desde já agradecido com um comentário a este "post".
#!/usr/bin/ruby -w
# This class checks if external host IP has changed since last time.
# To be used when ISPs assign dynamic IPs to clients.

require 'net/http'
require 'uri'
require 'fileutils'

# Turn off output buffering
$stdout.sync = true
$stderr.sync = true

class IpUpdater

  include FileUtils
  
  # Constructor
  def initialize
    @domain = '<WRITE_HERE_YOUR_DOMAIN>'  # ex: something.com
    @passwd = '<WRITE_HERE_YOUR_PASSWD>' # ex: secret phrase
    @ipUpdateHttpCmdTemplate = "http://dynamic.name-services.com/interface.asp?command=setdnshost&zone=DOMAIN&address=IP&DomainPassword=PASSWD"
    @routerIp = '<WRITE_HERE_YOUR_ROUTER_IP>' # 192.168.1.1
    @lastipfile = 'lastip.txt'   # filename that holds current registered IP in DNS
  end

  # Get IP from router web page
  def getCurrentIp
    ip = "0.0.0.0"
    url = URI.parse('http://192.168.1.1/')
    req = Net::HTTP::Get.new(url.path)
    res = Net::HTTP.start(url.host, url.port) do |http| 
      http.request(req)
    end
    line = res.body
    
    # search for line where is ip
    wanline = line.scan(/WAN IP:.*</)
    # take just the ip
    wanip = wanline[0]
    length = wanip.length
    ip = wanip[7..length-2].lstrip
    return ip
  end

  # Get last registered IP
  def getLastSavedIp
    ip = ""
    if File.exist?(@lastipfile)
      File.open(@lastipfile, "r") do |file|
        file.each_line do |line|
          # chomp was necessary to take out the "\n" from line
          ip = line.chomp
        end
      end
    end
    return ip
  end  
  
  # Update DNS servers with new IP
  def updateDnsIp(ip)
    ipUpdateHttpCmd = @ipUpdateHttpCmdTemplate.gsub(/IP/, ip)
    ipUpdateHttpCmd.gsub!(/DOMAIN/, @domain)
    ipUpdateHttpCmd.gsub!(/PASSWD/, @passwd)
    
    puts("Command: #{ipUpdateHttpCmd}")
    
    url = URI.parse(ipUpdateHttpCmd)
    req = Net::HTTP::Get.new(url.path + "?" + url.query)
    res = Net::HTTP.start(url.host, url.port) do |http|
      response = http.request(req)
      puts response.body
    end
  end

  # updates the file that holds current DNS IP
  def updateLastSavedIp(ip)
    File.open(@lastipfile, "w") do |file|
      file.puts(ip)
    end
  end

  # the script logic steps
  def run
    lastIp = getLastSavedIp
    currIp = getCurrentIp
    
    if (currIp == nil)
      puts("Could not get current IP. Exit!")
      return 1
    end
    
    puts("Last IP: #{lastIp}")
    puts("Curr IP: #{currIp}")
    
    # When comparing strings, take care of hidden caracters like "\n".
    # I "chomped" strings earlier just in case or else the following "if"
    # would always fail.
    # If something is unexpect while comparing strings, output the string 
    # like "put lastIp.inspect".
    if lastIp === currIp
      puts "IPs are the same. Do nothing."
      return 0
    end
        
    puts "IPs are different. Updating!"
    
    updateDnsIp(currIp)
    updateLastSavedIp(currIp)
    
    return 0
  end
  
end

# probably this is our main
ipUpdater = IpUpdater.new
exitCode = ipUpdater.run

$stdout.flush

exit(exitCode)