RT-200NE DynDNS 更新用プログラム

misima が利用できないとの書き込みが,今日,BBS にあった。なんのことはない,ルータ RT-200NE の WAN 側アドレスが変わってしまったため,ダイナミック DNS から引いた IP で私のサイトにアクセスできなくなっていたのだった。ログを確認すると元日からアウトだったようである。

お昼に年賀状を書き,夕方つれづれなるままに,RT-200NE から IP アドレス情報を取得してダイナミック DNS を自動更新するプログラムに取り組んだ。RT-200NE は Telnet 設定をサポートしていないので,YAMAHA ルータで使っていた従来の自動更新プログラムが利用できなくなったままこれまで放置していたのだ。Web 設定インタフェースしかもたない RT-200NE ではプログラム修正は面倒かと予想していたが,Perl libwww ライブラリを使って実際は簡単にできてしまった。

下にその Perl コードを掲げておくので,私と同様,Bフレッツで RT-200NE を NTT からレンタルしている方は参考にしてください。前回実行時にセットした IP アドレスを今回ルータから取得した IP と比較し変更があったら DynDNS の登録 IP を更新する。前回更新から一ヶ月経過していれば,DynDNS 期限切れ抹消を回避するため,IP に変更がなくても更新する。DynDNS を更新したあと,DNS リゾルバで確認し,結果を管理者にメールで通知する。これを crontab に仕込んで一定の時刻に実行するわけである。RT-200NE のみならず Web で設定するルータに応用が可能だと思う。ただし,このコードは UNIX マシン用であり,DynDNS 更新プログラム ddup がインストールされていることが前提である。LWP::UserAgent 以外にも,Net::DNS,Mail::Sender などの Perl モジュールが必要である。ユーザの環境に応じてユーザID,パスワード,プロバイダ名などの変数を適宜修正しないと,このままでは動作しないので注意。また,"→" は折り返しを示す便宜的な記号なので,これなしに一行でコードを記述しなければならない。

#!/usr/bin/perl
# -*- coding: utf-8; mode: cperl -*-
# DynDNS update utility
# Copyright(c) 2007, isao yasuda, All Rights Reserved.
#
# DESCRIPTION
# -----------
# 1. ルータの Web 接続情報頁を取得する。
# 2. 接続情報から現在の WAN 側 IP アドレスを解析する。
# 3. 以下の場合,DynDNS ドメイン情報を DDUP ユーティリティで更新する。
# (1) ルータ IP が変更になった場合
# (2) 前回更新から月が変わった場合
#    いずれでもない場合は何もしないで終了する。
# 4. 更新後 DNS リゾルバで確認する。
# 5. 更新/DNS 確認結果を管理者にメール送信する。
#
# HISTORY
# -------
# - 2001/05/26 Initial version for YAMAHA RTA-52i. Telnet IF.
# - 2002/10/25 Revised for PLANEX BRL-04F. Telnet IF.
# - 2003/07/18 Revised for YAMAHA RTA-55i. Telnet IF.
# - 2004/12/11 DNS 確認,メール送信追加.
# - 2008/01/02 Revised for NTT RT-200NE. WWW IF.
#
use strict;                                  # Strict check
use utf8;                                    # UTF-8 handling
use Encode;                                  # Code convert
use LWP::UserAgent;                          # libwww
use Net::DNS;                                # DNS module
use Mail::Sender;                            # Mail module
 
# 以下変数を適宜修正する
my $mydomain = 'mydomain.homeip.net';        # DynDNS ドメイン名
my $host     = 'rt200ne';                    # RT-200NE hostname
my $page     = 'info_main.html';             # RT-200NE info page
my $URL      = "http://$host/$page";         # RT-200NE info URL
my $username = 'user';                       # RT-200NE ユーザID
my $password = 'pass';                       # RT-200NE パスワード
my $provider = 'prov';                       # RT-200NE プロバイダID
my $smtpsv   = 'my.smtp.server';             # SMTP ホスト
my $mailfrom = 'dareka@dot.net';             # Mail address from
my $mailto   = 'dareka@dot.net';             # Mail address to
my $mailsub  = "DynDNS update report";       # Mail Subject
my $ipfile   = "/tmp/IPREC";                 # IP addres record
my $log      = "/var/log/ipaddress.log";     # Log file path
my $ddup     = "/usr/local/sbin/ddup";       # ddup プログラムパス
my $date     = `/bin/date '+%D %H:%M:%S'`;   # now date
my ($oldmonth, $newmonth, $day, $year);      # date item
my ($newIPaddr, $rc);
 
#
# ログファイル,初期処理
#
open(LOG, ">> $log");
chomp($date);
 
