DynDNSのIPを更新するRubyスクリプト

DynDNS使っています

最近のエントリから察せられるように、VineLinuxを入れたPCで自宅サーバを試しています。そして、ダイナミックDNS対策にDynDNSを使っているのですが、IP割り当てが一定時間ごとに変わる一般プロバイダを通していると、定期的にDynDNSへのIPの登録を変更する必要があります。
それを自動で行ってくれるプログラムがいくつか公開されています。

まず、ndyndnsを使ってみようと、インストールして試してみたのだが、グローバルIPをうまく取得できなかった。

DynDNS Update Clientを作ってみる

ndyndnsはC言語で書かれていて、またソースが公開されているので、ざっくりと中身の流れは分かっていた。それを頭に入れRubyで書いてみることにしました。
処理の流れは、以下の3ステップ。

  1. 現在のグローバルIPアドレスを取得
  2. 以前設定したIPアドレスと比較
  3. 変化があるか?
    1. なければ終了
    2. あれば、それを登録して、そのIPアドレスをメモしておく

グローバルIPを取得する

ルータにアクセスしてグローバルIPを取得する、という方法もあったのですが、解析に手間取りそうで却下。
DynDNSがIPを返してくれるページを設置してくれているのでそれを使うことにしました。

また、VALUE-DOMAINも同様なページを設置しています。

このページを取得したら正規表現で抜き出せば、現在のグローバルIPが取得できます。

Basic認証を行う

Rubyでページを取得するには、"open-uri"という便利なライブラリがあるのですが、Basic認証を超えて取得する方法が分かりにくかったので以下のページを参考に実装しました。

具体的には以下のような内容になります。

require "open-uri"
auth = "Basic "+["#{Username}:#{Password}"].pack("m").chomp
hashAuth = {
  "Authorization" => auth,
  "User-Agent"    => "update_dyndns.rb"
}
open( "http://need-basic.example.com/", hashAuth )

ハマったポイントは以下の2点。

  • packした文字列をchompする
  • "User-Agent"を設定しておく(DynDNSにつながるのに必要)

update_dyndns.rb

以上の情報をまとめて書いたコードが以下のものです。
使用するには、コードの冒頭に羅列している"DynDNS_"の定数を定義してから実行してみてください。
(これをcronで5分置き程度に実行するように設定すれば完了です。)

#!/usr/bin/ruby

DynDNS_Username = "username"
DynDNS_Password = "password"
DynDNS_Hostname = 'host.dyndns.biz'
DynDNS_Wildcard = 'NOCHG' # 'ON', 'NOCHG', 'OFF'
DynDNS_MX       = 'NOCHG' # 'NOCHG'
DynDNS_Backmx   = 'NOCHG' # 'YES', 'NO', 'NOCHG'

StoredIP_Filename = "/usr/local/bin/StoredIP.txt"

#------------------------------------------------------------
# 現在のグローバルIPを取得する.
# エラー時には"-1"を返します.
#------------------------------------------------------------
def getCurrentIP
  require "socket"
  require "open-uri"

  #urlToGetIP = "http://dyn.value-domain.com/cgi-bin/dyn.fcg?ip"
  urlToGetIP = "http://checkip.dyndns.com"

  reIP = /(\d+)\.(\d+)\.(\d+)\.(\d+)/ # IPアドレスの正規表現.

  begin
    return open( urlToGetIP ).read.slice( reIP )
  rescue
    return "-1" # error.
  end
end

#------------------------------------------------------------
# 前回取得していたIPを取得する.
# 取得していなければ "-1" を返します.
#------------------------------------------------------------
def getStoredIP
  begin
    f_ip = File.open( StoredIP_Filename, "r" )
    return f_ip.gets.rstrip # 1行目にIPを記述しています.
  rescue
    return "-1"
  end
end

#------------------------------------------------------------
# 現在のグローバルIPでアップデートします.
# @retval true 成功.
#------------------------------------------------------------
def updateDynDNS( ip )
  require "open-uri"

  auth = "Basic " +
    ["#{DynDNS_Username}:#{DynDNS_Password}"].pack("m").chomp
  hashAuth = {
    "Authorization" => auth,
    "User-Agent"    => "update_dyndns.rb"
  }
  uriUpdate = "http://members.dyndns.org/nic/update?" +
    "hostname=#{DynDNS_Hostname}" +
    "&myip=#{ip}" +
    "&wildcard=#{DynDNS_Wildcard}" +
    "&mx=#{DynDNS_MX}" +
    "&backmx=#{DynDNS_Backmx}"

  begin
    print "start update [#{uriUpdate}]\n"
    open( uriUpdate, hashAuth )
    print ".. update success.\n"
    return true
  rescue Exception => err
    print "update error: [#{err}]\n"
    return false
  end
end

#------------------------------------------------------------
# ファイルに格納します.
#------------------------------------------------------------
def storeIP( ip )
  begin
    f_ip = File.open( StoredIP_Filename, "w" )
    f_ip.puts "#{ip}\n"
    f_ip.puts Time.now.to_s
    f_ip.close
  rescue
    print "error to store ip\n"
  end
end

#------------------------------------------------------------
# 実際の処理.
#------------------------------------------------------------
storedIP = getStoredIP()
currentIP = getCurrentIP()
print "storedIP:  [#{storedIP}]\n"
print "currentIP: [#{currentIP}]\n"
if( storedIP != currentIP )
  print "need to update.\n"
  if updateDynDNS( currentIP ) == true
    # 成功.
    storeIP( currentIP )
  else
    # 失敗.
    print "ERROR: failed to update.\n"
  end
else
  print "no need to update.\n"
end

参考書籍

プログラミングRuby 第2版 言語編

プログラミングRuby 第2版 言語編

Rubyレシピブック 第2版 268の技

Rubyレシピブック 第2版 268の技