頻繁にルーターとWiFi-Ethernet中継機の接続が切れるので自動で再起動させて再接続させるようにした話

# 経緯

昨日[バックアップ用のWi-Fi-Ethernet中継機をRaspberryPiで作った話](https://qiita.com/cffnpwr/items/1b5ad5e809e363aed87a)という記事を書いた
概要としては

1. buffaloのwifi中継機と親機(ルーター)との接続が切れるのでそのたびに再起動する必要があった
1. 外からリモートデスクトップ等でPCに接続するときに切断されてると復帰できないので自動でつながってくれるバックアップ用の中継機を作った

こんなことをやったのだが、よくよく考えるとこの中継機はブラウザから設定をいじれて勿論再起動もできるのでHTTPのRequestを送ったら再起動できる筈だ
そう思ったので自動で中継機を再起動するようにした

# 環境

```
Computer: RaspberryPi 3B
      OS: Ubuntu 20.04.3 LTS
```

私の家で動いているbuffaloの中継機: [WEX-1166DHPS](https://www.buffalo.jp/product/detail/wex-1166dhps.html)

ネットワークのイメージ
![network.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1714294/64e4ccac-a62e-45c9-a344-e156b0b5cef4.png)

ここで、中継機は静的IPで設定しているのでルーターとの接続が切れてもRasPiと通信できるから自動で復旧させようという魂胆

# やったこと

## 中継機をcurlで操作できるようにする

### 取り敢えず手動で操作してwiresharkでHTTP Requestを見てみる

ログイン画面ではユーザー名、パスワード、モバイル端末かどうか、セッションIDっぽいやつが送信されてるっぽい(初期パスから変更してないのは許して)
![Screenshot from 2021-09-10 21-57-46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1714294/a2ddbe4b-233d-81c6-a039-7b50725f0eb3.png)

最後のセッションIDっぽいのが謎だけどPOSTでユーザー名とパスワード送信すればログイン出来そうな感じがする

### curlでリクエスト飛ばしてみる

```bash
$ 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じゃないといけないっぽい

もう一回やってみる

```bash
$ 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で持ってるっぽい
![Screenshot from 2021-09-10 22-18-34.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1714294/1f86d5ed-89e3-2ba2-cd18-ad26fdf1327e.png)

再読込みするたびにランダムに変わってるこれを渡してあげれば良さそう

### curlでリクエストを飛ばす(再挑戦)

```bash
$ 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>
```

ちゃんとログインできた

### 同様にして再起動のリクエストを飛ばしてみる

こっちもセッションIDがランダムで生成されるので一緒に渡す
![Screenshot from 2021-09-10 22-26-13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1714294/f2487d85-2d07-a228-579c-ce87543204c4.png)

```bash
$ 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>
	&nbsp;&nbsp;&nbsp;���[�e�B���e�B�[�̎g�����̓}�j���A�����Q�Ƃ��Ă��������B
	</p>
	</div>
</body>
</html>
```

また文字化けしているがうまくいっているっぽい

これでcurlを使って中継機を操作できるようになった

## スクリプトファイルの作成

### 上の動作を一気にやってくれるシェルスクリプトを書いた

自動で再起動するシェルスクリプトを`/usr/local/bin/rebootRepeater.sh`に作成した

```sh:/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を飛ばしてルーター(インターネット)に接続できなくなった時に実行するようにする
ついでに中継機に接続できないとそもそも動かないのでそれも弾く

```sh:/usr/local/bin/rebootRepeater.sh
#!/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
```

追加したのは以下のあたり

```bash
#   ルーターに接続できるかチェック
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`を作成してここに書いて読み込みようにする

```conf:/usr/local/etc/crontab
* * * * * sh /usr/local/bin/rebootRepeater.sh
```
rootで実行したいのでsudoで登録する

```bash
$ sudo crontab /usr/local/etc/crontab
```

## 動作確認

ルーターを再起動させて中継機の状態を確認したところルーターへの接続が切れたことを検出して再起動しているっぽい

しかし、だ

ルーターが復帰しても中継機から伸びる有線LANに接続されている私のPCは一向にインターネットにつながらない
何かがおかしいぞと思って様子を見たり、スクリプトにログを取る機能をつけてログを見ているとあることがわかった

***中継機がルーターに接続する前に再起動してる！！***

どうやら毎分実行しているので再起動した次の実行タイミングでルーターに接続していない(実際接続していないが)と判断されて再起動してしまって無限ループしているようだ

## 無限ループ対策

これはどうにかしないといけないので再起動したあとのcrontabの実行を遅らせることにする
私はあまり賢いわけではないのでcrontabの設定ファイルを書き換えて3分後に実行するように設定することで解決する

ということで以下のようにした
ついでにログを取るように

```sh:/usr/local/bin/rebootRepeater.sh
#!/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分後に実行するように設定して適用

```sh
echo "$(expr $(date +%-M) + 3) * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
```
ルーターへの疎通ができた場合は設定を毎分に戻して適用

```sh
echo "* * * * * sh /usr/local/bin/rebootRepeater.sh" > $CRN_PATH
crontab $CRN_PATH
```

## 動作確認(再挑戦)

さっきと同じようにルーターを再起動して動作確認をした

今回もちゃんと再起動をしているが、前回とは違って無限ループになっていないように見える
私のPCもインターネットに接続できるようになった

***ヨシ！！***

# あとがき

 - 昨日作ったバックアップ中継機がまさか1日で用無しになるとは思ってなかったから結構ショック
 - まあ、最初からこっちでやっとけよって話
 - もともと私の家ではRasPiがサーバーとして動いていたのでそこに機能を追加する形で実装できたので良かった

# スクリプトファイルの全容

一応最終的なスクリプトを載せておきますねー

```sh:/usr/local/bin/rebootRepeater.sh
#!/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
```