FreeDNSに登録しているDNSレコードを更新するDynamicDNSクライアントを作成

はじめに

DynamicDNSサービスに元々MyDNS.JPを使用していましたが、なぜか1レコード分以外のレコードがTXTレコードになってしまい、外部から自宅サーバへアクセスできなくなっていたので、FreeDNSに乗り換えてみました。

FreeDNSの特徴
  • MXレコード、Aレコード、CNAMEレコード、AAAAレコード等ほぼすべて対応している
  • DNSレコードのバックアップサーバとして自分でたてたDNSサーバを指定できる
  • wget等HTTPアクセスでDNSレコードを更新できる
  • XML APIが用意されており、自分でDynamicDNSクライアントを作成できる

手順

前提
  • 外部公開とかメンテとかで外部からサーバにアクセスしたいが、固定グローバルIPなんぞは不要っていう人
  • FreeDNSサービスにユーザ登録し、DNSのレコードを登録していること(Aレコード,MXレコード, AAAAレコードでも)
DynamicDNSクライアント環境
XML API取得

下記のURLにアクセスすると、XML形式でレコード更新用URLが取得できます。
(shaの部分は、登録ユーザ名+パスワードを使用してSHA-1で暗号化した文字列が入ります。)

URL: http://freedns.afraid.org/api/?action=getdyndns&sha=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx&style=xml

SHA-1暗号化

暗号化文字列は、username + pipe + passwordをSHA-1で暗号化すればよいようなので、
下記のような関数で暗号化文字列を作成します。

def gethashstr(username, password):

    import hashlib

    hashobj = hashlib.sha1()
    encstr = '|'.join([username, password])
    hashobj.update(encstr)
    hashstring = hashobj.hexdigest()
    return hashstring
更新用URLの取得

下記のように必要なパラメータを用意して

HASH_ARGO = "sha"
TARGET_PARAMS = {'action': "getdyndns", HASH_ARGO: None, 'style': "xml"}
TARGET_API = "http://freedns.afraid.org/api/"
TIMEOUT = 300 

urllib2を使ってDNSレコード更新用URLが記述されているXMLを取得しました。

def request(url, timeout, **params):

    query = ''
    for k, v in params.items():
        query += '?' if query == '' else '&' 
        query += '{key}={value}'.format(key=k, value=v)

    url += query

    import urllib2
    print 'Connecting URL: {url} ...'.format(url=url)
    res = urllib2.urlopen(url=url, timeout=timeout)
    print 'URL: {url} contents download finished.'.format(url=url)
    responsetext = res.read()
    return responsetext

TARGET_PARAMS[HASH_ARGO] = gethashstr(hoge, hogepass) 
response = request(TARGET_API, TIMEOUT, TARGET_PARAMS)

XMLからレコード更新用URL抽出

responsetextに取得したXMLが格納されているとして、XMLから更新用URLを抽出します。
XMLの解析モジュールは、dom,saxとかいろいろあったのですが、xml.etree.ElementTreeが一番簡単でしたので、それを使用しました。

利点

  • xpath風に目的のノードを取得できる
    • 下記のXMLから複数のurlのテキストを抽出する場合["item/url"](イテレータオブジェクトが返却される)

欠点

  • XML文字列をそのまま渡すと例外が発生する(ファイルオブジェクトを渡す必要あり)
    • しかし、StringIOで渡せば問題なく対処できる。
    • 通常のPCサーバであればXMLをtempfileにリダイレクトして渡せばよいと思います。
<root>
  <item>
    <host>www.hoge.com</host>
    <address>71.1.1.1</address>
    <url>http://freedns.afraid.org/dynamic/update.php?T3faGkljdax==</url>
  </item>
  <item>
    {...}
    <url>http://freedns.afraid.org/dynamic/update.php?jklIIUadaaa==</url>
  </item>
</root>
def update(responsetext, timeout):
    from xml.etree.ElementTree import ElementTree
    from StringIO import StringIO

    try:
        # xml.etree.ElementTree requires FileObject
        output = StringIO(responsetext)
        tree = ElementTree()
        tree.parse(output)
        urls = tree.findall("item/url")
DNSレコード更新

XMLから抽出した更新用URLにアクセスするだけでレコードの更新ができます。

for url in urls:
    res = urllib2.urlopen(url)

あとは、これをcrontabに1時間に1回動作するように設定しておけばいいのではないかと。
$ crontab -l
0 * * * * python /var/cron/freedns/freednsupdate.py

参考にしたもの

404 Not Found

pythonまじめに覚えようと思って、注文してみました。
ISBN:4048686291