Raspberry PiとSORACOMを使って回線を安価に冗長化する

# 概要

[Raspberry PiでUSBモデム挿入時にSORACOMへ自動接続する](http://qiita.com/gimy/items/1d49f8e08210e9c9f20a)で RPi2 を使って SORACOM への接続ができるようになったので、それを冗長化回線として使うようにしました。

# 仕様

* 主回線を既設の FTTH 、予備回線を SORACOM Air とします
* 両回線友にルータには RPi2 を使います
* 主回線時は予備回線側に自動切り替えし、回復時には切り戻します
* SORACOM Air 側も常時接続とします
    * LAN からインターネットへの接続の冗長化だけであれば障害時だけダイヤルアップすれば十分ですが、別途 VPN を使って外部からのアクセス経路の冗長化も予定しているため常時接続としました
* SORACOM Air の速度クラスは通常 s1.minimum とし、主回線障害時には s1.fast に上げます
* 主回線の障害の有無は特定サイトへの ping の成否で判断します
    * ppp インターフェースの監視だけだとプロバイダと上流間での障害を検知できないため
* 通常時は sub も main をデフォルト GW とします
    * なるべく SORACOM 側にトラフィックを生じさせないため
* 主回線側に障害が生じても main 自体が生きていれば sub をデフォルト GW として main もインターネット接続性を維持します

# 構成

* 主回線側ルータ(main)に 192.168.0.1 、予備回線側ルータ(sub)に 192.168.0.2 を割り当てます
* VIP 192.168.0.254 を LAN 内のデフォルト GW とし、回線状況によって主回線・予備回線のどちらかに割り当てます

## 通常時

```
            FTTH          SORACOM
   ppp0(主回線)|              | ppp0(予備回線)
          +---+---+      +---+---+
          |  main |      |  sub  |
          +-------+      +-------+
  192.168.0.1 |eth0      eth0| 192.168.0.2
192.168.0.254 +-------+------+
                     |
     ----------------+------------------ LAN
```

障害時

```
            FTTH          SORACOM
   ppp0(主回線)X              | ppp0(予備回線)
          +---+---+      +---+---+
          |  main |      |  sub  |
          +-------+      +-------+
  192.168.0.1 |eth0      eth0| 192.168.0.2
              +-------+------+ 192.168.0.254
                     |
     ----------------+------------------ LAN
```

# 準備

## ルータと DHCP の設定

main と sub それぞれが NAT ルータになるように iptables 等の設定を済ませておきます。
また、 VIP 192.168.0.254 が LAN 内のデフォルト GW になるよう DHCP サーバーの設定を変更しておきます。

## 速度クラス変更専用の SORACOM ユーザーを作成

予備回線への切り替え時に SORACOM の速度クラスを s1.fast に変更するので専用のユーザーを作成します。 keepalived から呼び出すのでユーザー名も keepalived にしておきます。予め [SORACOM SDK for Ruby](https://dev.soracom.io/jp/tools/) を参照して SORACOM のコマンドラインインターフェースが使えるようにしておきます。この作業は sub 側で行います。

ユーザー作成:

```shell-session
pi@sub:~ $ soracom user create_user --user-name=keepalived             
{
  "result": "success"
}
```

権限付与:

```shell-session
pi@sub:~ $ soracom user update_permission --user_name=keepalived --permission="{\"statements\": [ {\"effect\": \"allow\", \"api\": \"Subscriber:updateSpeedClass\"} ] }"
{
  "result": "success"
}
```

認証設定:
keepalived は root で動作するのですが、その際の HOME は / になるので、認証情報は /.soracom に保存しておく必要があります。

```shell-session
pi@sub:~ $ soracom user generate_auth_key --user-name=keepalived
{
  "authKeyId": “keyId-<略>",
  "authKey": "secret-<略>"
}
pi@sub:~ $ soracom configure --profile=keepalived
--- SORACOM CLI setup ---
This will create .soracom directory under /home/pi and place 'keepalived.json' in it.

Please select which authentication method to use.

1. Input AuthKeyId and AuthKey *Recommended*
2. Input Operator credentials (Operator Email and Password)
3. Input SAM credentials (OperatorId and UserName and Password)

select(1-3)> 1
authKeyId: keyId-<略>
authKey: 
wrote to /home/pi/.soracom/keepalived.json
pi@sub:~ $ sudo cp -r /home/pi/.soracom/keepalived.json /
```

# 主回線側(main)

## keepalived のインストールと設定

```shell-session
pi@main:~ $ sudo apt-get install -y keepalived
```

main がバックアップに回る際のスクリプトを作成

```bash:/etc/keepalived/backup.sh
#!/bin/sh
ip route replace default via 192.168.0.254 dev eth0
```

main がマスターに戻る際のスクリプトを作成

```bash:/etc/keepalived/master.sh
#!/bin/sh
ip route replace default dev ppp0
```

keepalived の設定ファイルを作成

```:/etc/keepalived/keepalived.conf
global_defs {
        notification_email {
# メール設定は省略
        }
}
 
vrrp_script check_ftth {
        script "ping -n -s 0 -c 1 -I ppp0 <ping対象のIPアドレス>"
        interval 1
        fall 3
        raise 3
}
 
vrrp_instance VI_1 {
        interface eth0
        state MASTER
        virtual_router_id 51
        priority 100
        advert_int 1
        authentication {
                auth_type PASS
                auth_pass xxxxxxxx
        }
        virtual_ipaddress {
                192.168.0.254/24 brd 192.168.0.255 dev eth0
        }
        track_script {
                check_ftth
        }
        notify_master "/etc/keepalived/master.sh"
        notify_backup "/etc/keepalived/backup.sh"
        notify_fault "/etc/keepalived/backup.sh"
} 
```

keepalived を起動します

```shell-session
pi@main:~ $ sudo systemctl start keepalived
```

# 予備回線側(sub)

## SORACOM Air への接続設定

例えば[Raspberry PiでUSBモデム挿入時にSORACOMへ自動接続する](http://qiita.com/gimy/items/1d49f8e08210e9c9f20a)等の設定により、 sub から SORACOM Air に自動で常時接続されるようにしておきます。

また、今回の設定では障害が発生しない限り SORACOM Air に一切のパケットが流れません。 SORACOM Air では1時間の無通信で回線が切断されますので、切れる前に ppp 側に ping を打つように crontab に登録しておきます。

```shell-session
pi@sub:~ $ sudo crontab -e
<略>
0 * * * * ping -w 3 -n 10.64.64.64
```

## keepalived のインストールと設定

```shell-session
pi@sub:~ $ sudo apt-get install -y keepalived
```

sub がマスターになる場合のスクリプトを作成

```bash:/etc/keepalived/master.sh
#!/bin/sh
ip route replace default dev ppp0
echo 1 > /proc/sys/net/ipv4/ip_forward
/usr/local/bin/soracom sim update_speed_class —-imsi=<SORACOM Air SIM の IMSI> --speed-class=s1.fast --profile=keepalived > /dev/null 2>&1
```

sub がバックアップに戻る際のスクリプトを作成

```bash:/etc/keepalived/backup.sh
#!/bin/sh
/usr/local/bin/soracom sim update_speed_class —imsi=<SORACOM Air SIM の IMSI> --speed-class=s1.minimum --profile=keepalived > /dev/null 2>&1
echo 0 > /proc/sys/net/ipv4/ip_forward
ip route replace default via 192.168.0.254 dev eth0
```

keepalived の設定ファイルを作成

```:/etc/keepalived/keepalived.conf
global_defs {
        notification_email {
# メール設定は省略
        }
}
 
vrrp_instance VI_1 {  
        interface eth0  
        state BACKUP  
        virtual_router_id 51  
        priority 50  
        advert_int 1  
        authentication {  
                auth_type PASS  
                auth_pass xxxxxxxx  
        }  
        virtual_ipaddress {  
                192.168.0.254/24 brd 192.168.0.255 dev eth0  
        }  
        notify_master "/etc/keepalived/master.sh"  
        notify_backup "/etc/keepalived/backup.sh"  
        notify_fault "/etc/keepalived/backup.sh"  
}  
```

main と同様に keepalived を起動します。

```shell-session
pi@sub:~ $ sudo systemctl start keepalived
```

# 動作確認

## 通常時

main に 192.168.0.254 が割り当たっていることを確認します。

```shell-session
pi@main:~ $ ip -f inet addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.0.1/24 brd 192.168.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.254/24 brd 192.168.0.255 scope global secondary eth0
       valid_lft forever preferred_ft forever
```

main で FTTH 回線がデフォルト GW になっていることを確認します。

```shell-session
pi@main:~ $ ip route show 
default dev ppp0  scope link 
<FTTHから割り当てられたアドレス> dev ppp0  proto kernel  scope link  src <FTTHでの対向アドレス>
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.1 
```

LAN 内のホストから外部への traceroute が main 経由になっていることを確認します。

```shell-session
gimy@localhost ~ $ traceroute -n 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 64 hops max, 52 byte packets
 1  192.168.0.1  2.234 ms  1.151 ms  1.173 ms
<略>
 9  8.8.8.8  4.613 ms  5.948 ms  4.475 ms
```

## 障害時

外部サイトに ping を打ち続けておきます。

```
gimy@locahost ~ $ ping -n 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
64 bytes from 8.8.8.8: icmp_seq=0 ttl=56 time=5.142 ms
64 bytes from 8.8.8.8: icmp_seq=1 ttl=56 time=6.954 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=56 time=8.226 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=56 time=7.622 ms
<略>
```
main 側の ppp を落とし疑似的に障害状態を作ります。

```shell-session
pi@main:~ $ sudo poff dsl-provider
```
先程開始した ping が途中で通らなくなりやがて回復することを確認します。

```shell-session
64 bytes from 8.8.8.8: icmp_seq=21 ttl=56 time=5.205 ms
64 bytes from 8.8.8.8: icmp_seq=22 ttl=56 time=7.666 ms
64 bytes from 8.8.8.8: icmp_seq=23 ttl=56 time=5.761 ms
64 bytes from 8.8.8.8: icmp_seq=24 ttl=56 time=5.418 ms
64 bytes from 8.8.8.8: icmp_seq=25 ttl=56 time=7.017 ms
64 bytes from 8.8.8.8: icmp_seq=26 ttl=56 time=6.637 ms
64 bytes from 8.8.8.8: icmp_seq=27 ttl=56 time=5.582 ms
92 bytes from 192.168.0.1: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 5400 8904   0 0000  40  01 2087 192.168.0.102  8.8.8.8 

Request timeout for icmp_seq 28
92 bytes from 192.168.0.1: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 5400 b0a6   0 0000  40  01 f8e4 192.168.0.102  8.8.8.8 

Request timeout for icmp_seq 29
92 bytes from 192.168.0.1: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 5400 23cd   0 0000  40  01 85be 192.168.0.102  8.8.8.8 

Request timeout for icmp_seq 30
92 bytes from 192.168.0.1: Destination Net Unreachable
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 5400 6cb1   0 0000  40  01 3cda 192.168.0.102  8.8.8.8 

Request timeout for icmp_seq 31
Request timeout for icmp_seq 32
92 bytes from 192.168.0.1: Redirect Host(New addr: 192.168.0.254)
Vr HL TOS  Len   ID Flg  off TTL Pro  cks      Src      Dst
 4  5  00 0054 57b0   0 0000  3f  01 52db 192.168.0.102  8.8.8.8 

Request timeout for icmp_seq 33
64 bytes from 8.8.8.8: icmp_seq=34 ttl=55 time=131.398 ms
64 bytes from 8.8.8.8: icmp_seq=35 ttl=55 time=136.203 ms
64 bytes from 8.8.8.8: icmp_seq=36 ttl=55 time=391.485 ms
64 bytes from 8.8.8.8: icmp_seq=37 ttl=55 time=188.250 ms
64 bytes from 8.8.8.8: icmp_seq=38 ttl=54 time=143.007 ms
64 bytes from 8.8.8.8: icmp_seq=39 ttl=54 time=597.688 ms
<略>
```

sub 経由になっていることを確認します。

```shell-session
gimy@localhost ~ $ traceroute -n 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 64 hops max, 52 byte packets
 1  192.168.0.2  2.949 ms  1.179 ms  1.152 ms
<略>
    8.8.8.8  130.123 ms
```

sub に 192.168.0.254 が割り当たっていることを確認します。

```shell-session
pi@sub:~ $ ip -f inet addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.0.2/24 brd 192.168.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet 192.168.0.254/24 brd 192.168.0.255 scope global secondary eth0
       valid_lft forever preferred_ft forever
```

sub で SORACOM 側がデフォルト GW になっていることを確認します。

```shell-session
gimy@sub:~ $ ip route show
default dev ppp0  scope link 
10.64.64.64 dev ppp0  proto kernel  scope link  src <SORACOM から割り当てられたアドレス>
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.2 
```

## 切り戻し

main の ppp 接続を回復させ、切り戻されていることを確認します。

```shell-session
pi@main:~ $ sudo pon dsl-provider
Plugin rp-pppoe.so loaded.
```

確認内容は通常時と同じです。

# 感想

s1.fast であっても 2Mbps なので、 SORACOM Air 側に failover した状態で LAN 内のクライアント機から Web ブラウズするとやはりストレスを感じます。しかし、月額300円程度で予備回線を維持できるというのはそれなりに意味があると思います。

# 参考

[SORACOM SDK for Ruby](https://dev.soracom.io/jp/tools/)