Raspberry PiでUSBモデム挿入時にSORACOMへ自動接続する

# 概要

[簡単にRaspberry PiでSORACOM Airを使う(FS01BU USBモデムを用いて自動接続)](http://qiita.com/dietposter/items/068019ee53252206a6bf)で紹介されているような方法を使うとFS01BU USBモデム+SORACOM AirのSIMの組み合わせでブート時に自動接続できるようになりますが、デバイス挿入時に自動接続させたかったので、その方法をまとめます。OS は Raspbian です。
`udev` でデバイス挿抜時に `ifup/ifdown` を実行することで実現します。

# 準備

必要なパッケージをインストールします。

```shell-session
pi@raspberrypi:~ $ sudo apt-get install wvdial usb-modeswitch
```

手動接続用のスクリプトをダウンロードします。

```shell-session
pi@raspberrypi:~ $ curl -O http://soracom-files.s3.amazonaws.com/connect_air.sh
pi@raspberrypi:~ $ chmod 755 ./connect_air.sh
```

モデムを挿した状態で実行して手動接続できることを確認します。

```shell-session
pi@raspberrypi:~ $ sudo ./connect_air.sh
```

この際、 /etc/wvdial.conf が生成されるので、以下ではそれを編集して使います。私の環境では以下が生成されます。

```ini:/etc/wvdial.conf
[Dialer Defaults] 
Init1 = ATZ 
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0 
Init3 = AT+CGDCONT=1,"IP","soracom.io" 
Dial Attempts = 3 
Stupid Mode = 1 
Modem Type = Analog Modem 
Dial Command = ATD 
Stupid Mode = yes 
Baud = 460800 
New PPPD = yes 
Modem = /dev/ttyUSB2 
ISDN = 0 
APN = soracom.io 
Phone = *99***1# 
Username = sora 
Password = sora 
Carrier Check = no 
Auto DNS = 1 
Check Def Route = 1 
```

# ifup で接続できるようにする

`/etc/network/interfaces` に以下を追記します。

```:/etc/network/interfaces
allow-hotplug soracom
iface soracom net wvdial
```

`ifup soracom` で接続されることを確認します。

```shell-session
pi@raspberrypi:~ $ sudo ifup soracom
pi@raspberrypi:~ $ ifconfig ppp0
ppp0      Link encap:Point-to-Pointプロトコル  
          inetアドレス:10.xxx.xxx.xxx P-t-P:10.64.64.64  マスク:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  メトリック:1
          RXパケット:7 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:8 エラー:0 損失:0 オーバラン:0 キャリア:0
      衝突(Collisions):0 TXキュー長:3 
          RXバイト:130 (130.0 B)  TXバイト:181 (181.0 B)
```

`ifdown soracom` で切断できます。

```shell-session
pi@raspberrypi:~ $ sudo ifdown soracom
pi@raspberrypi:~ $ ifconfig ppp0
ppp0: インタフェース情報を取得中にエラーが発生しました: デバイスが見つかりません
```

# 接続が切れても自動で再接続できるようにする

`/etc/ppp/peers/wvdial` で `persist` オプションを有効にします。

```udiff
--- a/ppp/peers/wvdial
+++ b/ppp/peers/wvdial
@@ -2,3 +2,5 @@ noauth
 name wvdial
 usepeerdns
 replacedefaultroute
+persist
+holdoff 10
```

# デバイス名を固定する

USBシリアルデバイスが1つしか接続されていない場合、ブート時に FS01BU が認識されると、

```shell-session
/dev/ttyUSB0
/dev/ttyUSB1
/dev/ttyUSB2
```

の3つのデバイスファイルを作成され、ダイヤルアップに使われるのは ```/dev/ttyUSB2``` です。しかし、他の USB シリアルデバイスが接続されていると認識順序によってこれがずれます。また、 FS01BU だけ接続されていても活線挿抜によりデバイス番号がずれることがあります。
このため、[デバイス名を固定する](http://qiita.com/osada9000/items/3e6ff429ba782624def1)を参考にデバイス名を固定します。ただし、誤判定を避けるため `bNumEndpoints` と `bInterfaceNumber` だけではなく `idVendor` と `idProduct` も `ATTRS{../`によって参照します。

```:/etc/udev/rules.d/10-soracom.rules
KERNEL=="ttyUSB*", ATTRS{../idVendor}=="1c9e", ATTRS{../idProduct}=="6801", ATTRS{bNumEndpoints}=="02", ATTRS{bInterfaceNumber}=="02", SYMLINK+="modem"
```

リブートしてシンボリックリンクが張られていることを確認します。

```shell-session
pi@raspberrypi:~ $ ls -l /dev/ttyUSB* /dev/modem
lrwxrwxrwx 1 root root         7  2月 26 07:48 /dev/modem -> ttyUSB2
crw-rw---- 1 root dialout 188, 0  2月 26 07:48 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1  2月 26 07:48 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2  2月 26 07:48 /dev/ttyUSB2
```

`wvdial` はデフォルトで `/dev/modem` を使うので、 `/etc/vwconf.conf` で `/dev/ttyUSB2` を指定している箇所を削除します。

```udiff
--- a/wvdial.conf
+++ b/wvdial.conf
@@ -9,7 +9,6 @@ Dial Command = ATD
 Stupid Mode = yes
 Baud = 460800
 New PPPD = yes
-Modem = /dev/ttyUSB2
 ISDN = 0
 APN = soracom.io
 Phone = *99***1#
```

再度 `ifup soracom` で接続されることを確認します。

# デバイス挿入時に接続する

[Raspberry Pi 2 + systemd + udevで、USBデバイス挿入時にサービスを起動する](http://thinkami.hatenablog.com/entry/2015/06/25/064658)に倣って先程の `udev` ルールを以下のように書き換えます。後述するように `action` を記述しないのがポイントです。

```/etc/udev/rules.d/10-soracom.rules
KERNEL=="ttyUSB*", ATTRS{../idVendor}=="1c9e", ATTRS{../idProduct}=="6801", ATTRS{bNumEndpoints}=="02", ATTRS{bInterfaceNumber}=="02", SYMLINK+="modem", ENV{SYSTEMD_WANTS}="ifup@soracom"
```

リブートすると、デバイス認識時に `ifup soracom` が自動実行されて接続されます。

```shell-session
pi@raspberrypi:~ $ ls -l /dev/ttyUSB* /dev/modem
lrwxrwxrwx 1 root root         7  2月 26 20:55 /dev/modem -> ttyUSB2
crw-rw---- 1 root dialout 188, 0  2月 26 20:55 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1  2月 26 20:55 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 2  2月 26 22:10 /dev/ttyUSB2
pi@raspberrypi:~ $ ifconfig ppp0
ppp0      Link encap:Point-to-Pointプロトコル  
          inetアドレス:10.xxx.xxx.xxx P-t-P:10.64.64.64  マスク:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  メトリック:1
          RXパケット:7 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:8 エラー:0 損失:0 オーバラン:0 キャリア:0
      衝突(Collisions):0 TXキュー長:3 
          RXバイト:130 (130.0 B)  TXバイト:181 (181.0 B)
```

この状態でデバイスを抜いて挿すと、デバイス番号がずれますが、シンボリックリンクが追従し、再接続されます。

```shell-session
pi@raspberrypi:~ $ ls -l /dev/ttyUSB* /dev/modem
lrwxrwxrwx 1 root root         7  2月 26 23:10 /dev/modem -> ttyUSB3
crw-rw---- 1 root dialout 188, 0  2月 26 23:10 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1  2月 26 23:10 /dev/ttyUSB1
crw-rw---- 1 root dialout 188, 3  2月 26 23:12 /dev/ttyUSB3
pi@raspberrypi:~ $ ifconfig ppp0
ppp0      Link encap:Point-to-Pointプロトコル  
          inetアドレス:10.xxx.xxx.xxx P-t-P:10.64.64.64  マスク:255.255.255.255
          UP POINTOPOINT RUNNING NOARP MULTICAST  MTU:1500  メトリック:1
          RXパケット:7 エラー:0 損失:0 オーバラン:0 フレーム:0
          TXパケット:8 エラー:0 損失:0 オーバラン:0 キャリア:0
      衝突(Collisions):0 TXキュー長:3 
          RXバイト:130 (130.0 B)  TXバイト:181 (181.0 B)
```

# 補足
## wvdial で他の接続先も使う場合
`/etc/wvdial.conf` に `Defaults` 以外の Dialer セクションを作りそちらに SORACOM 用の設定を移します。
その上で、 `/etc/network/interfaces` で provider としてそのセクションを指定します。

### 例
```udiff
--- a/wvdial.conf
+++ b/wvdial.conf
@@ -1,4 +1,4 @@
-[Dialer Defaults]
+[Dialer soracom]
 Init1 = ATZ
 Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
 Init3 = AT+CGDCONT=1,"IP","soracom.io"
```
```diff
--- a/network/interfaces
+++ b/network/interfaces
@@ -25,3 +25,4 @@ iface wlan1 inet manual
 
 allow-hotplug soracom
 iface soracom inet wvdial
+    provider soracom
```

## `ifdown` について

`systemd` の Unit ファイルには `ifup@.service` はあっても、 `ifdown@.service` はないのですが、

```shell-session
pi@raspberrypi:~ $ systemctl list-unit-files | grep 'if.*@'
ifup@.service                          static
```

`ifup@.service` の中身が以下のようになっており、 `udev` ルールで `action` を書かなければ `add` 時に `ifup` 、 `remove` 時に `ifdown` が実行されます。

```shell-session
pi@raspberrypi:~ $ cat /lib/systemd/system/ifup@.service 
[Unit]
Description=ifup for %I
After=local-fs.target network-pre.target networking.service
Before=network.target
BindsTo=sys-subsystem-net-devices-%i.device
ConditionPathIsDirectory=/run/network
DefaultDependencies=no

[Service]
ExecStart=/sbin/ifup --allow=hotplug %I
ExecStop=/sbin/ifdown %I
RemainAfterExit=true
```

# 参考

下記の手法を組み合わせることで実現できました。ありがとうございます。

[簡単にRaspberry PiでSORACOM Airを使う(FS01BU USBモデムを用いて自動接続)](http://qiita.com/dietposter/items/068019ee53252206a6bf)
[デバイス名を固定する](http://qiita.com/osada9000/items/3e6ff429ba782624def1)
[Raspberry Pi 2 + systemd + udevで、USBデバイス挿入時にサービスを起動する](http://thinkami.hatenablog.com/entry/2015/06/25/064658)