Linux ルータと EC2 / Android の間で WireGuard トンネルを張る

目次

はじめに

これまで自宅内で動いている古い Raspberry PI 上で WireGuard のエンドポイントを用意し、Amazon EC2 インスタンスや Android スマートフォンから自宅に VPN 接続し、いろいろやっていました。

先日自宅のルータを Linux マシンにして自由を得られたので、ルータマシンで WireGuard エンドポイントの機能を巻き取ることにしました。

本記事は、「Linux ルータで OCN 光の IPoE + MAP-E 接続を行う」 で記載した構成の Linux ルータを前提としています。


前提知識

WireGuard は大雑把に言うと次のような VPN 用のトンネリングプロトコルです。

  • エンドポイント間で UDP でトンネルを張る (IPv4 と IPv6 のどちらでも良い)
  • 暗号化・カプセル化された IPv4 または IPv6 パケットをトンネルで送受信する
  • 各エンドポイントは事前に鍵ペアを生成し、プロトコルの帯域外で公開鍵を交換しておく必要がある
  • 1往復のハンドシェイクで鍵交換を行い、セッション鍵を導出する
  • 鍵交換や暗号で用いるアルゴリズムはプロトコル仕様で固定されている

正確に知りたい方は、ホワイトペーパー 「WireGuard: Next Generation Kernel Network Tunnel」 を読んでください。

ホワイトペーパーの Section 1 にも書かれていますが、IPsec と異なりシンプルで実用的であり、OpenVPN と異なりカーネル空間で動作する (Linux カーネルに組み込まれている) ので、非常に便利な VPN プロトコルです。


Amazon EC2 との間の WireGuard トンネル

まずは、過去に記載した 「EC2から自宅経由でIPv4インターネット接続 (WireGuardトンネル編)」 をベースに、Amazon EC2 インスタンスとの間で IPv6 で WireGuard トンネルを張り、その上で IPv4 通信を行えるようにします。

目的

今回 Amazon EC2 との間で WireGuard トンネルを張る目的は次の2つです。

  1. EC2 インスタンスから自宅経由で IPv4 インターネットにアクセスできる
    • AWS のパブリック IPv4 アドレス費用を節約するため
  2. 自宅内から Amazon EC2 インスタンスにセキュアにアクセスできる
    • SSH 以外の通信を想定したもの
    • EC2 から自宅内へのアクセスは想定しない

ネットワーク構成

設計

ファイアウォールでの許可を最小限にしたいので、WireGuard 接続のエンドポイントのIPアドレスとポート番号は事前に決めたものを固定で利用するようにします。

例では次のようにしました。

  • 自宅側 Linux ルータ
    • OS : Ubuntu Server 24.04
    • エンドポイントのアドレス : 2400:X:X:X::1 (固定アドレス)
    • エンドポイントのポート番号 : 30000
  • EC2 インスタンス
    • OS : Debian 12
    • エンドポイントのアドレス : 2406:Y:Y:Y::4
    • エンドポイントのポート番号 : 30000

また、EC2 インスタンスのトンネルインターフェースには 192.168.9.1 というアドレスを付けて、そのアドレスで自宅側にやってくるようにします。

事前の鍵生成

自宅側 Linux ルータと EC2 インスタンスのそれぞれで事前の鍵を生成しておきましょう。

自宅側 Linux ルータは systemd-networkd でネットワークを設定するので、鍵を /etc/systemd/network ディレクトリに置いておきます。 systemd-networkd が秘密鍵ファイルを参照するので、パーミッションとグループを適切に変更する必要があります。

1$ sudo apt install wireguard-tools
2
3$ wg genkey | sudo tee /etc/systemd/network/wg-home.key
4$ sudo cat /etc/systemd/network/wg-home.key | wg pubkey | sudo tee /etc/systemd/network/wg-home.pub
5$ sudo chmod 640 /etc/systemd/network/wg-home.key
6$ sudo chown root:systemd-network /etc/systemd/network/wg-home.key

EC2 インスタンスは netplan でネットワークを設定するので、鍵を /etc/netplan ディレクトリに置いておきます。 netplan を利用する場合も systemd-networkd が秘密鍵ファイルを参照するので、パーミッションとグループを適切に変更します。

1$ sudo apt install wireguard-tools
2
3$ wg genkey | sudo tee /etc/netplan/wg-ec2.key
4$ sudo cat /etc/netplan/wg-ec2.key | wg pubkey | sudo tee /etc/netplan/wg-ec2.pub
5$ chmod 640 /etc/netplan/wg-ec2.key
6$ chown root:systemd-network /etc/netplan/wg-ec2.key

