🤖

自宅サーバーからMyDNSへのIP通知をPythonで書いてダイナミックDNSを実現する

2023/07/26に公開

僕は自宅サーバーを会社の有志に公開しているのですが、家のネット契約上Global IPが定期的に変わってしまうため、MyDNS.jpでダイナミックDNSを設定しています。

以前はMyDNSに定期的にログインするシェルスクリプトを作ってsystemdのタイマーで制御していたのですが、サーバOSのリプレースを機にPythonで書き直すことにしました。

僕はプロのプログラマとして会社で仕事をしているわけではなく、日曜プログラマーとしてゆるくやっているだけの人間なので、何か不備があるかもしれません。間違いや他に良いアイディアがありましたら気軽に教えて頂けると嬉しいです!

環境

OS: Proxmox VE 7.3-3
Python: 3.9.2

Pythonコード

Pythonコードのパス: /root/mydns.py
現在MyDNSに登録中のIPを保存するファイルのパス: /root/current_ip
ログファイルのパス: /root/mydns-notification.log

current_ip, mydns-notification.logファイルはあらかじめ作成しておいてください。

import sched
import time
import datetime

import requests
from requests.auth import HTTPBasicAuth

url = 'https://ipv4.mydns.jp/login.html'
username = '<username>'
password = '<password>'

count = 145
def notify(scheduler):
    global count
    scheduler.enter(300, 1, notify, (scheduler,))
    ip_addr = requests.get('http://ipinfo.io/json').json()['ip']
    
    with open("current_ip", "r") as f:
        current_ip = f.readline()

    if current_ip != ip_addr:
        response = requests.get(url, auth=HTTPBasicAuth(username, password))
        action = "MyDNSへ変更登録"
        if response.status_code == 200:
            result = "成功"
        else:
            result = "失敗"
        with open("current_ip", "w") as f:
            f.write(ip_addr)
        count += 1
    elif count >= 144:
        response = requests.get(url, auth=HTTPBasicAuth(username, password))
        action = "MyDNSへの定期ログイン"
        if response.status_code == 200:
            result = "成功"
        else:
            result = "失敗"
        count = 0
    else:
        action = "なにもせず"
        result = "成功"
        count += 1


    # Log Fileに履歴を書き込む
    with open('mydns-notification.log', 'a') as f:
        now = datetime.datetime.now()
        year, month, day, hour, minute = now.year, now.month, now.day, now.hour, now.minute
        f.write("{}年{}月{}日{}時{}分 ".format(year, month, day, hour, minute))
        f.write("MyDNS登録IP: {}, 取得IP: {}, Action {}, Result {}\n".format(current_ip, ip_addr, action, result))


my_scheduler = sched.scheduler(time.time, time.sleep)
my_scheduler.enter(1, 1, notify, (my_scheduler,))
my_scheduler.run()

コードの簡単な解説

MyDNSへのIP通知はログインページのBasic認証を通せば成立するので、

response = requests.get(url, auth=HTTPBasicAuth(username, password))

の1行で実現できます。

これを例えば5分など一定の間隔で叩けば、仮にglobal ipが変更になっても5分以内にはアクセス可能になります。

ただし毎回毎回ログインページを叩くのではMyDNSに余計な負荷をかけることになりますので、今回は

  • 半日に1回の定期ログイン
  • 自宅ネットワークのglobal ipが変わった時の緊急ログイン

の2つを設定することにしました。

タイマーはsched.schedulerモジュールを利用し、notify関数が実行されると真っ先に次回の実行が予約されるようにしています。

定期ログインはcountパラメータをglobal変数として設定し、毎回1加算をして144になったら定期ログイン実行という実装にしました。

global ip変更の検出は外部ファイル(/root/current_ip)に現在MyDNSへ登録中のIPを保持し、真のglobal ipは http://ipinfo.io/json のレスポンスをパースすることで取得しました。

ここまで押さえれば、後はシンプルなif分岐ですので理解は容易かと思います。

Logについては完全にオプションです。

Systemdへの登録

systemdに慣れてるのでsystemdにします。cronでも良いと思います。

/etc/systemd/system/mydns.service

[Unit]
Description = Notify IP to MyDNS

[Service]
ExecStart = python3 mydns.py
WorkingDirectory=/root
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target

以下のコマンドで有効化しサービス開始すれば完了です。

root@server:~# systemctl enable mydns
root@server:~# systemctl start mydns

ログファイルを確認

/root/mydns-notification.log
の内容を確認すると以下のような感じになってると思います。

20XX年Y月Z日10時0分 MyDNS登録IP: a.b.c.d, 取得IP: a.b.c.d, Action MyDNSへの定期ログイン, Result 成功
20XX年Y月Z日10時5分 MyDNS登録IP: a.b.c.d, 取得IP: a.b.c.d, Action なにもせず, Result 成功
20XX年Y月Z日10時10分 MyDNS登録IP: a.b.c.d, 取得IP: a.b.c.e, Action MyDNSへ変更登録 Result 成功
20XX年Y月Z日10時15分 MyDNS登録IP: a.b.c.e, 取得IP: a.b.c.e, Action なにもせず, Result 成功

Discussion