FreeDNSに登録しているDNSレコードを更新するDynamicDNSクライアントを作成_Second
はじめに
DynamicDNSサービスに元々MyDNS.JPを使用していましたが、なぜか1レコード分以外のレコードがTXTレコードになってしまい、外部から自宅サーバへアクセスできなくなっていたので、FreeDNSに乗り換えてみました。
追記:エキスパートPythonとPython v2.6.2のマニュアルにも目を通し、おかしなところとか、Pythonは、GIL(Global Intepreter Lock)の問題とかで、同時実行できるスレッドが一つに限定されてしまうので、マルチスレッドの代わりに、マルチプロセスをよく利用するとなっているので、それも変更しました。
またDNS更新クライアントとして使用していた機器が故障し、普通のPCから更新することにしました。
手順
前提
DynamicDNSクライアント環境
sheevaplug(電源プラグ型LinuxPC: FlashROM)常時電源ONにするとデーモンがダウンしたりするため除外- DesktopPC(2002年くらいのノーブランド)
- Ubuntu10.04
- python v2.6
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が一番簡単でしたので、それを使用しました。
利点
欠点
- 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
追記:
マルチプロセスを扱うワーカークラスを作成
#!/usr/bin/env python # -*= coding:utf-8 -*- from multiprocessing import Process import urllib2 class AsyncWorker(Process): def __init__(self, url, timeout): Process.__init__(self) self.url = url self.timeout = timeout def run(self): try: print 'Connecting URL: {url} ...'.format(url=self.url) res = urllib2.urlopen(url=self.url, timeout=self.timeout) except Exception, e: raise e else: print 'URL: {url} update successfully.'.format(url=self.url) return True
ワーカーを使用して処理を行う関数
# Update the dynamic dns records using API def update(responsetext, timeout): from xml.etree.ElementTree import ElementTree from cStringIO import StringIO try: # xml.etree.ElementTree requires FileObject output = StringIO(responsetext) tree = ElementTree() tree.parse(output) urls = tree.findall("item/url") # Record update from worker import AsyncWorker tasks = [AsyncWorker(u.text, timeout) for u in urls] for t in tasks: t.start() # wait for the background tasks to finish for t in tasks: t.join() print 'Dynamic dns update tasks has finished.' except Exception, e: raise e finally: output.close()
リスト内包表記とか使用しているものの、ワーカーの書き方とかSJC-P(Java)とかで勉強したものそのままひっぱてきた表現になってしまいました。
設定ファイルを読み込むクラスのメソッドの返却値も自分自身(self)を返却するようにして、メソッドチェーンっぽく続けて処理できるようにしてみました。