背景

先日引っ越しをした。引っ越し先のインターネット環境は一言で言えば残念で、好きな ISP と契約することができず、マンションが包括して契約している ISP 経由でしかインターネットに接続できない。悲しいことにこの ISP は各個にグローバル IP を割り当てないので、外に向けたサーバーの公開が制限されている [1]

この問題を回避するため、外部の VPS を VPN サーバーとし、ルータとの間にVPN を張ることでサーバーを公開できるようにした。外部のユーザーは、VPS に割り当てられたグローバル IP を通して、筆者の自宅内のサーバーに接続することができる。

構成

表題の通り、VPN には WireGuard を使う。高速に動作し、設定もシンプルらしい。

図1. ネットワーク構成図

図 1 のように、ルーターには ISP からプライベート IP アドレス、10.167.X.Y/24 が割り当てられている。グローバル IP 203.0.113.1 が割り当てられた VPS とルーター間で 10.10.0.0/24 のサブネットで VPN を構築し、VPS に 10.10.0.1 、ルーターに 10.10.0.2 を割り当てる。VPS と ルーターのそれぞれで NAT を行い、LAN内のサーバーから公開するポートに外部から接続できるようにする。LAN 内では、ルーターに 192.168.1.1、公開するサーバーには 192.168.1.2 が割り当てられているものとする。

VPS の OS には Debian (bullseye) を使う。ルーターには OpenWRT (18.06.4) を使う。

なお、WireGuard はピアツーピア型の VPN であるため、本来どちらがサーバーであるという概念は存在しないが、便宜上 VPS 側をサーバー側と呼ぶことにする。

VPN サーバー (VPS) 側の設定

WireGuardのインストール

WireGuard をインストールする。

$ sudo apt install wireguard

鍵生成

サーバーの秘密鍵を生成する。

$ sudo wg genkey > server.key

秘密鍵から公開鍵を生成する。

$ cat server.key | sudo wg pubkey > server.pub

便宜上秘密鍵を server.key(.pub) として書き出したが、値をファイルから読むことはないので、値を控えしだいファイルを消してしまっても構わない。

同様にして、ルーター側の鍵ペアも生成する。

$ sudo wg genkey > client.key
$ cat client.key | sudo wg pubkey > client.pub

IP転送の許可

サーバー内でのIP転送を許可する。/etc/sysctl.conf を編集し、

net.ipv4.ip_forward=1

をアンコメントする。

$ sudo sysctl -p

として設定を反映させる。

設定

/etc/wireguard/[インターフェース名].conf として設定ファイルを作成する。今回は wg0 とする。内容は以下の通り。

[Interface]
ListenPort = 51820
Address = 10.10.0.1/32
PrivateKey = [生成したサーバー側の秘密鍵]
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o [WAN向きのインターフェース名] -j MASQUERADE;
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o [WAN向きのインターフェース名] -j MASQUERADE;
SaveConfig = true

[Peer]
PublicKey = [生成したルーター側の公開鍵]
AllowedIPs = 10.10.0.0/24
PersistentKeepalive = 25

各項目の意味は以下の通り。
[Interface]
サーバー側の設定。
ListenPort: WireGuard が待ち受けるポート
Address: 作成するインターフェース (wg0) に割り当てるアドレス。/32 以外にすると通信できないので注意。
PrivateKey: サーバーが利用する秘密鍵
PostUp/PostDown: インターフェースの作成後/破棄後に実行させるコマンド。IPマスカレードを行い、VPN 内から外向きの通信を WAN 側インターフェースを経由して行えるようにしている。
SaveConfig: インターフェースを破棄した際に現在の WireGuard の設定をこのファイルに書き出す。true にしていると、インターフェースを作成してから設定を変更しても、インターフェースを落とした際に設定が上書きされるので注意したい。[2]

[Peer]
各クライアントに対する設定。複数のクライアントを設定するには、Peer セクションを複数書けば良い。
PublicKey: 接続を許可するクライアントの公開鍵
AllowedIPs: クライアントに許可する IP レンジ。0.0.0.0/0 には設定しないことを強く勧める。デフォルトゲートウェイが変更され、(SSH等でも)サーバーに接続できなくなる可能性がある。 → 参照
PersistentKeepalive: 無通信でも Keep-Alive パケットを送る間隔(秒)。NAT 越えの場合、無通信で切断されないように 25 に設定することが推奨されているようだ [3]。今回は NAT 越えではないが、念のために入れておいた。

ルーター側の設定

Web インターフェースにログインし、以下を行う。

必要なプラグインのインストール

上部メニューの System -> Software でソフトウェア管理ページを開き、Available Packages の中から luci-app-wireguard を探しインストールする。同様に luci-proto-wireguard もインストールする。

インターフェースの追加

Network -> Interfaces から、Add new interface... ボタンをクリックしてインターフェースを追加する。以下の通り設定する。

  • Name of the new interface: 追加するインターフェース名(wg0とする)を入力する。
  • Protocol of the new interface: WireGuard VPN

インターフェースの設定画面が開く。以下の通り設定する。

  • Private Key: 前のステップで生成したクライアント秘密鍵
  • Listen Port: 51820
  • IP Address: このルーターに割り当てる VPN 内の IP、10.10.0.2 を入力する。

