CUBE SUGAR CONTAINER

技術系のこと書きます。

WireGuard の VPN を Linux の Network Namespace で試す

WireGuard は VPN を構成するための一連の実装と通信プロトコル。 実装のコードベースが小さく、他の VPN ソフトウェアと比べて設定方法がシンプルという特徴がある。 今回は、その WireGuard を Linux の Network Namespace と一緒に試してみる。

使った環境は次のとおり。

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=22.04
DISTRIB_CODENAME=jammy
DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
$ uname -srm
Linux 5.15.0-69-generic x86_64

もくじ

下準備

あらかじめ必要なパッケージをインストールしておく。 WireGuard に直接関係するのは wireguard-tools だけ。

$ sudo apt-get install wireguard-tools iproute2 tcpdump

WireGuard のカーネルモジュールがロードされていることを確認しておく。

$ lsmod | grep -i wireguard
wireguard              94208  0
curve25519_x86_64      36864  1 wireguard
libchacha20poly1305    16384  1 wireguard
ip6_udp_tunnel         16384  1 wireguard
udp_tunnel             20480  1 wireguard
libcurve25519_generic    49152  2 curve25519_x86_64,wireguard

もしロードされていなければ modprobe する。

$ sudo modprobe wireguard

ネットワークを用意する

まずは Network Namespace を使ってネットワークを用意する。 なお、この工程ではまだ VPN とか暗号化は関係がない。 作るネットワークはセグメントが 1 つしかないシンプルなもの。 そこにホストとして Network Namespace が 2 つ繋がる。

はじめに peerapeerb という名前で 2 つの Network Namespace を作る。

$ sudo ip netns add peera
$ sudo ip netns add peerb

2 つの Network Namespace の間を結線する Virtual Ethernet デバイスのインターフェイスを用意する。

$ sudo ip link add peera-veth0 type veth peer name peerb-veth0

インターフェイスの両端を Network Namespace に移動してリンクアップさせる。

$ sudo ip link set peera-veth0 netns peera
$ sudo ip link set peerb-veth0 netns peerb
$ sudo ip netns exec peera ip link set peera-veth0 up
$ sudo ip netns exec peerb ip link set peerb-veth0 up

インターフェイスに IPv4 アドレスを付与しておく。 一応 MAC アドレスも変更しているが必須ではない。

$ sudo ip netns exec peera ip address add 192.0.2.1/24 dev peera-veth0
$ sudo ip netns exec peerb ip address add 192.0.2.2/24 dev peerb-veth0
$ sudo ip netns exec peera ip link set dev peera-veth0 address 00:00:5E:00:53:01
$ sudo ip netns exec peerb ip link set dev peerb-veth0 address 00:00:5E:00:53:02

これで Network Namespace 同士が L3 で疎通した。

$ sudo ip netns exec peera ping -c 3 192.0.2.2 -I 192.0.2.1
PING 192.0.2.2 (192.0.2.2) from 192.0.2.1 : 56(84) bytes of data.
64 bytes from 192.0.2.2: icmp_seq=1 ttl=64 time=0.079 ms
64 bytes from 192.0.2.2: icmp_seq=2 ttl=64 time=0.058 ms
64 bytes from 192.0.2.2: icmp_seq=3 ttl=64 time=0.057 ms

--- 192.0.2.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2041ms
rtt min/avg/max/mdev = 0.057/0.064/0.079/0.010 ms

WireGuard で VPN を構成する

ここからは、作成したネットワーク上に WireGuard で VPN を構成していく。

WireGuard が動作するには、各ノードごとに公開鍵ペアが必要になる。 まずは wg genkey コマンドを使って秘密鍵を生成する。

$ umask 077
$ wg genkey > peer-a-privatekey
$ wg genkey > peer-b-privatekey

次に wg pubkey コマンドを使って秘密鍵から公開鍵を生成する。

$ wg pubkey < peer-a-privatekey > peer-a-publickey
$ wg pubkey < peer-b-privatekey > peer-b-publickey

生成された鍵はいずれもテキストファイルになっている。 たとえば公開鍵ならこんな感じ。

$ cat peer-a-publickey 
FSofL/Slt+l1ga1hLaOq4yd4tJgNDWXX2dF1Q8Z4uRs=
$ cat peer-b-publickey
uhLVCdXY/dpVjYMCLAVHg5ye/afRrN5exDsFjBGO3C8=

公開鍵ペアができたら、次に WireGuard デバイスのインターフェイスを設定していく。 まずは Network Namespace の peerawg0 という名前で WireGuard インターフェイスを追加する。

$ sudo ip netns exec peera ip link add dev wg0 type wireguard

追加した WireGuard インターフェイスに IPv4 アドレスを付与する。 これが VPN トンネルの端点のアドレスになる。

$ sudo ip netns exec peera ip addr add 198.51.100.1/24 dev wg0

次に wg set コマンドを使って WireGuard インターフェイスに VPN 通信の受信用ポートと秘密鍵が格納されたファイルを設定する。 使うポートには特に決まりがないようだ。

$ sudo ip netns exec peera wg set wg0 listen-port 37564 private-key ./peer-a-privatekey

