春雨日記 about me tags

楽天モバイルSIMを刺した604HWをゲートウェイとして使っているのですが,切断時に自動的に再接続してくれないので微妙に困っていました.

WebUIのチェック

WebUIではステータスや接続・切断が操作できるようになっていますが,どのような通信を行っているのでしょうか.

ブラウザのデバッガを使って何を通信しているか見てみます.

調べた結果

  • http://192.168.128.1/api/monitoring/statusに接続状態(接続済みなど)が含まれる
  • http://192.168.128.1/api/dialup/dialで接続・切断を行う

という事が分かりました.

しかし,単純にcurlで落としてきただけではエラーが出ます.そのためセッションIDをCookieに含める必要があります.

状態を取得する

とりあえず,ブラウザのデータを拝借してcurlで読み取ってみます.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
curl -i -X GET -H "Cookie: SessionID=<セッションID>" http://192.168.128.1/api/monitoring/status
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Content-Type: text/html
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Content-Type-Options: nosniff
Connection: Keep-Alive
Content-Length: 1277

<?xml version="1.0" encoding="UTF-8"?>
<response>
<ConnectionStatus>902</ConnectionStatus>
<WifiConnectionStatus></WifiConnectionStatus>
<SignalStrength></SignalStrength>
<SignalIcon>4</SignalIcon>
<CurrentNetworkType>19</CurrentNetworkType>
<CurrentServiceDomain>3</CurrentServiceDomain>
<RoamingStatus>0</RoamingStatus>
<BatteryStatus></BatteryStatus>
<BatteryLevel></BatteryLevel>
<BatteryPercent></BatteryPercent>
<simlockStatus>0</simlockStatus>
<PrimaryDns></PrimaryDns>
<SecondaryDns></SecondaryDns>
<PrimaryIPv6Dns></PrimaryIPv6Dns>
<SecondaryIPv6Dns></SecondaryIPv6Dns>
<CurrentWifiUser></CurrentWifiUser>
<TotalWifiUser></TotalWifiUser>
<currenttotalwifiuser>0</currenttotalwifiuser>
<ServiceStatus>2</ServiceStatus>
<SimStatus>1</SimStatus>
<WifiStatus></WifiStatus>
<CurrentNetworkTypeEx>101</CurrentNetworkTypeEx>
<maxsignal>4</maxsignal>
<wifiindooronly>-1</wifiindooronly>
<wififrequence>0</wififrequence>
<classify>DataCard</classify>
<flymode>0</flymode>
<usbup>0</usbup>
<syscfgnetmode>1</syscfgnetmode>
<dfsstatus>-1</dfsstatus>
<wifiuseablestate>-1</wifiuseablestate>
<WanIPAddress></WanIPAddress>
<WanIPv6Address></WanIPv6Address>
<wifiswitchstatus>-1</wifiswitchstatus>
<batterydisplay>1</batterydisplay>
</response>

切断済みのレスポンスは上記の通りとなりました.

この中で,接続状態を見たい場合,重要なのはConnectionStatusです.

状態により,以下の通りとなりました.

状態 コード
接続済み 901
接続中 900
切断済み 902

接続・切断を操作する

http://192.168.128.1/api/dialup/dial にPOSTで

1
<?xml version="1.0" encoding="UTF-8"?><request><Action>0</Action></request>

を送る事で切断

1
<?xml version="1.0" encoding="UTF-8"?><request><Action>1</Action></request>

を送る事で接続

することができます.

レスポンスは200 OKで

1
<?xml version="1.0" encoding="UTF-8"?><response>OK</response>

です.

しかし,単純にセッションIDを付加しただけではエラーとなります.

ここでは更にヘッダに__RequestVerificationTokenを付加する事が必要です.

セッションIDを取得する

cookieを送信せずにhttp://192.168.128.1/をリクエストするとレスポンスに付加されます.

RequestVerificationTokenを取得する

デバッガで追いかけた所,

http://192.168.128.1/api/webserver/tokenにCookieを付加してリクエストすると取得できるようです.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
curl -i -X GET -H "Cookie: SessionID=<セッションID>" http://192.168.128.1/api/webserver/token
HTTP/1.1 200 OK
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Content-Type: text/html
X-Download-Options: noopen
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
Content-Security-Policy: default-src 'self' 'unsafe-inline' 'unsafe-eval'
X-Content-Type-Options: nosniff
Date: Sat, 09 Jul 2022 00:15:04 GMT
Connection: Keep-Alive
Content-Length: 138

