WireGuardという素晴らしいVPNソフトウェアがあります。シンプルなのでほとんどハマりどころはないのですが、自宅で試していたところ少し意図しない挙動を見つけたのでその理由と対処方法をまとめます。 WireGuard: fast, modern, secure VPN tunnel
何が素晴らしいのか、詳細が気になる方はこちらのスライドをご覧になるのがいいと思います。 [CB16] WireGuard:次世代耐乱用性カーネルネットワークトンネル by Jason Donenfeld
AllowedIPsに0.0.0.0/0を指定するとパケットが全てVPNインターフェイスに吸い込まれる
AllowedIPsに0.0.0.0/0を指定するのはどんなときかと言うと、VPNインターフェイス経由で送信するパケットの宛先をすべて許可したい時です。例えば、カフェのFree Wi-Fiが信用できない時にすべてのトラフィックをVPNサーバ経由にしたい時はVPN経由のパケットの宛先IPは何が来るかわからないのですべての宛先のパケットを許可するために0.0.0.0/0を指定しますね。
もしくは、少数派な使い方かもしれませんが、L3 VPNなのでstatic routingをしたり、VPNの両端のインターフェイスでルーティングプロトコル(ospf, bgpなど)を喋るデーモンを起動して動的に経路情報を交換するようにするかもしれません。ルーティングテーブルで宛先を制御する場合も意図せずパケットがフィルタされないようにするためにAllowedIPsは0.0.0.0/0にすると思います。
このような意図で0.0.0.0/0を指定する場合、実際に0.0.0.0/0にマッチするパケット、つまり全てのパケット(後述しますが厳密には全てではありません)がWireGuardのインターフェイスを経由してほしいわけではありません。が、 wg-quick
コマンドを使ってWireGuardのインターフェイスを作成すると全てのパケットがWireGuardのインターフェイスに吸い込まれてしまう現象が起きます。
現象の確認
OSはUbuntu18.04です。
ubuntu@c01:~$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:16:3e:a3:9e:eb brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.59.79.145/24 brd 10.59.79.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::216:3eff:fea3:9eeb/64 scope link valid_lft forever preferred_lft forever
NICは1つだけあります。
WireGuardの設定ファイルを作ってみました。wg0.confの中身はこんな感じです。
ubuntu@c01:~$ cat /etc/wireguard/wg0.conf [Interface] Address = 10.0.0.1/24 SaveConfig = true ListenPort = 51820 PrivateKey = GIWLRkDQXS/wym2pCWW2VDKTBpUp2a60AB+KSG18SEE= [Peer] PublicKey = uKjZpzBmrsp7YGqhpTLajsk2XiKj0HHYoRMkLVOaE2c= AllowedIPs = 0.0.0.0/0 Endpoint = 192.168.100.61:51820
192.168.100.61:51820
というのがVPNの接続先だと仮定します。(適当なIPなので実際には対向は存在しませんが今回は問題ではないのでこのまま続けます)
WireGuardを起動してみる
wg-quick
コマンドを使って起動してみます。
ubuntu@c01:~$ sudo wg-quick up wg0.conf [#] ip link add wg0 type wireguard [#] wg setconf wg0 /dev/fd/63 [#] ip address add 10.0.0.1/24 dev wg0 [#] ip link set mtu 1420 dev wg0 [#] ip link set wg0 up [#] wg set wg0 fwmark 51820 [#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 [#] ip -4 rule add not fwmark 51820 table 51820 [#] ip -4 rule add table main suppress_prefixlength 0 ubuntu@c01:~$ sudo wg interface: wg0 public key: M91GRcFHFdXtRL4UXh9d18sv1w9/KYbibOYcGyLzAwk= private key: (hidden) listening port: 51820 fwmark: 0xca6c peer: uKjZpzBmrsp7YGqhpTLajsk2XiKj0HHYoRMkLVOaE2c= endpoint: 192.168.100.61:51820 allowed ips: 0.0.0.0/0 ubuntu@c01:~$ ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 inet 127.0.0.1/8 scope host lo valid_lft forever preferred_lft forever inet6 ::1/128 scope host valid_lft forever preferred_lft forever 3: wg0: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000 link/none inet 10.0.0.1/24 scope global wg0 valid_lft forever preferred_lft forever 24: eth0@if25: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 00:16:3e:a3:9e:eb brd ff:ff:ff:ff:ff:ff link-netnsid 0 inet 10.59.79.145/24 brd 10.59.79.255 scope global eth0 valid_lft forever preferred_lft forever inet6 fe80::216:3eff:fea3:9eeb/64 scope link valid_lft forever preferred_lft forever
wg0
インターフェイスが追加されましたね。
インターネットに向けてpingを打ってみる
8.8.8.8に向けてpingを打ってみました。
ubuntu@c01:~$ ping -c 3 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. --- 8.8.8.8 ping statistics --- 3 packets transmitted, 0 received, 100% packet loss, time 2055ms
…あれ?パケットが全部落ちていますね。ちなみに同時にtcpdumpをしてみたのですが、
ubuntu@c01:~$ sudo tcpdump -n icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on wg0, link-type RAW (Raw IP), capture size 262144 bytes 12:52:34.626312 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 1667, seq 3, length 64 12:52:35.650419 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 1667, seq 4, length 64 12:52:36.674427 IP 10.0.0.1 > 8.8.8.8: ICMP echo request, id 1667, seq 5, length 64
wg0
インターフェイスのIPからインターネットに出ようとしていますね。これは変ですね。
WireGuardを落とすとどうなるでしょう?
ubuntu@c01:~$ sudo wg-quick down wg0.conf [#] wg showconf wg0 [#] ip -4 rule delete table 51820 [#] ip -4 rule delete table main suppress_prefixlength 0 [#] ip link delete dev wg0 ubuntu@c01:~$ ping -c 3 8.8.8.8 PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data. 64 bytes from 8.8.8.8: icmp_seq=1 ttl=118 time=3.37 ms 64 bytes from 8.8.8.8: icmp_seq=2 ttl=118 time=3.35 ms 64 bytes from 8.8.8.8: icmp_seq=3 ttl=118 time=3.19 ms --- 8.8.8.8 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2003ms rtt min/avg/max/mdev = 3.196/3.309/3.378/0.093 ms
パケットは普通にインターネットに届いているようです。これが今回困った事象です。
原因
WireGuardを起動した状態でルーティングテーブルを確認してみます。一見問題がないように見えますね。
ubuntu@c01:~$ ip route default via 10.59.79.1 dev eth0 10.0.0.0/24 dev wg0 proto kernel scope link src 10.0.0.1 10.59.79.0/24 dev eth0 proto kernel scope link src 10.59.79.145
WireGuardの起動時の出力を見ると気になるログが2箇所あります。
[#] ip -4 route add 0.0.0.0/0 dev wg0 table 51820 [#] ip -4 rule add not fwmark 51820 table 51820
なにやら怪しいですね。
ubuntu@c01:~$ ip rule 0: from all lookup local 32764: from all lookup main suppress_prefixlength 0 32765: not from all fwmark 0xca6c lookup 51820 32766: from all lookup main 32767: from all lookup default ubuntu@c01:~$ ip route show table 51820 default dev wg0 scope link
fwmark 51820
がパケットにセットされていない場合はルーティングテーブル 51820
を参照するルールで、そのルーティングテーブルはデフォルトルートとして wg0
インターフェイスが設定されています。つまり、何もパケットがマークされていない全てのパケットは wg0
からインターネットに出ていこうとします。これは困りますね。逆にどういう時に fwmark 51820
が付いているのでしょうか?会社の同期がそれっぽいものを見つけてきてくれました。
Routing & Network Namespaces - WireGuard
Improved Rule-based Routing
の段落をご覧ください。
we are able to set an fwmark on all packets going out of WireGuard's UDP socket
とあります。WireGuardによって暗号化されたパケットに fwmark
をつけてくれるようです。全てのパケットをVPN経由にしたい場合、0.0.0.0/0の宛先をWireGuard経由にしたくなりますが、そうするとVPN自体の暗号化されたパケットもVPNインターフェイス(wg0)にルーティングされてしまいループしてしまって外に出られなくなりますが、 fwmark
がついているパケットだけホストに元からあるルーティングテーブルを参照させ、それ以外のパケットは全てVPNインターフェイスにルーティングされるようにしてくれるようです。冒頭で例として挙げた、「カフェのFree Wi-Fiが信用できない時にすべてのトラフィックをVPNサーバ経由にしたい時」などにはありがたい挙動ですが、後者の「static routingをしたり、VPNの両端のインターフェイスでルーティングプロトコル(ospf, bgpなど)を喋るデーモンを起動して動的に経路情報を交換する」場合には全てのパケットがVPNインターフェイスに吸い込まれてはこまりますね。
対処方法
原因はわかりました。デフォルトルートを wg0
向ける設定追加がされないようにすればいいだけですね。Table = off
という設定を入れるとルーティングテーブルに設定が追加されないようになりました。
[Interface] Address = 10.0.0.1/24 SaveConfig = true Table = off ListenPort = 51820 PrivateKey = aIG1izlztM2ISSsaLPmQpamqc0FdFnUoYTpeeYNWQm0= [Peer] PublicKey = uKjZpzBmrsp7YGqhpTLajsk2XiKj0HHYoRMkLVOaE2c= AllowedIPs = 0.0.0.0/0 Endpoint = 192.168.100.61:51820
さらに追記
PostUp
で設定を変更せずとも Table = off
でルーティングテーブルの操作をしないようにできました。
追記
指摘を頂いたので記事中の設定を修正しました。
@kuro_m88 WireGuardの環境構築に当たり下記エントリー大変参考になりました。ありがとうございました。一点「PostUp = ip -4 route del 0.0.0.0/0 dev %i table $(wg show %i listen-port)」の部分ですが、listen-port≠table numberなので51820固定とした方が良さそうです!https://t.co/Z4Zb3JpzNl
— Tiger (@Astro_Tiger) 2020年9月26日