自宅側 Linux ルータのネットワーク設定

systemd-networkd 用でネットワークやトンネルを設定します。

 1## 外側のネットワークインターフェース設定を変更
 2$ sudo vim /etc/systemd/network/10-enp2s0.network
 3$ cat /etc/systemd/network/10-enp2s0.network
 4----------
 5[Match]
 6Name=enp2s0
 7
 8[Network]
 9LinkLocalAddressing=ipv6
10IPv6AcceptRA=true
11IPv6PrivacyExtensions=true
12Address=2400:X:X:X:Z:Z:Z:Z/64
13Address=2400:X:X:X::1/64    # 固定アドレスを追加
14Tunnel=tun-mape
15
16[Route]  # この Route セクションを追加
17Destination=2406:Y:Y:Y::4
18PreferredSource=2400:X:X:X::1
19Gateway=_ipv6ra
20----------
21
22## トンネルインターフェースの設定を作成
23$ sudo vim /etc/systemd/network/30-tun-ec2.netdev
24$ cat /etc/systemd/network/30-tun-ec2.netdev
25----------
26[NetDev]
27Name=tun-ec2
28Kind=wireguard
29
30[WireGuard]
31PrivateKeyFile={自宅側 Linux ルータの秘密鍵ファイルのパス}
32ListenPort=30000
33
34[WireGuardPeer]
35PublicKey={EC2インスタンスの公開鍵の文字列}
36AllowedIPs=192.168.9.1/32
37Endpoint=[2406:Y:Y:Y::4]:30000
38----------
39
40## トンネルインターフェースのネットワーク設定を作成
41$ sudo vim /etc/systemd/network/30-tun-ec2.network
42$ cat /etc/systemd/network/30-tun-ec2.network
43----------
44[Match]
45Name=tun-ec2
46
47[Route]
48Destination=192.168.9.1/32
49----------
50
51## ネットワーク設定を反映
52$ sudo systemctl restart systemd-networkd.service

EC2 インスタンスにアクセスする際に Temporary アドレスではなく固定アドレスを送信元にしたいので、固有の経路 (10-enp2s0.network ファイルの [Route] セクション) を追加しています。

反映後の主な設定の状態は次の通りです。

 1## トンネルインターフェースの確認
 2$ ip addr show tun-ec2
 3----------
 45: tun-ec2: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
 5    link/none
 6----------
 7
 8## WireGuard のステータス確認
 9$ sudo wg show tun-ec2
10----------
11interface: tun-ec2
12  public key: {自宅側 Linux ルータの公開鍵}
13  private key: (hidden)
14  listening port: 30000
15
16peer: {EC2 インスタンスの公開鍵}
17  endpoint: [2406:Y:Y:Y::4]:30000
18  allowed ips: 192.168.9.1/32
19----------
20
21## IPv6 ルートテーブルの確認
22$ ip -6 route
23----------
242406:Y:Y:Y::4 via fe80::T:T:T dev enp2s0 proto ra src 2400:X:X:X::1 metric 1024 expires 1797sec hoplimit 64 pref medium
25default via fe80::T:T:T dev enp2s0 proto ra metric 1024 expires 1797sec hoplimit 64 pref medium
26(その他経路は省略)
27----------
28
29## IPv4 ルートテーブルの確認
30$ ip route
31----------
32default dev tun-mape proto static scope link
33192.168.1.0/24 dev enp3s0 proto kernel scope link src 192.168.1.1
34192.168.9.1 dev tun-ec2 proto static scope link
35----------

ip6tablesiptables で着信や転送の許可も行います。 各チェインの DROP ルールよりも前に置く必要があります。

1## WireGuard の通信を許可
2$ sudo ip6tables -A INPUT -s 2406:Y:Y:Y::4/128 -d 2400:X:X:X::1/128 -i enp2s0 -p udp -m udp --sport 30000 --dport 30000 -j ACCEPT
3
4## トンネル内の通信に関する設定
5$ sudo iptables -A INPUT -i tun-ec2 -m state --state RELATED,ESTABLISHED -j ACCEPT
6$ sudo iptables -A FORWARD -i tun-ec2 -o tun-mape -j ACCEPT
7$ sudo iptables -A FORWARD -i tun-ec2 -m state --state RELATED,ESTABLISHED -j ACCEPT
8$ sudo iptables -t mangle -A FORWARD -o tun-ec2 -p tcp -m tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

EC2 インスタンスから IPv4 インターネットに自由に行けるようにしつつ、自宅側に向けた通信は開始できないようにしています。

