OpenWrtでRakuten WiFi PocketをUSB接続で使う

# 環境

- Rakuten WiFi Pocket R310
  - ソフトウェアバージョン: R310_V3.19_20210302
  - ハードウェアバージョン: V3.0
  - OSバージョン: 3.4.110
  - SIM: Rakuten UN-LIMIT V

```console
root@OpenWrt:~# cat /etc/openwrt_release
DISTRIB_ID='OpenWrt'
DISTRIB_RELEASE='21.02.0-rc1'
DISTRIB_REVISION='r16046-59980f7aaf'
DISTRIB_TARGET='ath79/generic'
DISTRIB_ARCH='mips_24kc'
DISTRIB_DESCRIPTION='OpenWrt 21.02.0-rc1 r16046-59980f7aaf'
DISTRIB_TAINTS=''
```

# 必要なパッケージのインストール

デフォルトではマスストレージデバイスとして認識されているようですので、usb_modeswitchをインストールします。

```console
root@OpenWrt:~# opkg update
root@OpenWrt:~# opkg install usb-modeswitch
root@OpenWrt:~# cat /sys/kernel/debug/usb/devices
...
T:  Bus=01 Lev=02 Prnt=02 Port=01 Cnt=02 Dev#=  5 Spd=480  MxCh= 0
D:  Ver= 2.00 Cls=02(comm.) Sub=00 Prot=00 MxPS=64 #Cfgs=  1
P:  Vendor=19d2 ProdID=1557 Rev= 1.01
S:  Manufacturer=DEMO,Incorporated
S:  Product=DEMO Mobile Boardband
S:  SerialNumber=1234567890ABCDEF
C:* #Ifs= 3 Cfg#= 1 Atr=c0 MxPwr=500mA
A:  FirstIf#= 0 IfCount= 2 Cls=02(comm.) Sub=06 Prot=00
I:* If#= 0 Alt= 0 #EPs= 1 Cls=02(comm.) Sub=06 Prot=00 Driver=(none)
E:  Ad=87(I) Atr=03(Int.) MxPS=  16 Ivl=32ms
I:* If#= 1 Alt= 0 #EPs= 0 Cls=0a(data ) Sub=00 Prot=00 Driver=(none)
I:  If#= 1 Alt= 1 #EPs= 2 Cls=0a(data ) Sub=00 Prot=00 Driver=(none)
E:  Ad=81(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=01(O) Atr=02(Bulk) MxPS= 512 Ivl=0ms
I:* If#= 2 Alt= 0 #EPs= 2 Cls=08(stor.) Sub=06 Prot=50 Driver=(none)
E:  Ad=82(I) Atr=02(Bulk) MxPS= 512 Ivl=0ms
E:  Ad=02(O) Atr=02(Bulk) MxPS= 512 Ivl=125us
```

CDC-ECM(0x02,0x06)として認識されたので、cdc_etherカーネルモジュールをインストールします。

```console
root@OpenWrt:~# opkg install kmod-usb-net-cdc-ether
root@OpenWrt:~# dmesg | grep cdc_ether
[  336.882875] usbcore: registered new interface driver cdc_ether
[  341.468475] cdc_ether 1-1.2:1.0 eth1: register 'cdc_ether' at usb-ehci-platform-1.2, ZTE CDC Ethernet Device, 34:4b:50:00:00:00
```

# インターフェースを追加

「wwan」と「wwan6」の名前でインターフェースを追加します。
物理インターフェースの「eth1」に紐付けて、ファイアウォールゾーンの「wan」に割り当てます。

```console
uci set network.wwan=interface
uci set network.wwan.ifname='eth1'
uci set network.wwan.proto='dhcp'
uci set network.wwan6=interface
uci set network.wwan6.ifname='eth1'
uci set network.wwan6.proto='dhcpv6'
uci commit network

uci set dhcp.wwan='dhcp'
uci set dhcp.wwan.interface='wwan'
uci set dhcp.wwan.ignore='1'
uci commit dhcp

uci add_list firewall.@zone[1].network='wwan'
uci add_list firewall.@zone[1].network='wwan6'
uci commit firewall

/etc/init.d/network restart
/etc/init.d/firewall reload
```

# 接続テスト