#
# RT-200NE から接続情報を取得し,現在の WAN 側 IP アドレスを解析する。
#
my $ua = LWP::UserAgent->new;
# RT-200NE 接続情報頁の URL をセット
my $req = HTTP::Request->new(GET => $URL);
# ルータ認証用管理者ID,パスワードをセット
$req->authorization_basic($username, $password);
my $response = $ua->request($req)->as_string;
# ルータ出力頁をデコード
$response = decode('shiftjis', $response);
# DOS形式をUNIX形式にテキスト修正
$response =~ s/\r\n/\n/gm;
# 接続情報中の目的とするプロバイダ接続IPを正規表現で取得
if ($response =~ /<TR><TD NOWRAP COLSPAN\=2 →
BGCOLOR\=white><B><LABEL onMouseOver\=msgShow\(event,33\) →
onMouseOut\=msgHide\(\)>【接続先1 \[$provider\] 状態】 →
<\/LABEL><\/B><\/TD><\/TR>[^<]*<TR →
BGCOLOR\=THISTLE>\n\s+<TD><LABEL →
onMouseOver\=msgShow\(event,11\) →
onMouseOut\=msgHide\(\)>WAN[^<]*<\/LABEL><\/TD>→
[^<]*<TD>([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*<\/TD>/gm) {
    $newIPaddr = $1;                     # 現在の WAN 側 IP アドレス
} else {
    print LOG "$date IP address was not found in info-page.\n";
    doexit(1);
}
 
#
# 前回変更したときの情報を読み込み,IP アドレスと日付(月)を取得する。
#
my $IPrec = `/bin/cat $ipfile`;          # read stored record
(my $olddate, my $oldIPaddr) = split(/,/, $IPrec);
($oldmonth, $day, $year) = split(/\//, $olddate, 3);
($newmonth, $day, $year) = split(/\//, $date, 3);
chomp($oldIPaddr);
 
#
# 前回の IP アドレスと,現在のルータ IP アドレスが一致するか,
# 月が変わっているか,確認する。
#
if ($oldIPaddr eq $newIPaddr) {          # 前回/今回 IP アドレス比較
    if ($newmonth ne $oldmonth) {        # 月比較
        print LOG "$date IP address is to be refreshed: $newIPaddr\n";
        $rc = 2;
    } else {                             # IP,月ともに変更なければ終了
        print LOG "$date IP address is not changed: $oldIPaddr\n";
        doexit(3);
    }
} else {                                 # IP アドレスが変わった場合
    print LOG "$date IP address is changed: $oldIPaddr -> $newIPaddr\n";
    $rc = 0;
}
 
#
# DDUPユーティリティでドメイン情報を更新する。
#
my $result = "$ddup --host $mydomain --wildcard --ip $newIPaddr --debug\n";
$result .= `$ddup --host $mydomain --wildcard --ip $newIPaddr --debug`;
 
# IPレコードを更新 (次回実行時比較のため)
open(IP, ">$ipfile");
print IP "$date,$newIPaddr";             # update stored IP record
close(IP);
 
#
# 更新が成功したか,実際に DNS リゾルバで確認する。
#
my $res = new Net::DNS::Resolver;
my $query = $res->search($mydomain);
 
if ($query) {
    foreach my $rr ($query->answer) {
        next unless $rr->type eq "A";
        $result .= "\n\nDomain\t: " . $mydomain . "\n" .
            "IP address\t: " . $rr->address . "\n";
        $mailsub .= " Suceeded! " . $date;
    }
} else {
    $result .= "\n\n*** Query failed: " . $res->errorstring . "\n";
    $mailsub .= " Failed! ". $date;
    if ($rc%2 == 0) {
        $rc = 4;
    } else {
        $rc = 5;
    }
}
 
#
# 更新/DNS 確認結果を管理者にメールする。
#
$Mail::Sender::NO_X_MAILER = 1;          # no X-Mailer
my $mailer = new Mail::Sender;
$mailer->MailMsg({smtp =>    $smtpsv,
                  from =>    $mailfrom,
                  to =>      $mailto,
                  subject => $mailsub,
                  msg =>     $result});
 
if ($Mail::Sender::Error) {
    print LOG "$date Mailing Error: " . $Mail::Sender::Error . "\n$result\n";
    if ($rc%2 == 0) {
        $rc = 6;
    } else {
        $rc = 7;
    }
} else {
    print LOG "$date Mailed to $mailto.\n$result\n";
}
 
#
# 後始末。
#
doexit($rc);
 
#
# ログをクローズ,リターンコードをセットして終了
# リターンコード: 偶数=DNS更新実行; 奇数=未更新,エラー検知
sub doexit {
    my $r = shift;
    close(LOG);
    exit($r);
}

※ 2009.2.4 付記
現在 FreeBSD ports では ddup は配布されていない。ddclient というダイナミック DNS 更新ユーティリティがすでに一般的になっており,現在はこれを利用することをお勧めする。記事『メールサーバ大往生・ddclient インストール』を参照。