EC2 インスタンスのネットワーク設定

netplan でトンネルを設定します。

 1## 外側のネットワークインターフェース設定を変更
 2$ sudo vim /etc/netplan/91-tun-home.yaml
 3$ cat /etc/netplan/91-tun-home.yaml
 4----------
 5network:
 6  version: 2
 7  tunnels:
 8    tun-home:
 9      mode: wireguard
10      port: 30000
11      key: {EC2 インスタンスの秘密鍵ファイルのパス}
12      peers:
13      - allowed-ips: [0.0.0.0/0]
14        endpoint: "[2400:X:X:X::1]:30000"
15        keys:
16          public: {自宅側 Linux ルータの公開鍵}
17      addresses:
18      - 192.168.9.1/32
19      routes:
20      - to: 0.0.0.0/0
21        metric: 1
22  ethernets:
23    ens5:
24      routes:
25      - to: 169.254.0.0/16
26        via: 172.31.0.1
27      - to: 172.31.0.0/16
28        via: 172.31.0.1
29----------
30
31## ネットワーク設定を反映
32$ sudo netplan apply

デフォルト経路をトンネルで自宅側に向けるようにしています。 ただし、リンクローカルアドレス "169.254.0.0/16" や VPC アドレス "172.31.0.0/16" は自宅側に転送せずに ens5 から直接送信する必要があるので、固定の経路を設定しています。

反映後の主な設定の状態は次の通りです。

 1## トンネルインターフェースの確認
 2$ ip addr show tun-home
 3----------
 43: tun-home: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1420 qdisc noqueue state UNKNOWN group default qlen 1000
 5    link/none
 6    inet 192.168.9.1/32 scope global tun-home
 7       valid_lft forever preferred_lft forever
 8----------
 9
10## WireGuard のステータス確認
11$ sudo wg show tun-home
12----------
13interface: tun-home
14  public key: {EC2 インスタンスの公開鍵}
15  private key: (hidden)
16  listening port: 30000
17
18peer: {自宅側 Linux ルータの公開鍵}
19  endpoint: [2400:X:X:X::1]:30000
20  allowed ips: 0.0.0.0/0
21----------
22
23## IPv4 ルートテーブルの確認
24$ ip route
25----------
26default dev tun-home proto static scope link metric 1
27default via 172.31.0.1 dev ens5 proto dhcp src 172.31.0.4 metric 100
28169.254.0.0/16 via 172.31.0.1 dev ens5 proto static onlink
29172.31.0.0/20 dev ens5 proto kernel scope link src 172.31.0.4 metric 100
30172.31.0.0/16 via 172.31.0.1 dev ens5 proto static onlink
31172.31.0.1 dev ens5 proto dhcp scope link src 172.31.0.4 metric 100
32172.31.0.2 dev ens5 proto dhcp scope link src 172.31.0.4 metric 100
33----------

EC2 インスタンスのセキュリティグループで次のようなインバウンドを許可しておきます。

  • プロトコル : カスタム UDP
  • ポート番号 : 30000
  • ソース : 2400:X:X:X::1/128

動作確認

tcpdump を見ながら、ping, curl, ssh コマンドなどで次のようなことを確認すればOKでしょう。

  • EC2 インスタンスから IPv4 インターネットヘのアクセス
  • ルータから EC2 インスタンスへの IPv4 でのアクセス
  • ルータ以外の自宅マシンから EC2 インスタンスへの IPv4 でのアクセス
    • 特に TCP SYN パケットに含まれる MSS が調整されて 1380 になっていること

WireGuard のハンドシェイク部分の動作を何度も確認する際は、networkctl delete コマンドでトンネルインターフェースを削除し、systemctl restart systemd-networkdnetplan apply コマンドを実行してトンネルを再作成すれば良いです。


Android スマートフォンとの間の WireGuard トンネル

普段使いの Android スマートフォンと自宅の間も WireGuard トンネルを張っています。

目的

スマートフォンとの間で WireGuard トンネルを張る目的は次の2つです。

  1. スマートフォンから自宅にある特定のマシンにアクセスできる
    • モバイル回線利用時も自宅内にある DNS サーバを利用したい
  2. 他者管理の無線 LAN をできるだけ安全に利用する
    • イベント会場や新幹線などで提供する無線 LAN を使う際に全ての IP パケットが暗号化されるようにしたい

設計