```console
root@OpenWrt:~# ifstatus wwan
...
        "proto": "dhcp",
        "device": "eth1",
        "ipv4-address": [
                {
                        "address": "192.168.0.100",
                        "mask": 24
                }
        ],
        "route": [
                {
                        "target": "0.0.0.0",
                        "mask": 0,
                        "nexthop": "192.168.0.1",
                        "source": "192.168.0.100/32"
                }
        ],
        "dns-server": [
                "192.168.0.1"
        ],
...

root@OpenWrt:~# ifstatus wwan6
...
        "proto": "dhcpv6",
        "device": "eth1",
        "ipv6-address": [
                {
                        "address": "240b:c010:411:d7cb:364b:50ff:fe00:0",
                        "mask": 64,
                        "preferred": 61,
                        "valid": 241
                }
        ],
        "ipv6-prefix": [

        ],
        "ipv6-prefix-assignment": [

        ],
        "route": [
                {
                        "target": "240b:c010:411:d7cb:0:f:9449:eb01",
                        "mask": 64,
                        "nexthop": "::",
                        "metric": 256,
                        "valid": 241,
                        "source": "::/0"
                },
                {
                        "target": "::",
                        "mask": 0,
                        "nexthop": "fe80::1",
                        "metric": 512,
                        "valid": 241,
                        "source": "240b:c010:411:d7cb:364b:50ff:fe00:0/64"
                }
        ],
        "dns-server": [
                "fe80::1"
        ],
...

root@OpenWrt:~# ping -c 4 google.com
PING google.com (172.217.25.238): 56 data bytes
64 bytes from 172.217.25.238: seq=0 ttl=114 time=42.521 ms
64 bytes from 172.217.25.238: seq=1 ttl=114 time=23.051 ms
64 bytes from 172.217.25.238: seq=2 ttl=114 time=21.842 ms
64 bytes from 172.217.25.238: seq=3 ttl=114 time=41.539 ms

root@OpenWrt:~# ping6 -c 4 google.com
PING google.com (2404:6800:4004:81b::200e): 56 data bytes
64 bytes from 2404:6800:4004:81b::200e: seq=0 ttl=115 time=301.799 ms
64 bytes from 2404:6800:4004:81b::200e: seq=1 ttl=115 time=28.033 ms
64 bytes from 2404:6800:4004:81b::200e: seq=2 ttl=115 time=35.634 ms
64 bytes from 2404:6800:4004:81b::200e: seq=3 ttl=115 time=26.224 ms
```

# LAN側のホストからIPv6を使えるようにする