Peers セクションの Add ボタンをクリックして、ピアを追加する。これはクライアントから見たピアなので、つまりサーバーのこと。

  • Public Key: 前のステップで生成したサーバー公開鍵
  • Allowed IPs: サーバー側に許可される IP レンジを入力する。すなわち 10.10.0.1/24
  • Route Allowed IPs: 有効にする
  • Endpoint Host / Port: サーバーの WAN 側 IP とポート番号を入力する。203.0.113.1 および 51820 とする。
  • Persistent Keep Alive: 有効にする。

こちらから接続に行くのに、接続に行った先が許可される IP レンジを指定するというのも妙な話だが、上でも書いたようにピアツーピア型の VPN であるためだ。

上のタブから Firewall Settings を開く。

  • Create / Assign firewall-zone: wan にする。[4]

Save & Apply ボタンを押して反映する。

接続の確認

ここまでで VPS とルータ間で接続が確立できているはずだ。Network -> Interface で、追加した wg0 がアクティブになったことや(図2)、Status -> Wireguard Status で Peer が表示され、Latest Handshake が直近の日次になっていることを確認する(図3)。また、VPS と ルーター、互いに VPN 内 IP で ping が通ることを確認する。

interface_wg0

図2. インターフェース w0 が設定された画面例

Wireguard Status の画面例

図3. Wireguard Status の画面例

インターフェース一覧で wg0 にエラーが表示されている場合、Restart ボタンでインターフェースを再起動したり、ルーター本体を再起動すると直るかもしれない。

VPS 側でも

sudo wg

とすることで状態を確認できる。

ポートフォワードの設定

本記事の目的は、外部のユーザーが LAN 内のサーバーに接続できるようにすることである。つまり、ポートフォワードを設定しなければならない。今回の構成では、ルーター側で行う通常のポートフォワードに加え、最初に接続を受ける VPS 側でもポートフォワードを設定する必要がある。

VPS 側でのポートフォワードの設定

iptables で淡々とルールを追加する。例えば、TCP 80 に来た通信を LAN 内のサーバーで受け取れるようにするには、

$ sudo iptables -t nat -A PREROUTING -i [WAN向きのインターフェース名] -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.10.0.2;
$ sudo iptables -t nat -A POSTROUTING -m tcp -p tcp --dst 10.10.0.2 --dport 80 -j SNAT --to-source 10.10.0.1;

とする。上記のルールが VPN 生成/破棄時に自動で設定/削除されるように、wg0.confPostUp / PostDown に追加しておくと良い。直接書くと見づらいので、以下のような内容で /etc/wireguard/wg0-up.sh/etc/wireguard/wg0-down.sh というファイルを作り、

wg0-up.sh

#!/bin/bash

iptables -t nat -A PREROUTING -i [WAN向きのインターフェース名] -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.10.0.2;
iptables -t nat -A POSTROUTING -m tcp -p tcp --dst 10.10.0.2 --dport 80 -j SNAT --to-source 10.10.0.1;

wg0-up.sh

#!/bin/bash

iptables -t nat -D PREROUTING -i [WAN向きのインターフェース名] -p tcp -m tcp --dport 80 -j DNAT --to-destination 10.10.0.2;
iptables -t nat -D POSTROUTING -m tcp -p tcp --dst 10.10.0.2 --dport 80 -j SNAT --to-source 10.10.0.1;

PostUp / PostDown で実行されるようにした。

wg0.conf

[Interface]
...
PostUp = iptables -A FORWARD -i %i -j ACCEPT; iptables -t nat -A POSTROUTING -o [WAN向きのインターフェース名] -j MASQUERADE; /etc/wireguard/wg0-up.sh
PostDown = iptables -D FORWARD -i %i -j ACCEPT; iptables -t nat -D POSTROUTING -o [WAN向きのインターフェース名] -j MASQUERADE; /etc/wireguard/wg0-down.sh
...

ルーター側でのポートフォワードの設定

Network -> Firewall 内の Port Forwards タブから設定できる。上記の HTTP を転送する例であれば、

  • Protocol: TCP
  • External Zone: wan
  • External Port: 80
  • Internal Zone: lan
  • Internal IP Address: 192.168.1.2
  • Internal Port: 80

上記のようなフォワードルールを作成する。図4. に作成した後の画面を示す。

ルータ側でのポートフォワードを設定した画面

図4. ルータ側でのポートフォワードを設定した画面

まとめ

本記事では、Wireguard を使って VPS と OpenWRT 間で拠点間 VPN を構築し、グローバル IP が与えられない環境下で外部向けにサーバーを公開する方法を説明した。Wireguard は初めて使ったが、設定ファイルがとても簡素で分かりやすく、またハマりそうな要素も少なく、必要最低限の VPN を構築するには便利だと感じた。


  1. 月 500 円ほどで固定 IP をもらえるオプションがあるので申し込み中。固定されてなくても良いのでとりあえずグローバル IP が欲しい。 ↩︎

  2. 正直 false でもいい気がするが、どの解説サイトでも true になっているので一応 true にしておいた。 ↩︎

  3. https://www.wireguard.com/quickstart/ ↩︎

  4. 好みで新しいゾーンを作るのも良い。 ↩︎