一方のエンドポイントのグローバルアドレスやポート番号を固定できない場合の構成としていきます。 また、私が利用しているモバイル通信キャリアでは IPv6 は利用できず、IPv4 プライベートアドレスだけが払い出されるため、IPv4 で WireGuard トンネルを張ります。

  • 自宅側 Linux ルータ
    • OS : Ubuntu Server 24.04
    • エンドポイントのIPアドレス : 192.168.1.1
    • エンドポイントのポート番号 : 30001
    • 外部公開するグローバルIPアドレス : 153.xx.xx.xx (MAP-E 接続のグローバルアドレス)
    • 外部公開するポート番号 : 1856 (仮) (MAP-E 接続のグローバルアドレスで利用できるポート番号の中の1つ)
  • スマートフォン
    • OS : Android 14
    • エンドポイントのアドレス : 不定
    • エンドポイントのポート番号 : 不定

また、スマートフォンのトンネルインターフェースには 192.168.9.2 というアドレスを付けることにします。

1つの WireGuard エンドポイントで複数のピアとトンネルを張ることもできますが、EC2 との間で張ったトンネルとは目的が異なるので、Linux ルータ側では異なるエンドポイントとしてトンネルを張ることにしました。

Android スマートフォンの設定

Android スマートフォンには WireGuard 公式アプリ をインストールし、次の図のような感じで設定しました。

普段は自宅にある DNS サーバ (192.168.1.10) にだけアクセスできれば良いので、Allowed IPs を限定しています。 全通信を自宅経由にする際は、手動で Allowed IPs を変更する想定です。

自宅側 Linux ルータの鍵は前節で作成した鍵をそのまま使えばいいはずなので、新たに作っていません。

自宅側 Linux ルータのネットワーク設定

systemd-networkd 用でネットワークやトンネルを設定します。

 1## トンネルインターフェースの設定を作成
 2$ sudo vim /etc/systemd/network/31-tun-android.netdev
 3$ cat /etc/systemd/network/31-tun-android.netdev
 4----------
 5[NetDev]
 6Name=tun-android
 7Kind=wireguard
 8
 9[WireGuard]
10PrivateKeyFile={自宅側 Linux ルータの秘密鍵ファイルのパス}
11ListenPort=30001
12
13[WireGuardPeer]
14PublicKey={スマートフォンの公開鍵の文字列}
15AllowedIPs=192.168.9.2/32
16----------
17
18## トンネルインターフェースのネットワーク設定を作成
19$ sudo vim /etc/systemd/network/31-tun-android.network
20$ cat /etc/systemd/network/31-tun-android.network
21----------
22[Match]
23Name=tun-android
24
25[Route]
26Destination=192.168.9.2/32
27----------
28
29## ネットワーク設定を反映
30$ sudo systemctl restart systemd-networkd.service

iptables で着信や転送の許可も行います。 各チェインの DROP ルールよりも前に置く必要があります。

1## WireGuard の通信を許可
2$ sudo iptables -t nat -A PREROUTING -d 153.xx.xx.xx/32 -p udp -m udp --dport {公開ポート番号} -j DNAT --to-destination 192.168.1.1:30001
3$ sudo iptables -A INPUT -i tun-mape -p udp -m udp --dport 30001 -j ACCEPT
4
5## トンネル内の通信に関する設定
6$ sudo iptables -A FORWARD -d 192.168.1.10/32 -i tun-android -j ACCEPT
7$ sudo iptables -A FORWARD -i tun-android -o tun-mape -j ACCEPT

上には記載していませんが、自宅の DNS サーバやインターネットからスマートフォンへの戻りの通信は、別のルールとして既に存在しています。

自宅の無線 LAN に接続している際も WireGuard トンネル経由の通信が動作するような iptables ルールにしていますので、帰宅したときにスマートフォン側でトンネルをオフにしなくても大きな問題は起きません。

動作確認

tcpdump を見ながら、スマートフォンでトンネルをオン・オフしたり、Wi-Fi 接続をオン・オフしたりして、WireGuard や内部のパケットが期待通り流れているか確認すればOKでしょう。

注意点

上記の設定は、自宅外からトンネルを利用する際に、スマートフォンに IPv4 アドレスのみ割り当てられていることを前提としています。 スマートフォンに IPv6 アドレスが割り当てられている場合、自宅の DNS サーバを使うようにするだけなら問題ないですが、全通信を自宅経由にして無線 LAN を安全に利用するという目的は達成できないはずなので、設定を鵜呑みにしないでください。

今は試す環境が無いのですが、試す環境を作ったら設定内容を更新するかもしれません。

※2025-02-16追記 : 上記の問題を解決する記事を書きました。⇒ IPv6 通信も WireGuard で自宅経由の VPN にする