Rakuten WiFi PocketからのRAを見てみます。
![rakuten_wifi_pocket_cap1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/73089/dbed21ed-3cfa-510a-0c56-b1d0c0bcb958.png)
Pフラグは0となっていますのでND Proxy ([RFC 4389](https://tools.ietf.org/html/rfc4389))が使えそうです。
以下のようにND Proxyを設定します。

```console
uci set dhcp.wwan6='dhcp'
uci set dhcp.wwan6.interface='wwan6'
uci set dhcp.wwan6.ndp='relay'
uci set dhcp.wwan6.ra='relay'
uci set dhcp.wwan6.master='1'
uci set dhcp.lan.dhcpv6='server'
uci set dhcp.lan.ndp='relay'
uci set dhcp.lan.ra='relay'
address="$(ifstatus lan | jsonfilter -e '@["ipv6-prefix-assignment"][0]["local-address"].address')"
[ "$address" ] && uci add_list dhcp.lan.dns="$address"
uci commit dhcp

/etc/init.d/odhcpd restart
```

設定の結果は次のようになります。

 - WWAN6をアップストリームインターフェースに指定します。(`master='1'`)
 - WWAN6/LANインターフェース間でNS/NAパケットをproxyします。(`ndp='relay'`)
 - WWAN6/LANインターフェース間でRS/RAパケットをproxyします。(`ra='relay'`)
 - LANインターフェイスのULAをDNSサーバアドレスとしてリンク上のホストに通知します。(`dhcpv6='server'`, `dns=`[^1])

LAN側のホストからipv6.google.comにtraceroute (tracert)してみます。

```console
> tracert ipv6.google.com

ipv6.l.google.com [2404:6800:4004:810::200e] へのルートをトレースしています
経由するホップ数は最大 30 です:

  1    <1 ms    <1 ms    <1 ms  OpenWrt.lan [fd57:1deb:47a3::1]
  2     3 ms     1 ms     1 ms  240b:c010:411:d7cb:0:f:9449:ec01
  3    35 ms    57 ms    41 ms  240b:c010:102:1b58::4
  4    49 ms    52 ms    65 ms  240b:c010:102:1b58::4
  5    55 ms    49 ms    51 ms  240b:c010:102:15e5:0:4:0:584
  6    66 ms    65 ms    68 ms  240b:c010:105::7225:0:1
  7    50 ms    42 ms    31 ms  240b:c010:105::7225:0:2
  8    73 ms    37 ms    74 ms  240b:c010:115::8223:0:0
  9    52 ms    66 ms    45 ms  240b:c010:125::8122:0:0
 10    62 ms    42 ms    58 ms  2001:4860:1:1::14b6
 11    45 ms    58 ms    54 ms  2001:4860:0:1000::1
 12     *        *        *     要求がタイムアウトしました。
 13   108 ms    52 ms    46 ms  nrt12s28-in-x0e.1e100.net [2404:6800:4004:810::200e]

トレースを完了しました。
```

# Rakuten WiFi PocketのTips

## Wi-Fiを無効にする場合

Wi-Fiは使用しないので管理画面から無効に設定しましたが、本体のボタンを操作したり再起動すると有効に戻ってしまうようです。

```sh:/root/r310-wifi-enabled.sh
#!/bin/sh

ROUTER_IP="192.168.0.1"
USERNAME="admin"
PASSWORD="admin"

log() {
    logger -t "$(basename $0 .sh)" "$1: ${2-$(cat)}"
}

base64_uri() {
    echo -n "$1" | base64 -w0 | jq -sRr @uri
}

loginfo() {
    curl -s "http://$ROUTER_IP/reqproc/proc_get" \
        -H "Referer: http://$ROUTER_IP/index.html" \
        --data "multi_data=1&isTest=false&cmd=loginfo%2C${*// /%2C}"
}

cmd() {
    local loginfo=$(loginfo "$*")
    # Login
    [ "$(echo $loginfo | jq -cRr 'fromjson?|.loginfo')" = ok ] || {
        curl -s "http://$ROUTER_IP/reqproc/proc_post" \
            -H "Referer: http://$ROUTER_IP/index.html" \
            --data "isTest=false&goformId=LOGIN&username=$(base64_uri $USERNAME)&password=$(base64_uri $PASSWORD)" |
        jq -r '.result' |
        log LOGIN
        loginfo=$(loginfo "$*")
    }
    echo "$loginfo" | jq -cR 'fromjson?|del(.loginfo)'
}

# Get Wi-Fi state
wifi_cur_state=$(cmd wifi_cur_state | jq -r '.wifi_cur_state')
[ "$wifi_cur_state" ] || exit 1
log wifi_cur_state "$wifi_cur_state"

# Enable or disable Wi-Fi
wifi_enabled="${1:-0}"
[ "$wifi_cur_state" = "$wifi_enabled" ] || {
    curl -s "http://$ROUTER_IP/reqproc/proc_post" \
        -H "Referer: http://$ROUTER_IP/index.html" \
        --data "goformId=SET_WIFI_INFO&isTest=false&wifiEnabled=$wifi_enabled" |
    jq -r '.result' |
    log wifiEnabled
}
```

```console
root@OpenWrt:~# opkg install curl jq coreutils-base64
root@OpenWrt:~# chmod +x r310-wifi-enabled.sh
root@OpenWrt:~# ./r310-wifi-enabled.sh
root@OpenWrt:~# ./r310-wifi-enabled.sh 1
root@OpenWrt:~# echo "*/10 * * * * /root/r310-wifi-enabled.sh" >> /etc/crontabs/root
root@OpenWrt:~# /etc/init.d/cron restart
```

## MCC/MNCの取得

ログイン処理が必要だったので上記のスクリプトを使い回しています。

```diff
--- r310-wifi-enabled.sh 2021-03-25 16:00:38.991491600 +0900
+++ r310-cmd.sh 2021-03-25 16:01:28.979587700 +0900
@@ -32,17 +32,4 @@
     echo "$loginfo" | jq -cR 'fromjson?|del(.loginfo)'
 }

-# Get Wi-Fi state
-wifi_cur_state=$(cmd wifi_cur_state | jq -r '.wifi_cur_state')
-[ "$wifi_cur_state" ] || exit 1
-log wifi_cur_state "$wifi_cur_state"
-
-# Enable or disable Wi-Fi
-wifi_enabled="${1:-0}"
-[ "$wifi_cur_state" = "$wifi_enabled" ] || {
-    curl -s "http://$ROUTER_IP/reqproc/proc_post" \
-        -H "Referer: http://$ROUTER_IP/index.html" \
-        --data "goformId=SET_WIFI_INFO&isTest=false&wifiEnabled=$wifi_enabled" |
-    jq -r '.result' |
-    log wifiEnabled
-}
+cmd "$*"
```

```console
$ ./r310-cmd.sh mcc mnc
{"mcc":"440","mnc":"11"}
```

auローミングが終了したエリアで使用しているため、44053に接続した場合に正しい値を返すかどうかは分かりません。

# 参考
- [[OpenWrt Wiki] Use cdc_ether driver based dongles for WAN connection](https://openwrt.org/docs/guide-user/network/wan/wwan/ethernetoverusb_cdc)
- [piu lentoの日々 (piu lento days) &raquo; Blog Archive  &raquo; iVideo iV501 tips](https://blog.piulento.net/archives/105831)
- [piu lentoの日々 (piu lento days) &raquo; Blog Archive  &raquo; いくつか分かったが、やっぱりイマイチだ・・・ (楽天モバイルのその後)](https://blog.piulento.net/archives/127805)

[^1]: `ra='relay'`に設定した場合は、DNSサーバアドレスを明示的に指定しないと受信したRAのRDNSSを書き換えてくれないようなので念のため設定しました。現時点ではRakuten Wifi PocketはRDNSSをサポートしていないので省略しても問題ありません。