この記事は UdonTech Advent Calendar 2023の2日目の記事です。
| \ |∀ ゚) ダレモイナイ・・アナウメスルナラ イマノウチ |⊂ |
一般のご家庭ではISP様の気分次第で*1割り当てられたIPアドレスが変わってしまうので、 外部からアクセスしたいんだけど、IPアドレスを知るためには同時に家庭内LANにも居ないといけないという ジレンマに悩まされていました。
解決策としてDDNS(Dynamic DNS)というのがこれまた古くから使われているんですが、年に数回外出先からsshで帰宅する程度にしか使わないので 名前を割り当てられるほどの大掛かりなサービス要らんのだけどなぁという気持ちもあって、なんとなくアレ使うの嫌なんですよね。
そもそも、一般に公開するサービスを提供するわけじゃないので誰にでも分かりやすいアドレスなんてものも不要だし、 むしろ名前でアクセスできると攻撃を受ける頻度が上がるだけじゃないだろうか(要出典)という気もします。
ところで、最近知ったのですが、一部のpublicなDNSに特殊なクエリを投げると自分の(DNS側から見た時の)IPアドレスを返してくれるそうです。
ということは、これを家庭内LANのマシン上で定期的に動かしつつ、IPアドレスが変わっていたら通知するシステムがあれば十分じゃないか!
ということで作りました :D
自分のIPアドレスを知る
これを実行するだけ。
dig myip.opendns.com @208.67.222.222 +short
+short オプションは、今回初めて知ったんですが便利ですね。 最初はsedでanswer sectionを抜き出して〜とかやってたんですが、このオプションをつければ単にIPアドレスだけ返してくれます。
通知する
そんなもんslackにDMで投げときゃ良いでしょ。 ってことで、こちらの記事を参考に incomming webhook を設定してcurlでポストします。
デプロイメント
crontab -e
IPアドレスを調べてファイルに書き出し、前のIPアドレスとdiffとって違ってたらslackに投げるシェルスクリプトを作っておいて毎時5分くらいに動かします。
修正版
これで完成!!と思いきや、なんか定期的にIPアドレスが変わっていないのに通知が来る現象が起きました。
直前のIPアドレスとのdiffをとった時のログを見ると
1c1,4 < 192.168.0.1 --- > > ; <<>> DiG 9.10.6 <<>> myip.opendns.com @208.67.222.222 +short > ;; global options: +cmd > ;; connection timed out; no servers could be reached
あ、time outしとる・・・
というわけで、digが非ゼロで終了したら1分待ってリトライするようにして完成です(たぶん)((今のところ連続して同じIPアドレスが通知されたことは無いので、大丈夫そうだけど単にdigがエラーを返してないだけという可能性もあるので・・・)
需要は無さそうですが一応最終版のスクリプトを貼っておきます。
function onDetectNewGlobalIP(){ #save difference diff ${OUTPUTDIR}/globalIP.txt ${OUTPUTDIR}/globalIP_new.txt #post to slack curl -X POST -H 'Content-type: application/json'\ --data '{"text":"'$(cat ${OUTPUTDIR}/globalIP.txt)'"}' ${WEBHOOK_URL} rt=$? rm ${OUTPUTDIR}/globalIP.txt mv ${OUTPUTDIR}/globalIP_new.txt ${OUTPUTDIR}/globalIP.txt return $rt } { dig myip.opendns.com @208.67.222.222 +short > ${OUTPUTDIR}/globalIP_new.txt dig_rt=$? error_count=0 while [ $dig_rt != 0 ] do sleep 60 dig myip.opendns.com @208.67.222.222 +short > ${OUTPUTDIR}/globalIP_new.txt dig_rt=$? error_count=$(( ${error_count} + 1)) if [ ${error_count} -gt ${MAX_RETRY} ];then echo "dig failed more than ${MAX_RETRY} in a row" exit 1 fi done if [ -f ${OUTPUTDIR}/globalIP.txt ];then diff -q ${OUTPUTDIR}/globalIP_new.txt ${OUTPUTDIR}/globalIP.txt if [ $? -ne 0 ];then echo 'global IP address is changed!' onDetectNewGlobalIP else echo 'global IP address is not changed!' rm ${OUTPUTDIR}/globalIP_new.txt fi else echo 'global IP address file does not exist' onDetectNewGlobalIP fi } > ${OUTPUTDIR}/$(date +%Y%m%d-%H%M%S).log
OUTPUTDIR, WEBHOOK_URL, MAX_RETRYはそれぞれPC上のファイル置き場、slackに通知するためのwebhookのURL、MAX_RETRYはdigがエラーを返した時に リトライを行なう数です。
cronにこのスクリプトを仕込んで動かしつつ、cronで毎日1週間前のログを消すといった感じで運用していますが、今のところ快適に使えてます。
大前提としてルーターの設定などで外部からのsshアクセスは捌けるようにしておく必要がありますが、これで半年ほど使っていて特に問題なさそうです。