IPv6環境でひかり電話を外出先から使う（by RPi + WireGuard or OpenVPN）

# はじめに

ひかり電話の内線としてSIPクライアントを使用することは「[スマホdeひかり電話](https://flets.com/hikaridenwa/smartphone/)」として案内されている公式機能である．この機能はホームゲートウェイ（以下HGW）のLAN側と通信できれば使用できるので，外出先であってもVPN接続を行えばスマホからひかり電話を使うことができた．

一方で[フレッツ光環境においてIPv6プラスを有効にすることでPPPoEの混雑を回避して高速なネット接続を実現した場合](https://qiita.com/nbhr/items/2850ccaa3456a8553bbf)には，HGWなどによるVPN接続機能を使用することはできず，結果として外出先からひかり電話を使うこともできないと説明されることが多い．

しかし実際にはIPv6プラス環境でもVPNは使用可能であり，ひかり電話も外出先から利用可能である．以下ではその設定方法を説明する．

## おことわり

~~この記事に書いた内容によって外出先のスマホからひかり電話の発着信が可能となりますが，末尾に述べるように「スマホからのすべての通信がVPNを通る」ことになってしまいます．これを「ひかり電話関係の通信のみがVPNを通り，それ以外の通信は直接インターネットに流れる」ようにすることはまだできておらず，理想的な実装とはなっていません．~~
解決しました．

# 想定する環境

* ネット環境は「フレッツ光 + IPv6プラス」 
  * IPv6プラスで割り当てられるIPv4アドレスは111.222.333.444
* ホームゲートウェイ (以下の例ではPR-500KI) のLAN側アドレスは192.168.1.1
* 適当なLinux機（以下の例ではラズベリーパイ（debian 10.7））が利用でき，そのアドレスは192.168.1.2（何でもよいが，DHCPであっても固定アドレスが割り振られるとする．HGWで設定できる）

最終的に下記のようなネットワーク構成を目指す．

```
+-------------------------------Mobile
| VPN client <-- SIP client app      |
+------------------------------------+
     |
     | Internet
     v
+-[111.222.333.444:12345]----------HGW
|    |                               |
|    |            Hikari SIP server  |
+--- | -----------[192.168.1.1:5060]-+
     |                        ^
     | Home NW 192.168.1.0/24 |
     v                        |
+-[192.168.1.2:1194]--------- | ---RPi
|    | VPN server             |      |
|    |                        |      |
|    | 192.168.2.0/24         |      |
|    +---------------> NAT ---+      |
|                                    |
+------------------------------------+
```

# 方針

大まかな流れは以下の通り．

1. IPv6プラスで利用できる「利用可能ポート」のうちの１つを使ってインターネットからLinux機への通信を許可する
2. Linux機でVPNサーバを立ち上げる
3. VPNからHGWへのSIPアクセスを許すようにiptablesを設定する

# 1. 「利用可能ポート」の設定

まず http://192.168.1.1:8888/enabler.ipv4/main にアクセスして利用可能ポートの一覧を表示し，適当なものを１つ選ぶ．今回の例ではポート `12345` 番とする．

続いて左のメニューから「静的NAPT設定」（[http://192.168.1.1:8888/enabler.ipv4/napt_entry_settings](http://192.168.1.1:8888/enabler.ipv4/napt_entry_settings)）を開き，

* 対象プロトコル： `UDP`
* 公開対象ポート： `12345`　（上記で選んだ利用可能ポート）
* 宛先アドレス　： `192.168.1.2`　（Linux機のアドレス）
* 宛先ポート　　： `1194`　（適当でよいが，わかりやすくOpenVPNのものを使う）

というエントリを加える．

# 2. Dynamic DNS

外部IPv4アドレス111.222.333.444は固定IPではない（頻繁に変わるわけでも無いようだが）．
そのため必須ではないものの，実用上は外部IPv4アドレスをDynamic DNS (例：mydns.jp) に登録しておき， `ddclient` などを用いてIPv4アドレスの変更を反映させることが望ましい．以下では `foo.mydns.jp` とする．

# 3. VPNの設定

ここまでの設定によって，外部からLinux機に `foo.mydns.jp:12345` としてアクセスすることができる．したがってこのアドレスで適当なサーバを待ち受けさせてVPNを張ればよい．つまり必要なものは「任意のポート番号（今回は `12345` 番）で待ち受けることができるVPNサーバ」であり，大きく以下の選択肢が考えられる．

1. SIP over L2 VPN (tap)
2. SIP over L3 VPN (tun)
3. SIP over SSH SOCKS proxy

まず L2 VPN ではクライアントがHGWと同一ネットワークにbridge接続されるので，ひかり電話につなぐという目的に対してはもっとも素直な実現方法であると思われる．しかし現時点ではAndroid / iPhoneともにbridge接続型のVPNアプリに対応していないので，L2 VPNはあきらめざるを得ない．

* [Why does the Android app not support TAP-style tunnels?](https://openvpn.net/faq/why-does-the-app-not-support-tap-style-tunnels/)
* [Why doesn’t the iOS app support tap-style tunnels?](https://openvpn.net/faq/why-doesnt-the-app-support-tap-style-tunnels/)

またpoor man's VPNとも呼ばれるSSHによるSOCKSプロキシ（Dynamic Forward）を使うことができれば，当該ポートでSSHサーバが待ち受ければ良いだけなので最も簡単な実装方法となりえる．しかしSOCKSに対応したSIPクライアントは見当たらず（SIPプロトコル的に無理筋と思われる），これも実際には実現できない．

結果としてL3 VPNが選択肢として残る．以下では OpenVPN と WireGuard を用いる方法を説明する（WireGuard のほうが一般に接続確立，通信速度とも高速である）．いずれの場合においても，L3 VPNではクライアント（外出先のスマホ）がHGWやLinux機とは別のプライベートネットワークに接続されることとなるため，HGWとの間でSIP接続をするためにもうひと手間が必要となる（後述）．

なお以下ではWireGuard, OpenVPNともに同じポート番号 `1194` を用いて説明している．両者を併存させる場合は別ポートを使用し，HGWでの転送設定も同様に２つ用意すること．

## 3-1. WireGuardの場合

主に[この記事](https://qiita.com/naomori/items/1ee1a314cbdd3010f3ee)に沿ってインストールすることができる．

```
# apt update
# apt upgrade
# apt install raspberrypi-kernel-headers
# echo "deb http://deb.debian.org/debian/ unstable main" | tee --append /etc/apt/sources.list.d/unstable.list
# apt install dirmngr 
# wget -O - https://ftp-master.debian.org/keys/archive-key-$(lsb_release -sr).asc | apt-key add -
# printf 'Package: *\nPin: release a=unstable\nPin-Priority: 150\n' | tee --append /etc/apt/preferences.d/limit-unstable
# apt update
# apt install wireguard 
# reboot
```

続いて鍵の作成を行う．

```
$ su
# cd /etc/wireguard
# umask 077
# wg genkey > server_private.key
# wg pubkey > server_public.key < server_private.key
# wg genkey > client_private.key
# wg pubkey > client_public.key < client_private.key
```

以下の内容で `/etc/wireguard/wg0.conf` と `/etc/wireguard/client.conf` を作成する．

```wg0.conf
[Interface]
Address = 192.168.2.1/24
ListenPort = 1194

PrivateKey = （server_private.keyの内容）

PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -A FORWARD -o %i -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -D FORWARD -o %i -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

[Peer]
PublicKey = （client_public.keyの内容）
AllowedIPs = 192.168.2.2/32
```

```client.conf
[Interface]
Address = 192.168.2.2/24
DNS = 192.168.1.1

PrivateKey = （client_private.keyの内容）

[Peer]
PublicKey = （server_public.keyの内容）
AllowedIPs = 192.168.2.1/32, 192.168.1.0/24

Endpoint = foo.mydns.jp:12345
PersistentKeepAlive = 30
```

この `client.conf` をスマホに何とかして送るか，`qrencode`（なければ `apt install qrencode`）でQRコード画像を作成し，これを何とかして画面表示してスマホの [WireGuard アプリ](https://play.google.com/store/apps/details?id=com.wireguard.android)で読み取る．

```
# qrencode -o a.png < client.conf
```

最後にデーモンとして登録して起動する．

```
# systemctl enable wg-quick@wg0
# systemctl start wg-quick@wg0
```

念のため

```
# systemctl status wg-quick@wg0
```

により起動を確認しておくこと．

## 3-2. OpenVPNの場合

まず以下の手順で公開鍵を作成する（`gen-dh` には相当時間がかかる）．

```
# apt update
# apt upgrade
# apt install openvpn openssl easy-rsa
# cd /usr/share/easy-rsa/
# ./easyrsa init-pki
# ./easyrsa build-ca
# ./easyrsa gen-dh
# ./easyrsa build-server-full server nopass
# ./easyrsa build-client-full client nopass
```

次に OpenVPN サーバの設定を行う．

```
# cd /etc/openvpn/
# gzip -cd /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz > server.conf
# cp /usr/share/easy-rsa/pki/private/client.key .
# cp /usr/share/easy-rsa/pki/private/server.key .
# cp /usr/share/easy-rsa/pki/issued/client.crt .
# cp /usr/share/easy-rsa/pki/issued/server.crt .
# cp /usr/share/easy-rsa/pki/dh.pem .
# cp /usr/share/easy-rsa/pki/ca.crt .
# openvpn --genkey --secret ta.key
# vi server.conf
```

```server.conf
port 1194
proto udp
dev tun
ca /etc/openvpn/ca.crt
cert /etc/openvpn/server1.crt
key /etc/openvpn/server1.key  # This file should be kept secret
dh /etc/openvpn/dh.pem
server 192.168.3.0 255.255.255.0
push "route 192.168.1.0 255.255.255.0"
keepalive 10 120
tls-auth /etc/openvpn/ta.key 0 # This file is secret
cipher AES-256-CBC
persist-key
persist-tun
status /var/log/openvpn/openvpn-status.log
verb 3
explicit-exit-notify 1
```

続いてクライアント用ファイルとして下記 `client.ovpn` を作成し，`client.crt`, `client.key`, `ta.key`, `ca.crt` とともにスマホに送り，[OpenVPNアプリ](https://play.google.com/store/apps/details?id=net.openvpn.openvpn)から読み込む．

```client.ovpn
client
dev tun
proto udp
remote foo.mydns.jp 12345
resolv-retry infinite
nobind
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
remote-cert-tls server
tls-auth ta.key 1
cipher AES-256-CBC
verb 3
pull
float
```



# 4. SIPのためのNAT設定

今回のVPNはL3接続であるため，Linux機でNATを構成しなければHGWに到達できない．またさらに，SIPの立場で考えれば，NAT配下にあるVPNクライアントが，NAT外（＝HGWが存在するLAN）のSIPサーバに接続する関係になるため，SIPがNATを超えることができるように，いわゆる Application-level Gateway を用意しなくてはならない．Linux では `nf_nat_sip` モジュールでこの機能が実現されている．

まず `/etc/modules` に以下の１行を追加する．

```/etc/modules
nf_nat_sip
```

続いて `/etc/sysctl.conf` に以下の行を追加する．

```/etc/sysctl.conf
net.ipv4.ip_forward=1
net.netfilter.nf_conntrack_helper = 1
```

最後に `/etc/rc.local` に以下の行を追加する．

```/etc/rc.local
iptables -t nat -A POSTROUTING -s 192.168.2.0/24 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 192.168.3.0/24 -o eth0 -j MASQUERADE
```

なおこの `iptables` の設定は最低限のものであり，特にデフォルトのポリシーを ACCEPT にしていない場合は適宜設定を行う必要がある（Raspbian / Raspberry Pi OSのデフォルトは全ACCEPT）．

# 5. VPNの確認

ここまでで一度Linux機を再起動し，下記によりVPNサーバが起動していることを確認する．

```
# systemctl status openvpn
# systemctl status wg-quick@wg0
```

続いて `sysctl.conf` の内容が反映されていることを下記により確認する．

```
# sysctl net.netfilter.nf_conntrack_helper
net.netfilter.nf_conntrack_helper = 1
```

```
# sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1
```

また `nf_nat_sip` が読み込まれていることを下記により確認する．

```
# lsmod | grep nf_nat_sip
nf_nat_sip             20480  0
nf_conntrack_sip       36864  1 nf_nat_sip
nf_nat                 49152  3 nf_nat_sip,xt_MASQUERADE,nft_chain_nat
nf_conntrack          135168  6 xt_state,nf_conntrack_sip,nf_nat_sip,xt_MASQUERADE,xt_conntrack,nf_nat
```

最後にNATが設定されていることを確認する．

```
# iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  192.168.2.0/24       anywhere
MASQUERADE  all  --  192.168.3.0/24       anywhere

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
```

ここまでの段階で，スマホのWiFiを切ってモバイル回線にしたうえで，WireGuard もしくは OpenVPN で接続ができるはずである．接続ができたならば，

1. WEBブラウザで HGW (http://192.168.1.1/) を開くことができるか？
2. AGEPhoneなどのSIPクライアントで発呼できるか？

を確認する．AGEPhoneの場合，VPNをつないだ状態であれば自動的にHGWを見つけてプロファイルを構成できるはずである．そのうえで，SIPサーバがNATの先にあることを明示するために，

* 高度な設定→アカウント→Contact値の置換：グローバル

としておく．またモバイル回線を使用する場合は，

* 高度な設定→ネットワーク→モバイルネットワークを使用：チェックをいれる

とする．加えて，使用帯域を最低限にするために

* 高度な設定→メディア→コーデックの優先順位（3G/LTE）：Speex/8000, GSM, Speex/16000など軽量なものを最優先にする

としておいたほうがよい（外出先のWiFiから接続する場合はコーデックの優先順位（WiFi）も変更するべきだが，その場合は自宅WiFiにつないだ場合にも反映される点に注意）．


# まとめと課題

以上により，スマホのモバイル回線，つまり外出先から，ひかり電話を使用することができるようになった．なお上記の設定はひかり電話関連の通信のみがVPNに流れて，それ以外は通常の経路に流れる．

もしすべての通信をフレッツ経由にしたい場合は，WireGuardでは

```
- AllowedIPs = 192.168.2.1/32, 192.168.1.0/24
+ AllowedIPs = 0.0.0.0/0
```

とし，OpenVPN では

```
- push "route 192.168.1.0 255.255.255.0"
+ push "redirect-gateway def1 bypass-dhcp"
+ push "dhcp-option DNS 192.168.1.1"
```

とすればよい．

また今回はドコモ回線を想定してVPNに用いるネットワークを `192.168.2.0/24` などとした．出先のWiFi環境で使用する場合は，

* ネットワークアドレスが重複しておらず，かつ
* ポート `12345` 番へのアクセスが許されている

ときに限って動作する点に注意が必要である．