経緯
昨日バックアップ用のWi-Fi-Ethernet中継機をRaspberryPiで作った話という記事を書いた
概要としては
- buffaloのwifi中継機と親機(ルーター)との接続が切れるのでそのたびに再起動する必要があった
- 外からリモートデスクトップ等でPCに接続するときに切断されてると復帰できないので自動でつながってくれるバックアップ用の中継機を作った
こんなことをやったのだが、よくよく考えるとこの中継機はブラウザから設定をいじれて勿論再起動もできるのでHTTPのRequestを送ったら再起動できる筈だ
そう思ったので自動で中継機を再起動するようにした
環境
Computer: RaspberryPi 3B
OS: Ubuntu 20.04.3 LTS
私の家で動いているbuffaloの中継機: WEX-1166DHPS
ここで、中継機は静的IPで設定しているのでルーターとの接続が切れてもRasPiと通信できるから自動で復旧させようという魂胆
やったこと
中継機をcurlで操作できるようにする
取り敢えず手動で操作してwiresharkでHTTP Requestを見てみる
ログイン画面ではユーザー名、パスワード、モバイル端末かどうか、セッションIDっぽいやつが送信されてるっぽい(初期パスから変更してないのは許して)
最後のセッションIDっぽいのが謎だけどPOSTでユーザー名とパスワード送信すればログイン出来そうな感じがする
curlでリクエスト飛ばしてみる
$ curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num=1234567890' -L 192.168.0.253/login.html
curl: (1) Received HTTP/0.9 when not allowed
ん?なんか怒られた
HTTP0.9じゃないといけないっぽい
もう一回やってみる
$ curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num=1234567890' -L 192.168.0.253/login.html --http0.9
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=Shift_JIS">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="Thu, 01 Dec 1994 16:00:00 GMT">
<META http-equiv="Content-Style-Type" content="text/css">
<!--
//-->
<title>ERROR</title>
<meta name="author" content="buffalo">
<script language="JavaScript">
</script>
<style type="text/css">
<!--
body.BFK_BODY {
background-color: #ffffff;
font-size : 15px;
font-weight : 600;
color : #294887;
padding-top : 2px;
padding-bottom : 7px;
font-family : "Arial";
}
.C_CONFIRM {
color : #202020;
font-family : "Arial";
}
-->
</style>
</head>
<body class="BFK_BODY">
<p>�G���[: �s���ȃZ�b�V����ID�ɂ��A�N�Z�X�����m���܂����B���̐ݒ�v���͖�������܂��B</p>
</body>
</html>
一部文字化けしてるけどだめっぽいな
やっぱりセッションIDっぽいやつが必要そう
謎のセッションIDを探す
WEBの設定画面のソースコードを見るとhiddenで持ってるっぽい
再読込みするたびにランダムに変わってるこれを渡してあげれば良さそう
curlでリクエストを飛ばす(再挑戦)
$ curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num=1959743982' -L 192.168.0.253/login.html --http0.9
<HTML>
<HEAD>
<title>WEX-1166DHPS - BUFFALO AirStation</title>
<meta HTTP-EQUIV="Content-Type" Content="text/html; charset=Shift_JIS"></meta>
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="Thu, 01 Dec 1994 16:00:00 GMT">
<META http-equiv="Content-Style-Type" content="text/css">
<script language="JavaScript">
function init()
{
top.location.href = "index.html";
}
</script>
</HEAD>
<BODY onload="init();">
</body>
</html>
ちゃんとログインできた
同様にして再起動のリクエストを飛ばしてみる
$ curl -X POST -d 'nosave_reboot=1&nosave_session_num=04239915' -L 192.168.0.253/init.html --http0.9
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=Shift_JIS">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-Control" content="no-cache">
<meta http-equiv="Expires" content="Thu, 01 Dec 1994 16:00:00 GMT">
<META http-equiv="Content-Style-Type" content="text/css">
<!--
//-->
<title>Please wait...</title>
<meta name="author" content="buffalo">
<script language="JavaScript">
<!--
//-->
</script>
<style type="text/css">
<!--
body.BFK_BODY {
background-color: #ffffff;
font-size : 15px;
font-weight : 600;
color : #294887;
padding-top : 2px;
padding-bottom : 7px;
font-family : "Arial";
}
.C_CONFIRM {
color : #202020;
font-family : "Arial";
}
.C_MOD {
color : red;
font-family : "Arial";
}
-->
</style>
</head>
<body class="BFK_BODY" onload="top.waitingFwUpdate(false);">
<p>�ċN�����ł��B</p>
<p>���Ɩ� 30 �b�A���҂����������B</p>
<div class="C_CONFIRM">
<p>
���̌�A�ݒ�𑱂���ꍇ�́A���̎菇���s���Ă��������B<br>
<ol>
<li>WEB�u���E�U�[��S�ďI�����Ă��������B</li>
<li>���g���̃p�\�R���ƃG�A�X�e�[�V�������ʐM�ł���ݒ�ɂȂ��Ă��鎖���m�F���Ă��������B</li>
<li>���[�e�B���e�B�[����WEB�u���E�U�[���N�����ăG�A�X�e�[�V������WEB�ݒ���s���Ă��������B</li>
</ol>
���[�e�B���e�B�[�̎g�����̓}�j���A�����Q�Ƃ��Ă��������B
</p>
</div>
</body>
</html>
また文字化けしているがうまくいっているっぽい
これでcurlを使って中継機を操作できるようになった
スクリプトファイルの作成
上の動作を一気にやってくれるシェルスクリプトを書いた
自動で再起動するシェルスクリプトを/usr/local/bin/rebootRepeater.sh
に作成した
# !/bin/sh
# 中継機のIPアドレス
RPTR_ADDR="192.168.0.253"
# ログインセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/login.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="')"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get login sessionID"
exit 1
fi
# ログインする 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/login.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Login error"
exit 1
fi
# initセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/init.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="' | head -n 1)"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get init sessionID"
exit 1
fi
# 再起動する 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_reboot=1&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/init.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Couldn't reboot"
exit 1
fi
やっていることは自動でセッションIDを取得してそれを使ってログインと再起動するだけ
基本的に上の方でやったcurlの動作をgrepとかtrとかでいい感じにやって、失敗したらエラー吐いて終了するようにしている
ついでに失敗した時にその時間と失敗した原因を吐かせるようにした
ルーターに接続できない時に再起動を実行するようにする
これを毎回実行してると不必要に再起動するのでpingを飛ばしてルーター(インターネット)に接続できなくなった時に実行するようにする
ついでに中継機に接続できないとそもそも動かないのでそれも弾く
# !/bin/sh
# ルーターのIPアドレス
RTR_ADDR="192.168.0.1"
# 中継機のIPアドレス
RPTR_ADDR="192.168.0.253"
# ルーターに接続できるかチェック
ping $RTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
# pingを飛ばして中継機に接続できるかチェック
ping $RPTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
echo "$(date): Couldn't Reach Repeater"
exit 1
fi
# ログインセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/login.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="')"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get login sessionID"
exit 1
fi
# ログインする 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/login.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Login error"
exit 1
fi
# initセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/init.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="' | head -n 1)"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get init sessionID"
exit 1
fi
# 再起動する 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_reboot=1&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/init.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Couldn't reboot"
exit 1
fi
echo "$(date): Rebooted"
exit 0
fi
echo "$(date): Repeater active"
exit 0
追加したのは以下のあたり
# ルーターに接続できるかチェック
ping $RTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
# pingを飛ばして中継機に接続できるかチェック
ping $RPTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
echo "$(date): Couldn't Reach Repeater"
exit 1
fi
# ==========
# 再起動の処理
# ==========
fi
echo "$(date): Repeater active"
exit 0
pingは疎通すると0を返すのでそれを利用してチェックしている
まず、ルーターに疎通確認をして問題なければ終了
ルーターに疎通しなかった場合、中継機に疎通確認する
中継機にも疎通しないとどうしようもないので終了
中継機には疎通する場合はさっきの再起動の処理
crontabで毎分実行
crontabで毎分死活確認をするようにした
crontabは誤って削除するとまずいことになりそうなので/usr/local/etc/crontab
を作成してここに書いて読み込みようにする
* * * * * sh /usr/local/bin/rebootRepeater.sh
rootで実行したいのでsudoで登録する
$ sudo crontab /usr/local/etc/crontab
動作確認
ルーターを再起動させて中継機の状態を確認したところルーターへの接続が切れたことを検出して再起動しているっぽい
しかし、だ
ルーターが復帰しても中継機から伸びる有線LANに接続されている私のPCは一向にインターネットにつながらない
何かがおかしいぞと思って様子を見たり、スクリプトにログを取る機能をつけてログを見ているとあることがわかった
中継機がルーターに接続する前に再起動してる!!
どうやら毎分実行しているので再起動した次の実行タイミングでルーターに接続していない(実際接続していないが)と判断されて再起動してしまって無限ループしているようだ
無限ループ対策
これはどうにかしないといけないので再起動したあとのcrontabの実行を遅らせることにする
私はあまり賢いわけではないのでcrontabの設定ファイルを書き換えて3分後に実行するように設定することで解決する
ということで以下のようにした
ついでにログを取るように
# !/bin/sh
# ルーターのIPアドレス
RTR_ADDR="192.168.0.1"
# 中継機のIPアドレス
RPTR_ADDR="192.168.0.253"
# ログファイルのパス
LOG_PATH="/var/log/RBTRPTR.log"
# crontab設定のパス
CRN_PATH="/usr/local/etc/crontab"
# ルーターに接続できるかチェック
ping $RTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
# pingを飛ばして中継機に接続できるかチェック
ping $RPTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
echo "$(date): Couldn't Reach Repeater" >> $LOG_PATH
exit 1
fi
# ==========
# 再起動の処理
# ==========
echo "$(expr $(date +%-M) + 3) * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
echo "$(date): Rebooted" >> $LOG_PATH
exit 0
fi
echo "* * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
echo "$(date): Repeater active" >> $LOG_PATH
exit 0
やっていることとしては再起動のあとにdate
コマンドで取得した現在の分に3を足して3分後に実行するように設定して適用
echo "$(expr $(date +%-M) + 3) * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
ルーターへの疎通ができた場合は設定を毎分に戻して適用
echo "* * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
動作確認(再挑戦)
さっきと同じようにルーターを再起動して動作確認をした
今回もちゃんと再起動をしているが、前回とは違って無限ループになっていないように見える
私のPCもインターネットに接続できるようになった
ヨシ!!
あとがき
- 昨日作ったバックアップ中継機がまさか1日で用無しになるとは思ってなかったから結構ショック
- まあ、最初からこっちでやっとけよって話
- もともと私の家ではRasPiがサーバーとして動いていたのでそこに機能を追加する形で実装できたので良かった
スクリプトファイルの全容
一応最終的なスクリプトを載せておきますねー
# !/bin/sh
# ルーターのIPアドレス
RTR_ADDR="192.168.0.1"
# 中継機のIPアドレス
RPTR_ADDR="192.168.0.253"
# ログファイルのパス
LOG_PATH="/var/log/RBTRPTR.log"
# crontab設定のパス
CRN_PATH="/usr/local/etc/crontab"
# ルーターに接続できるかチェック
ping $RTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
# pingを飛ばして中継機に接続できるかチェック
ping $RPTR_ADDR -c 1 >> /dev/null
if [ $? != 0 ]; then
echo "$(date): Couldn't Reach Repeater" >> $LOG_PATH
exit 1
fi
# ログインセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/login.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="')"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get login sessionID" >> $LOG_PATH
exit 1
fi
# ログインする 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_Username=admin&nosave_Password=password&MobileDevice=0&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/login.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Login error" >> $LOG_PATH
exit 1
fi
# initセッションIDを取得
SESSION_ID="$(curl -fsL ${RPTR_ADDR}/init.html | grep -o '<input type="hidden" name="nosave_session_num" value=".*">' | grep -o 'value=".*"' | tr -d 'value="' | head -n 1)"
if [ $SESSION_ID = "" ]; then
echo "$(date): Couldn't get init sessionID" >> $LOG_PATH
exit 1
fi
# 再起動する 成功すると無を返す 失敗するとERRORを返す
IS_ERROR="$(curl -X POST -d 'nosave_reboot=1&nosave_session_num='${SESSION_ID} -fsL ${RPTR_ADDR}/init.html --http0.9 | grep 'ERROR' | tr -d '<>tile/')"
if [ $IS_ERROR != "" ]; then
echo "$(date): Couldn't reboot" >> $LOG_PATH
exit 1
fi
echo "$(expr $(date +%-M) + 3) * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
echo "$(date): Rebooted" >> $LOG_PATH
exit 0
fi
echo "* * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
echo "$(date): Repeater active" >> $LOG_PATH
exit 0