<?xml version="1.0" encoding="UTF-8"?><response><token>トークン</token></response>

更に用途によって(?)前半部分と後半部分が分かれているようです.

前半32文字とその後全てで分かれており,dialの際に必要なのは後半部分です.

Pythonで自動化

必要な情報は揃いましたので,Pythonにて実際のリクエストを行ってみました.

ほぼ初pythonなので変なことしてる可能性もありますが,とりあえず動いてくれています.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
from time import sleep
from urllib import request, parse
from pprint import pprint
import re

def getSession():
    cookiepattern = 'SessionID=(.*?);'
    get_req = request.Request('http://192.168.128.1/', method="GET")
    with request.urlopen(get_req) as res:
        status = res.getcode()
        if status != 200:
            return 'err'

        cookieraw = res.getheader('Set-Cookie')
        regxres = re.match(cookiepattern,cookieraw)
        if regxres:
            return regxres.group(1)
        else:
            return "err"


def getStatus(session):
    header = {'Cookie': 'SessionID=' + session + ';'}
    get_req = request.Request('http://192.168.128.1/api/monitoring/status',headers=header ,method="GET")
    with request.urlopen(get_req) as res:
        if res.getcode() != 200:
            return 'err' 
        body = res.read().decode('utf-8')
        # 切断中
        if body.find('<ConnectionStatus>902</ConnectionStatus>') != -1:
            return 'disconnected'
        # 接続済み
        if body.find('<ConnectionStatus>901</ConnectionStatus>') != -1:
            return 'connected'
        # 接続中
        if body.find('<ConnectionStatus>900</ConnectionStatus>') != -1:
            return 'connecting'
        return 'unknown'


def getVerifyToken(session):
    tokenpattern = '.*<token>(.*?)</token>.*'
    header = {'Cookie': 'SessionID=' + session + ';'}
    get_req = request.Request('http://192.168.128.1/api/webserver/token',headers=header ,method="GET")
    with request.urlopen(get_req) as res:
        if res.getcode() != 200:
            return 'err'
        body = res.read().decode('utf-8').split(' ')[2]
        regxres = re.match(tokenpattern,body)
        if regxres:
            return regxres.group(1)
        else:
            return 'err'


def requestDial(session, token, mode):
    post_data = ''
    if mode == 'connect':
        post_data = '<?xml version="1.0" encoding="UTF-8"?><request><Action>1</Action></request>'.encode('utf-8')
    else:
        post_data = '<?xml version="1.0" encoding="UTF-8"?><request><Action>0</Action></request>'.encode('utf-8')
    header = {  'Cookie': 'SessionID=' + session + ';',
                '__RequestVerificationToken': token[32:]}
    post_req = request.Request('http://192.168.128.1/api/dialup/dial',data=post_data,headers=header ,method="POST")
    with request.urlopen(post_req) as res:
        body = res.read().decode('utf-8')
        if res.getcode() != 200:
            return 'err'
        if(body.find('OK')):
            return 'ok'
        else:
            return 'err'
        
    
session = getSession()
if getStatus(session) != 'disconnected':
    pprint('already connected')
    exit(0)
pprint(requestDial(session,getVerifyToken(session),'connect'))
sleep(5)
if getStatus(session) != 'disconnected':
    pprint('dial success')
    exit(0)
else:
    pprint('dial fail')
    exit(1)

成功です✌

あとはsystemd-timerで実行するようにします.

/etc/systemd/system/604hw.service

[Unit]
Description=604hw dialer

[Service]
Type=oneshot
ExecStart=/usr/bin/python3 /usr/local/bin/604hw.py

/etc/systemd/system/604hw.timer

[Unit]
Description=Run 604hw dialer

[Timer]
OnBootSec=15min
OnUnitActiveSec=30

[Install]
WantedBy=timers.target

とりあえず30秒毎に実行すれば困らないでしょう.

おわりに

朝起きたら切れてるなんて事は無くなりそうです.

おまけ

604HWのusb_modeswitch設定

ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1f0e", RUN+="/usr/sbin/usb_modeswitch -v 0x12d1 -p 1f0e -J"

更におまけ(というかメモ)

W05

ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="12d1", ATTRS{idProduct}=="1f01", RUN+="/usr/sbin/usb_modeswitch -v 0x12d1 -p 1f01 -J"