続いて VPN トンネルを張るピアに関する情報を設定する。 ここではピアの公開鍵とエンドポイント、送信元になる端点のアドレスなどを指定する。

$ sudo ip netns exec peera wg set wg0 peer $(cat peer-b-publickey) persistent-keepalive 25 allowed-ips 198.51.100.2/32 endpoint 192.0.2.2:37564

あとは WireGuard インターフェイスをリンクアップさせる。

$ sudo ip netns exec peera ip link set wg0 up

wg コマンドで設定されている内容を確認できる。

$ sudo ip netns exec peera wg
interface: wg0
  public key: FSofL/Slt+l1ga1hLaOq4yd4tJgNDWXX2dF1Q8Z4uRs=
  private key: (hidden)
  listening port: 37564

peer: uhLVCdXY/dpVjYMCLAVHg5ye/afRrN5exDsFjBGO3C8=
  endpoint: 192.0.2.2:37564
  allowed ips: 198.51.100.2/32
  latest handshake: 25 seconds ago
  transfer: 564 B received, 688 B sent
  persistent keepalive: every 25 seconds

peera と対になる設定を peerb にも設定する。

$ sudo ip netns exec peerb ip link add dev wg0 type wireguard
$ sudo ip netns exec peerb ip addr add 198.51.100.2/24 dev wg0
$ sudo ip netns exec peerb wg set wg0 listen-port 37564 private-key ./peer-b-privatekey
$ sudo ip netns exec peerb wg set wg0 peer $(cat peer-a-publickey) persistent-keepalive 25 allowed-ips 198.51.100.1/32 endpoint 192.0.2.1:37564
$ sudo ip netns exec peerb ip link set wg0 up
$ sudo ip netns exec peerb wg
interface: wg0
  public key: uhLVCdXY/dpVjYMCLAVHg5ye/afRrN5exDsFjBGO3C8=
  private key: (hidden)
  listening port: 37564

peer: FSofL/Slt+l1ga1hLaOq4yd4tJgNDWXX2dF1Q8Z4uRs=
  endpoint: 192.0.2.1:37564
  allowed ips: 198.51.100.1/32
  latest handshake: 49 seconds ago
  transfer: 572 B received, 596 B sent
  persistent keepalive: every 25 seconds

これで VPN トンネルが張られた。

動作を確認する

それでは、実際にトンネルの端点間で ping を打ってみよう。

$ sudo ip netns exec peera ping -c 3 198.51.100.2 -I 198.51.100.1
PING 198.51.100.2 (198.51.100.2) from 198.51.100.1 : 56(84) bytes of data.
64 bytes from 198.51.100.2: icmp_seq=1 ttl=64 time=0.371 ms
64 bytes from 198.51.100.2: icmp_seq=2 ttl=64 time=0.740 ms
64 bytes from 198.51.100.2: icmp_seq=3 ttl=64 time=1.09 ms

--- 198.51.100.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2007ms
rtt min/avg/max/mdev = 0.371/0.732/1.086/0.291 ms

ちゃんと疎通があるようだ。

パケットキャプチャもしてみよう。 ping を打ちっぱなしにした状態で peerbwg0 インターフェイスをキャプチャする。 すると、次のように平文の ICMP パケットが流れている。

$ sudo ip netns exec peerb tcpdump -tnl -i wg0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on wg0, link-type RAW (Raw IP), snapshot length 262144 bytes
IP 198.51.100.1 > 198.51.100.2: ICMP echo request, id 39464, seq 1, length 64
IP 198.51.100.2 > 198.51.100.1: ICMP echo reply, id 39464, seq 1, length 64
IP 198.51.100.1 > 198.51.100.2: ICMP echo request, id 39464, seq 2, length 64
IP 198.51.100.2 > 198.51.100.1: ICMP echo reply, id 39464, seq 2, length 64
IP 198.51.100.1 > 198.51.100.2: ICMP echo request, id 39464, seq 3, length 64
IP 198.51.100.2 > 198.51.100.1: ICMP echo reply, id 39464, seq 3, length 64

一方で peerb-veth0 インターフェイスをキャプチャすると UDP:37564 ポートで通信がやり取りされている。 これは、WireGuard がトランスポート層のプロトコルに UDP を使うため。 つまり、これが VPN でやり取りされる暗号化されたパケットということになる。

$ sudo ip netns exec peerb tcpdump -tnl -i peerb-veth0
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on peerb-veth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
IP 192.0.2.1.37564 > 192.0.2.2.37564: UDP, length 128
IP 192.0.2.2.37564 > 192.0.2.1.37564: UDP, length 128
IP 192.0.2.2.37564 > 192.0.2.1.37564: UDP, length 148
IP 192.0.2.1.37564 > 192.0.2.2.37564: UDP, length 92
IP 192.0.2.2.37564 > 192.0.2.1.37564: UDP, length 32
IP 192.0.2.1.37564 > 192.0.2.2.37564: UDP, length 128
IP 192.0.2.2.37564 > 192.0.2.1.37564: UDP, length 128
IP 192.0.2.1.37564 > 192.0.2.2.37564: UDP, length 128
IP 192.0.2.2.37564 > 192.0.2.1.37564: UDP, length 128

いじょう。