strongSwan は IPsec VPN を構成するのに用いられるソフトウェア。 今回は、その strongSwan を Network Namespace で作ったネットワーク上で動かしてみる。 動作モードとしては VTI (Virtual Tunnel Interface) デバイスを使った Route-based を利用する。
なお、現行バージョンの strongSwan は設定ファイルや PID ファイルの置き場所がビルド時にバイナリへハードコードされる。 そのため、そのままでは単一のホスト上で複数のインスタンスを動作させることができない。 この課題については Mount 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 $ ipsec version Linux strongSwan U5.9.5/K5.15.0-69-generic University of Applied Sciences Rapperswil, Switzerland See 'ipsec --copyright' for copyright information.
もくじ
下準備
まずは strongSwan をインストールする。
$ sudo apt-get install strongswan
インストールしたタイミングで systemd の ipsec サービスが動作し始める。 今回は systemd のサービスは不要なので止めておく。
$ sudo systemctl stop ipsec
$ sudo systemctl disable ipsec
ネットワークを作る
まずは Network Namespace を使ってネットワークを作っていく。 この工程では IPsec VPN とかは関係ない。 ただ、単一のセグメント (203.0.113.0/24) のネットワークを構成するだけ。
はじめに、IPsec VPN のトンネルを張るルータに相当するネームスペースを用意する。
$ sudo ip netns add router1 $ sudo ip netns add router2
両者を接続するための Virtual Ethernet デバイスのインターフェイスを作成する。
$ sudo ip link add rt1-veth0 type veth peer name rt2-veth0
両端のインターフェイスをネームスペースに所属させる。
$ sudo ip link set rt1-veth0 netns router1 $ sudo ip link set rt2-veth0 netns router2
インターフェイスをリンクアップさせる。
$ sudo ip netns exec router1 ip link set rt1-veth0 up $ sudo ip netns exec router2 ip link set rt2-veth0 up
インターフェイスに IPv4 アドレスを付与する。
$ sudo ip netns exec router1 ip address add 203.0.113.1/24 dev rt1-veth0 $ sudo ip netns exec router2 ip address add 203.0.113.2/24 dev rt2-veth0
ルータとして動作するように、それぞれのネームスペースについてカーネルのパラメータを変更する。 ただ、今回の構成であればおそらく設定しなくても問題ない。
$ sudo ip netns exec router1 sysctl net.ipv4.ip_forward=1 $ sudo ip netns exec router2 sysctl net.ipv4.ip_forward=1
これでネットワークはできた。 一旦 L3 で疎通があることを ping で確認しておく。
$ sudo ip netns exec router1 ping -c 3 203.0.113.2 -I 203.0.113.1 PING 203.0.113.2 (203.0.113.2) from 203.0.113.1 : 56(84) bytes of data. 64 bytes from 203.0.113.2: icmp_seq=1 ttl=64 time=0.086 ms 64 bytes from 203.0.113.2: icmp_seq=2 ttl=64 time=0.071 ms 64 bytes from 203.0.113.2: icmp_seq=3 ttl=64 time=0.070 ms --- 203.0.113.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2026ms rtt min/avg/max/mdev = 0.070/0.075/0.086/0.007 ms
IPsec VPN を構成する
続いて IPsec VPN に関する設定をしていく。
まず、デフォルトで /etc
以下に用意されている strongSwan の設定ファイルを一旦 /var/tmp/strongswan-conf
にコピーしておく。
これは、前述したとおり strongSwan のバイナリに設定ファイルの場所がハードコードされているため。
複数のインスタンスを動かすには一旦別の場所に退避させておく必要がある。
$ mkdir -p /var/tmp/strongswan-conf $ cp /etc/strongswan.conf /var/tmp/strongswan-conf/ $ cp -r /etc/strongswan.d/ /var/tmp/strongswan-conf/
コピーした設定ファイルの中で charon.conf
を修正する。
設定ファイルの中の install_routes
というキーを no
に設定する。
この修正は strongSwan を Route-based で動作させる際に必要になる。
$ sed -i.back -e "s/# install_routes = yes/install_routes = no/" /var/tmp/strongswan-conf/strongswan.d/charon.conf
設定項目がちゃんと書き換わっていることを確認する。
$ grep install_routes /var/tmp/strongswan-conf/strongswan.d/charon.conf install_routes = no
router1 を設定する
ここからは Network Namespace で作成した各ルータをシェルで操作していく。 以後は、どのルータを操作しているか分かりやすいように、左端に名前を補足としてつけておく。
まずは router1
から。
新しくシェルを起動したら unshare(1) で Mount Namespace を有効にする。
こうすることで、マウントの操作をしてもシステム側に影響を与えなくする。
(router1) $ sudo unshare --mount bash
さらに nsenter(1) を使って、先ほど作成した Network Namespace を有効にする。
(router1) # nsenter --net=/var/run/netns/router1
次のように rt1-veth0
インターフェイスが見えるようになる。
(router1) # ip address show 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 4: rt1-veth0@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 02:74:2c:c1:b3:26 brd ff:ff:ff:ff:ff:ff link-netns router2 inet 203.0.113.1/24 scope global rt1-veth0 valid_lft forever preferred_lft forever inet6 fe80::74:2cff:fec1:b326/64 scope link valid_lft forever preferred_lft forever
続いて /etc
と /var/run
に tmpfs で作ったファイルシステムをマウントする。
これら 2 つのディレクトリは、前述したとおり Ubuntu の strongSwan バイナリにハードコードされた設定ファイルと PID ファイルの置き場所になっている。
Mount Namespace を分けてあるので、ここでマウントの操作をしてもシステム側に影響が及ぶことはない。
(router1) # mount -t tmpfs tmpfs /etc/ (router1) # mount -t tmpfs tmpfs /var/run
マウントしたディレクトリが空っぽに見えることを確認する。
(router1) # ls /etc (router1) # ls /var/run
空っぽになった /etc
以下に設定ファイルを配置していく。
/etc/ipsec.conf
に IKE や IPsec の動作パラメータを記述していく。
ここでは 203.0.113.1
と 203.0.113.2
の間で IPsec VPN を張る。
トンネル両端のサブネットには 192.0.2.0/24
と 198.51.100.0/24
を使う。
(router1) # cat << 'EOF' > /etc/ipsec.conf config setup charondebug="all" conn %default authby=psk type=tunnel auto=start conn myvpn keyexchange=ikev2 left=203.0.113.1 leftsubnet=192.0.2.0/24 right=203.0.113.2 rightsubnet=198.51.100.0/24 ike=aes256-sha256-modp1024! esp=aes256! mark=42 EOF
事前共有鍵を /etc/ipsec.secrets
に書き込む。
もちろん、本来であればもっとちゃんとした事前共有鍵を使った方が良い。
(router1) # cat << 'EOF' > /etc/ipsec.secrets 203.0.113.1 203.0.113.2 : PSK "DeadBeef" EOF
先ほど /etc
以下から退避させておいたコンフィグ群を書き戻す。
(router1) # cp /var/tmp/strongswan-conf/strongswan.conf /etc/ (router1) # cp -r /var/tmp/strongswan-conf/strongswan.d/ /etc/
この時点で /etc
以下は次のような構成になっている。
(router1) # ls /etc ipsec.conf ipsec.secrets strongswan.conf strongswan.d
あとは ipsec start コマンドを実行すると関連するデーモンが動き始める。
(router1) # ipsec start --debug Starting strongSwan 5.9.5 IPsec [starter]... Loading config setup Loading conn 'myvpn'
このとき PID ファイルが /var/run
以下に作られる。
なお、プロセスをフォアグラウンドで動作させたいときは、次のように --nofork
オプションを付けておくと良い。
(router1) # ipsec start --nofork --debug
router2 を設定する
router2
についても、同様に設定していく。
やることはほとんど変わらない。
Mount Namespace と Network Namespace を有効にしたシェルを新たに用意する。
(router2) $ sudo unshare --mount bash (router2) # nsenter --net=/var/run/netns/router2 (router2) # ip address show 1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN group default qlen 1000 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 3: rt2-veth0@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000 link/ether 62:8a:44:2c:56:0a brd ff:ff:ff:ff:ff:ff link-netns router1 inet 203.0.113.2/24 scope global rt2-veth0 valid_lft forever preferred_lft forever inet6 fe80::608a:44ff:fe2c:560a/64 scope link valid_lft forever preferred_lft forever
設定ファイルを書き込む。
(router2) # mount -t tmpfs tmpfs /etc/ (router2) # mount -t tmpfs tmpfs /var/run (router2) # cat << 'EOF' > /etc/ipsec.conf config setup charondebug="all" conn %default authby=psk type=tunnel auto=start conn myvpn keyexchange=ikev2 ike=aes256-sha256-modp1024! esp=aes256! left=203.0.113.2 leftsubnet=198.51.100.0/24 right=203.0.113.1 rightsubnet=192.0.2.0/24 mark=42 EOF (router2) # cat << 'EOF' > /etc/ipsec.secrets 203.0.113.2 203.0.113.1 : PSK "DeadBeef" EOF (router2) # cp /var/tmp/strongswan-conf/strongswan.conf /etc/ (router2) # cp -r /var/tmp/strongswan-conf/strongswan.d/ /etc/ (router2) # ls /etc ipsec.conf ipsec.secrets strongswan.conf strongswan.d
ipsec start でデーモンを起動する。
(router2) # ipsec start --debug Starting strongSwan 5.9.5 IPsec [starter]... Loading config setup Loading conn 'myvpn'
このとき Mount Namespace が分かれていないと PID ファイルが既にあると言われて起動に失敗する。
SA (Security Association) の状態を確認する
ここまで作業が終わったら IKE / IPsec SA の状態を確認する。 ipsec status コマンドを実行して、次のように SA が確立していれば問題ない。
(router1) # ipsec status Security Associations (1 up, 0 connecting): myvpn[1]: ESTABLISHED 4 seconds ago, 203.0.113.1[203.0.113.1]...203.0.113.2[203.0.113.2] myvpn{1}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: c31aaf62_i c5314c6e_o myvpn{1}: 192.0.2.0/24 === 198.51.100.0/24
router2
についても同様に SA が確立していることを確認しておく。
(router2) # ipsec status Security Associations (1 up, 0 connecting): myvpn[2]: ESTABLISHED 8 seconds ago, 203.0.113.2[203.0.113.2]...203.0.113.1[203.0.113.1] myvpn{2}: INSTALLED, TUNNEL, reqid 1, ESP SPIs: c5314c6e_i c31aaf62_o myvpn{2}: 198.51.100.0/24 === 192.0.2.0/24
これで IPsec VPN のトンネルはできた。
VTI デバイスを追加して経路を設定する
続いては VTI デバイスを追加して経路を設定する。
一般に、ここからの作業はシェルスクリプトにした上でデーモンからキックしてもらうことで自動化することが多い 1。 しかし、今回は分かりやすさのために手作業でこなしていく。
まずは router1
の方に VTI デバイスのインターフェイスを追加する。
ここで local と remote には、先ほど構築した IPsec VPN のトンネルのエンドポイントになっているアドレスを指定する。
そして key には /etc/ipsec.conf
に指定した mark
と同じ値を指定する。
こうすることで処理の対象にする IPsec SA を識別するようだ。
(router1) # ip tunnel add ipsec0 mode vti local 203.0.113.1 remote 203.0.113.2 key 42
インターフェイスをリンクアップさせる。
(router1) # ip link set ipsec0 up
動作確認用としてインターフェイスに IPv4 アドレスを設定する。
このとき、アドレスは /etc/ipsec.conf
の leftsubnet
に指定したサブネットに属するものにする。
(router1) # ip addr add 192.0.2.1/24 dev ipsec0
/etc/ipsec.conf
の rightsubnet
に到達するには、追加したインターフェイスを経由するように経路を追加する。
(router1) # ip route add 198.51.100.0/24 dev ipsec0
同じ内容を router2
側にも設定する。
(router2) # ip tunnel add ipsec0 mode vti local 203.0.113.2 remote 203.0.113.1 key 42 (router2) # ip link set ipsec0 up (router2) # ip addr add 198.51.100.1/24 dev ipsec0 (router2) # ip route add 192.0.2.0/24 dev ipsec0
動作を確認する
これで全ての設定が完了した。 試しに VTI デバイスのインターフェイスの間で ping を打ってみよう。
$ sudo ip netns exec router1 ping 198.51.100.1 -I 192.0.2.1 PING 198.51.100.1 (198.51.100.1) from 192.0.2.1 : 56(84) bytes of data. 64 bytes from 198.51.100.1: icmp_seq=1 ttl=64 time=0.196 ms 64 bytes from 198.51.100.1: icmp_seq=2 ttl=64 time=0.170 ms 64 bytes from 198.51.100.1: icmp_seq=3 ttl=64 time=0.190 ms ...
どうやらちゃんと疎通があるようだ。
インターフェイスをパケットキャプチャすると、ちゃんとパケットがやり取りされている。 当たり前だけど、ここの内容は平文になっている。
$ sudo ip netns exec router1 tcpdump -tnl -i ipsec0 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on ipsec0, link-type RAW (Raw IP), snapshot length 262144 bytes IP 192.0.2.1 > 198.51.100.1: ICMP echo request, id 8611, seq 75, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 8611, seq 75, length 64 IP 192.0.2.1 > 198.51.100.1: ICMP echo request, id 8611, seq 76, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 8611, seq 76, length 64 IP 192.0.2.1 > 198.51.100.1: ICMP echo request, id 8611, seq 77, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 8611, seq 77, length 64 ...
一方で Virtual Ethernet デバイスのインターフェイスの方をキャプチャすると IPsec の ESP パケットの形で流れている。
$ sudo ip netns exec router1 tcpdump -tnl -i rt1-veth0 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on rt1-veth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes IP 203.0.113.1 > 203.0.113.2: ESP(spi=0xc5314c6e,seq=0x18), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xc31aaf62,seq=0x18), length 120 IP 203.0.113.1 > 203.0.113.2: ESP(spi=0xc5314c6e,seq=0x19), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xc31aaf62,seq=0x19), length 120 IP 203.0.113.1 > 203.0.113.2: ESP(spi=0xc5314c6e,seq=0x1a), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xc31aaf62,seq=0x1a), length 120 ...
IKE のネゴシエーションの様子も確認しておこう。 次のように ESP 以外に IKE 関連のパケットもキャプチャできるようにして tcpdump を起動する。
$ sudo ip netns exec router1 tcpdump -tnl -i rt1-veth0 esp or udp port 500 or udp port 4500 or tcp port 4500 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on rt1-veth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
そして、どちらでも良いので router[12] で ipsec stop してから ipsec start する。 ようするに IKE のネゴシエーションからやり直してもらう。
(router2) # ipsec stop (router2) # ipsec start
すると、次のように IKE 関連のパケットがキャプチャできる。
$ sudo ip netns exec router1 tcpdump -tnl -i rt1-veth0 esp or udp port 500 or udp port 4500 or tcp port 4500 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on rt1-veth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes IP 203.0.113.2.500 > 203.0.113.1.500: isakmp: parent_sa ikev2_init[I] IP 203.0.113.1.500 > 203.0.113.2.500: isakmp: parent_sa ikev2_init[R] IP 203.0.113.2.4500 > 203.0.113.1.4500: NONESP-encap: isakmp: child_sa ikev2_auth[I] IP 203.0.113.1.4500 > 203.0.113.2.4500: NONESP-encap: isakmp: child_sa ikev2_auth[R]
パケットについている [I]
や [R]
は Initiator と Responder のどちらなのかを表しているようだ。
まとめ
今回は strongSwan を Network Namespace と Mount Namespace を駆使することで単一ホスト上で動かしてみた。