はじめに
- そのまま使えるコードはありません
- 実際の環境にばらつきがあると思われるため…
- 方式がいろいろ考えられるため、ほんの一例
- 異常系や例外は未考慮…
今更DDNS
DDNSサービスはいろいろあるが、自分のルーターにはDDNSサービスがバンドルされていない。
DDNSを自前でやるにも、方式がいろいろありそう。
どうやるのが良いのだろう。
前提
- サーバーは、BIND9(RFC2136に対応していれば、何でもよさそう)
- クライアントは、Ubuntu 16.04(systemdが動けば、何でもよさそう)
- ルーターは、SEIL/x86(IPアドレスを取得する方式によっては、何でもよさそう)
- ホスト名をつける対象は、PPPoE接続のグローバルIPv4アドレス
コンセプト
- なるべくOSやルーターに依存しない、汎用的な方法を探る
- なるべくDDNS専用のサーバープロセスなどを立てない
- つまり、手抜き
DDNSクライアント
DDNSクライアントの機能は、ざっくりこんな感じと想像。
- 外部のグローバルIPアドレスを得る
- そのIPアドレスをDNSサーバーに送る
- DNSレコードを書き換える
このうち、2.と3.は基本的にnsupdateでできるはず。
問題は1.。ルーターがSEIL/x86なので、ルーターにバンドルされたDDNSサービスが無い。
DDNSサービスに応じたクライアントはいろいろあるが、サービスにある程度依存していると思われる1。
今回はサーバーがただのBIND9なので、それに応じたクライアントを考えてみる。
主な凡例
- 203.0.113.1: 外部のグローバルIPアドレス
- 192.168.0.1: ルーターのプライベートIPアドレス
- hoge.dip.example.org: 外部のグローバルIPアドレスに対応したホスト名
- 203.0.113.254: DNSサーバーアドレス
外部のグローバルIPアドレスを得る方法
ルーターから取得する
ルーターに定期的にログインする
- メリット: ルーターはPPPoEで得たIPアドレスを確実に知っている
- デメリット: ポーリングのために定期的にログインする(そしてlogも荒れる)
SEILなら、show status ppp pppoe0
で表示できるのは知っていた。
SNMP非対応のRT57iを使っていた時にお世話になった、telnetで得た情報をMRTG化する記事を思い出した。
SEILにsshでコマンド送れるのかな…と思って実行したら、できてしまった。
$ ssh -i ~/.ssh/gw.local user@192.168.0.1 'show status ppp pppoe0'
7ffeffff: Interface: pppoe0
7ffeffff: LCP state: opened
7ffeffff: IPCP state: opened
7ffeffff: IPv6CP state: initial
7ffeffff: BCP state: initial
7ffeffff: LCP negotiated options:
7ffeffff: none
7ffeffff: IPCP negotiated options:
7ffeffff: address 203.0.113.1
7ffeffff: primary dns address 198.51.100.1
7ffeffff: secondary dns address 198.51.100.2
7ffeffff: IPv6CP negotiated options:
7ffeffff: none
7ffeffff: BCP negotiated options:
7ffeffff: none
7ffeffff: keepalive: 30 seconds interval
00000000: success
7ffe0000: exit(0).
$ ssh -i ~/.ssh/gw.local user@192.168.0.1 'show status ppp pppoe0' | sed -nr 's/^.*\s\s+address\s(.*)\s*$/\1/p'
203.0.113.1
sshの鍵をパスフレーズ無しで作ると、シェルスクリプトからも情報が取得できそうだ。sedの正規表現は適当。
ただ、色々謎がある。
- 7ffeffff: って?
-
show log
にerror telnet telnetd: sppp__ioctl:40: socket: Operation not permitted
が残る
同じことをRTX1200にやると、Received disconnect from 192.168.0.1 port 22:2: Channel request 'exec' is not supported
というエラー。VyOSではInvalid command: [show]
というエラー。
sshでなくtelnet+expectならうまく行く可能性もあるが、どちらにせよ無理やり感が残る。
SNMPで取得する
- メリット: ルーターの情報を取得する一般的な方法
- デメリット: 自分の知識不足
RT57iでは、SNMPが使えないのでtelnetで情報を得ていたのだった。
SEILならSNMPが使える。
そこで、snmpwalk。
$ sudo apt install snmp
:
$ snmpwalk -v 2c -c HogeHoge 192.168.0.1 | grep 203.0.113.1
:
iso.3.6.1.2.1.4.21.1.7.0.0.0.0 = IpAddress: 203.0.113.1
:
$ snmpget -v 2c -c HogeHoge 192.168.0.1 iso.3.6.1.2.1.4.21.1.7.0.0.0.0 | sed -nr 's/.*IpAddress: (.*)\s*$/\1/p'
203.0.113.1
アドレスが取れる。トラップを使えば割り込みにできるかもしれない。
ただ、ここで取得できた情報は、何を示しているのか。こちらを見ながらMIBを入れてみた後、
$ snmptranslate iso.3.6.1.2.1.4.21.1.7.0.0.0.0
:
RFC1213-MIB::ipRouteNextHop.0.0.0.0
すると、ipRouteNextHopとのこと2。インターフェースのアドレスというわけではなさそうだ。
PPPoEが接続できなかった場合は、どうなるのか…3。
##外部サーバーから取得する
- メリット: 簡単そう
- デメリット: 外部のサーバーに依存することになる
外側のグローバルIPアドレスは、インターネットにあるwebサーバーなどにアクセスすると、ログに残る。
ならば、webサーバーに教えてもらえばよいのでは。
こちらの記事によると、こういったサービスがいくつかあるようだ。
試しにifconfig.coを使ってみた
$ curl ifconfig.co
203.0.113.1
外部のサービスに依存したくない場合、自分のwebサーバーにアドレスを返すページを置いておいて、curlで取得する手もある。
PHPでは、<?php echo $_SERVER["REMOTE_ADDR"]; ?>
などで、返すことができるようだ。
ただ、この方法でも、webサーバーは必要となる。
とりあえず、この方式にするかな。
IPアドレスを送ってDNSレコードを書き換える
nsupdateで更新するにしても、外部のグローバルIPアドレスを取得した後、多少ロジックが必要だ。
アドレスの変化をポーリングする場合、例えば
- 今の外部のグローバルIPアドレスを取得する
- 今までのIPアドレスを取得する
- 両者が異なっていたら、nsupdateする
といったことを定期的に動かす必要がある。
定期的に動かすには、サービスにする必要がある。手抜きしたい…。
cron
最初に思った。もちろん使える。
systemd
systemdでも、サービスを簡単に作れるのでは。
探すと、こちらに詳しい記事が。
--user
で、ユーザーモードで動かせて、enable
もできる。
起動n分後に実行などもできそうなので、SEIL/x86の仮想マシンが起動して、PPPoEでアドレスを取得完了後、といったタイミングも狙えそう。
これならシェルスクリプトを定期実行できそうなので、クライアント全体はシェルスクリプトでで実現しようかな。
シェルスクリプトからのnsupdate
検索して出てくるnsupdateの例は、インタラクティブモードで、プロンプトからコマンドを入力しているものが多い。
nsupdateのマニュアルを見ると、インタラクティブモードでなく、標準入力やファイルでもコマンドを渡せる模様。
コマンドのテンプレートを用意しておき、アドレスをsedで置き換えて標準入力から渡してみる。
このマニュアルによると、アドレスの変更という操作は存在せず、一旦deleteしてからaddする必要がある模様。
設置ファイル
[Unit]
Description=Dynamic IP update service
[Service]
Type=simple
ExecStart=/bin/sh /home/hogehoge/bin/dip.sh
[Install]
WantedBy=default.target
[Unit]
Description=Dynamic IP update service
[Timer]
OnBootSec=3min
OnUnitActiveSec=30min
Unit=dip.service
[Install]
WantedBy=timers.target
#!/bin/sh
#PPP_IP=`ssh -i /home/hogehoge/.ssh/192.168.0.1 user@192.168.0.1 'show status ppp pppoe0' | sed -nr 's/^.*\s\s+address\s(.*)\s*$/\1/p'`
#PPP_IP=`snmpget -v 2c -c HogeHoge 192.168.0.1 iso.3.6.1.2.1.4.21.1.7.0.0.0.0 | sed -nr 's/.*IpAddress: (.*)\s*$/\1/p'`
#PPP_IP=`curl -s ifconfig.co`
PPP_IP=`curl -s http://www.example.org/~hogehoge/remote-addr.php`
DNS_IP=`dig @203.0.113.254 hoge.dip.example.org A | sed -nr 's/^hoge\.dip\.example\.org.*IN\tA\t(.*)\s*$/\1/p'`
if [ -n "$PPP_IP" -a \( -z "$DNS_IP" -o "$PPP_IP" != "$DNS_IP" \) ]; then
cat /home/hogehoge/bin/nsupdate.txt | sed "s/NEW_IP/$PPP_IP/" | nsupdate -k /home/hogehoge/.ssh/Khoge.dip.example.org.+xxx+yyyyy.key
fi
server 203.0.113.254
update delete hoge.dip.example.org A
update add hoge.dip.example.org 600 A NEW_IP
send
<?php echo $_SERVER["REMOTE_ADDR"]; ?>
サービス開始
# ファイル読み込み
$ systemctl --user daemon-reload
# ファイル読み込み状況確認
$ systemctl --user list-unit-files
# 実行
$ systemctl --user start dip.service
$ systemctl --user start dip.timer
# 実行状況確認
$ systemctl --user status dip.service
$ systemctl --user status dip.timer
# 有効化
$ systemctl --user enable dip.service
$ systemctl --user enable dip.timer
感想
結局ごちゃごちゃしてしまった…
GnuDIPとか入れたほうがよかったかしら…
でも色々勉強になった。
宿題他
- 旧IPアドレス取得は、毎回DNS検索という手抜き
- PPPoEが接続できない間は、何が起こるんでしょうね…(未確認)
- shの条件式があやしい
- sed -rってMacOSでは使えないのね…