今回は Linux の Network Namespace で作ったネットワーク上で Libreswan を動かして IPsec VPN を試してみる。 なお、Libreswan には、いくつかの動作モードがある。 今回は、その中でも Route-based VPN using VTI と呼ばれる動作モードを利用する。 これは VTI (Virtual Tunnel Interface) というインターフェイスを作成して、そこに明示的な経路を指定することで一致するパケットを暗号化するというもの。
使った環境は次のとおり。
$ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=22.04 DISTRIB_CODENAME=jammy DISTRIB_DESCRIPTION="Ubuntu 22.04.3 LTS" $ uname -srm Linux 5.15.0-87-generic x86_64 $ ipsec --version Linux Libreswan 3.32 (netkey) on 5.15.0-87-generic
もくじ
下準備
まずは必要なパッケージをインストールする。
$ sudo apt-get install libreswan iproute2 tcpdump
デフォルトで有効になる systemd のサービスは利用しないため停止しておく。
$ sudo systemctl stop ipsec
$ sudo systemctl disable ipsec
ネットワークを作成する
次に Network Namespace を使ってネットワークを作成する。 作成するネットワークの論理構成を以下に示す。
上記で router1
の 203.0.113.1
と router2
の 203.0.113.2
の間に IPsec VPN のトンネルを作る。
router1
と router2
の dummy0
インターフェイスは検証を簡単にするためにホストの代わりとして作っている。
なお、後述する VTI デバイスのインターフェイスに直接同じ IP アドレスを振っても構わない。
今回は、一応インターフェイスくらいは分けておくかという気持ちで dummy インターフェイスを作成している。
まずは必要な Network Namespace を作成する。
$ sudo ip netns add router1 $ sudo ip netns add router2
Network Namespace 同士をつなぐ Virtual Ethernet デバイスのインターフェイスを作成する。
$ sudo ip link add rt1-veth0 type veth peer name rt2-veth0
作成したインターフェイスを Network Namespace に所属させる。
$ sudo ip link set rt1-veth0 netns router1 $ sudo ip link set rt2-veth0 netns router2
インターフェイスの状態を UP に設定する。
$ sudo ip netns exec router1 ip link set rt1-veth0 up $ sudo ip netns exec router2 ip link set rt2-veth0 up
それぞれのインターフェイスに IP アドレスを付与する。
$ 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
それぞれの Network Namespace に IPsec トンネルを作成する上で必要なカーネルパラメータを設定する。
$ sudo ip netns exec router1 sysctl net.ipv4.ip_forward=1 $ sudo ip netns exec router1 sysctl net.ipv4.conf.default.send_redirects=0 $ sudo ip netns exec router1 sysctl net.ipv4.conf.default.accept_redirects=0 $ sudo ip netns exec router1 sysctl net.ipv4.conf.default.rp_filter=0 $ sudo ip netns exec router2 sysctl net.ipv4.ip_forward=1 $ sudo ip netns exec router2 sysctl net.ipv4.conf.default.send_redirects=0 $ sudo ip netns exec router2 sysctl net.ipv4.conf.default.accept_redirects=0 $ sudo ip netns exec router2 sysctl net.ipv4.conf.default.rp_filter=0
それぞれの Network Namespace に dummy インターフェイスを追加して IP アドレスを付与する。
$ sudo ip netns exec router1 ip link add dummy0 type dummy $ sudo ip netns exec router1 ip link set dummy0 up $ sudo ip netns exec router1 ip address add 192.0.2.1/24 dev dummy0 $ sudo ip netns exec router2 ip link add dummy0 type dummy $ sudo ip netns exec router2 ip link set dummy0 up $ sudo ip netns exec router2 ip address add 198.51.100.1/24 dev dummy0
以上で、先ほど示したネットワークの論理構成が完成した。
ひとまず router1
の 203.0.113.1
と router2
の 2023.0.113.2
の間で疎通があることを確認する。
$ 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.078 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.075 ms --- 203.0.113.2 ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2116ms rtt min/avg/max/mdev = 0.071/0.074/0.078/0.003 ms
また、現状では 192.0.2.1
と 198.51.100.1
の間に疎通がないことも確認する。
$ sudo ip netns exec router1 ping -c 3 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. --- 198.51.100.1 ping statistics --- 3 packets transmitted, 0 received, 100% packet loss, time 2096ms
これは、経路の設定がされていないため。 もちろん経路さえ追加すれば疎通は取れるが、経路は VPN で暗号化されないため平文でやり取りされる。
$ sudo ip netns exec router1 ip route show 192.0.2.0/24 dev dummy0 proto kernel scope link src 192.0.2.1 203.0.113.0/24 dev rt1-veth0 proto kernel scope link src 203.0.113.1 $ sudo ip netns exec router2 ip route show 198.51.100.0/24 dev dummy0 proto kernel scope link src 198.51.100.1 203.0.113.0/24 dev rt2-veth0 proto kernel scope link src 203.0.113.2
IPsec VPN を構成する
ここからは Libreswan を使って IPsec VPN を構成していく。
はじめに NSS database を初期化する。 初期化しておかないと後述する Pluto デーモンを起動できない。
$ sudo ipsec initnss Initializing NSS database
次に、それぞれの Network Namespace 上で動作する Libreswan の設定ファイルなどを置くためのディレクトリを用意する。
$ mkdir -p /var/tmp/router1 $ mkdir -p /var/tmp/router2
そして、それぞれのディレクトリに IPsec の事前共有鍵 (Pre-Shared Key; PSK) を用意する。
$ cat << 'EOF' > /var/tmp/router1/ipsec.secrets 203.0.113.1 203.0.113.2 : PSK "DeadBeef" EOF $ cat << 'EOF' > /var/tmp/router2/ipsec.secrets 203.0.113.2 203.0.113.1 : PSK "DeadBeef" EOF
同様に Libreswan の設定ファイルも用意する。
ikev2=yes
を設定することで明示的に IKEv2 を有効にしている。
$ cat << 'EOF' > /var/tmp/router1/ipsec.conf conn %default authby=secret auto=add ikev2=yes conn myvpn left=203.0.113.1 leftsubnet=192.0.2.0/24 right=203.0.113.2 rightsubnet=198.51.100.0/24 mark=42/0xffffffff vti-interface=ipsec0 vti-routing=yes vti-shared=no EOF $ cat << 'EOF' > /var/tmp/router2/ipsec.conf conn %default authby=secret auto=add ikev2=yes conn myvpn left=203.0.113.2 leftsubnet=198.51.100.0/24 right=203.0.113.1 rightsubnet=192.0.2.0/24 mark=42/0xffffffff vti-interface=ipsec0 vti-routing=yes vti-shared=no EOF
設定ファイルにフォーマット的な問題がないかは次のようにして確認できる。 何も出力がなければフォーマット的な問題がないと分かる。 ただし、設定内容の妥当性までは検証してくれないので過度な期待はしない方が良い。
$ /usr/libexec/ipsec/addconn --config /var/tmp/router1/ipsec.conf --checkconfig $ /usr/libexec/ipsec/addconn --config /var/tmp/router2/ipsec.conf --checkconfig
次に、それぞれの Network Namespace 上で ipsec verify コマンドを実行して、すべて [OK]
になっていることを確認する。
[OK]
以外の表示があると、何か動作に不都合のある設定がされていることが分かる。
$ sudo ip netns exec router1 ipsec verify Verifying installed system and configuration files Version check and ipsec on-path [OK] Libreswan 3.32 (netkey) on 5.15.0-87-generic Checking for IPsec support in kernel [OK] NETKEY: Testing XFRM related proc values ICMP default/send_redirects [OK] ICMP default/accept_redirects [OK] XFRM larval drop [OK] Pluto ipsec.conf syntax [OK] Checking rp_filter [OK] Checking that pluto is running [OK] Pluto listening for IKE on udp 500 [OK] Pluto listening for IKE/NAT-T on udp 4500 [OK] Pluto ipsec.secret syntax [OK] Checking 'ip' command [OK] Checking 'iptables' command [OK] Checking 'prelink' command does not interfere with FIPS [OK] Checking for obsolete ipsec.conf options [OK] $ sudo ip netns exec router2 ipsec verify Verifying installed system and configuration files Version check and ipsec on-path [OK] Libreswan 3.32 (netkey) on 5.15.0-87-generic Checking for IPsec support in kernel [OK] NETKEY: Testing XFRM related proc values ICMP default/send_redirects [OK] ICMP default/accept_redirects [OK] XFRM larval drop [OK] Pluto ipsec.conf syntax [OK] Checking rp_filter [OK] Checking that pluto is running [OK] Pluto listening for IKE on udp 500 [OK] Pluto listening for IKE/NAT-T on udp 4500 [OK] Pluto ipsec.secret syntax [OK] Checking 'ip' command [OK] Checking 'iptables' command [OK] Checking 'prelink' command does not interfere with FIPS [OK] Checking for obsolete ipsec.conf options [OK]
以上で準備ができたので Libreswan の Pluto デーモンを起動する。
オプションで動作ディレクトリや設定ファイルに先ほど作成したものを指定する。
また、デバッグメッセージをターミナルに出したいので --nofork
と --stderrlog
をつけて実行する。
こうすればデーモンとして動作せず、ログを標準エラー出力に出せる。
まずは router1
の方から。
$ sudo ip netns exec router1 ipsec pluto \ --nofork \ --stderrlog \ --rundir /var/tmp/router1 \ --config /var/tmp/router1/ipsec.conf \ --secretsfile /var/tmp/router1/ipsec.secrets
ログからエラーなどが生じていないことを確認する。
次に、新しくターミナルを別に開いて router2
の Pluto デーモンを起動する。
$ sudo ip netns exec router2 ipsec pluto \ --nofork \ --stderrlog \ --rundir /var/tmp/router2 \ --config /var/tmp/router2/ipsec.conf \ --secretsfile /var/tmp/router2/ipsec.secrets
デーモンを起動できたら ipsec auto コマンドを使って IPsec VPN のコネクションを開始する。
$ sudo ip netns exec router1 ipsec auto \ --config /var/tmp/router1/ipsec.conf \ --ctlsocket /var/tmp/router1/pluto.ctl \ --start myvpn
コマンドを実行すると IPsec VPN のセッションが確立される。 確立されたセッションは ipsec show コマンドで確認できる。
$ sudo ip netns exec router1 ipsec show 192.0.2.0/24 <=> 198.51.100.0/24 using reqid 16393 $ sudo ip netns exec router2 ipsec show 198.51.100.0/24 <=> 192.0.2.0/24 using reqid 16389
セッションが確立されると自動的に router1
と router2
に ipsec0
という名前で VTI が作成される。
$ sudo ip netns exec router1 ip address show ipsec0 5: ipsec0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000 link/ipip 203.0.113.1 peer 203.0.113.2 inet6 fe80::200:5efe:cb00:7101/64 scope link valid_lft forever preferred_lft forever $ sudo ip netns exec router2 ip address show ipsec0 5: ipsec0@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN group default qlen 1000 link/ipip 203.0.113.2 peer 203.0.113.1 inet6 fe80::200:5efe:cb00:7102/64 scope link valid_lft forever preferred_lft forever
そして、宛先が VTI の経路が対向のサブネット (rightsubet) から自動的に設定される。 ようするに経路にマッチして VTI にフォワードされたパケットが自動的に暗号化されるということ。
$ sudo ip netns exec router1 ip route show 192.0.2.0/24 dev dummy0 proto kernel scope link src 192.0.2.1 198.51.100.0/24 dev ipsec0 scope link 203.0.113.0/24 dev rt1-veth0 proto kernel scope link src 203.0.113.1 $ sudo ip netns exec router2 ip route show 192.0.2.0/24 dev ipsec0 scope link 198.51.100.0/24 dev dummy0 proto kernel scope link src 198.51.100.1 203.0.113.0/24 dev rt2-veth0 proto kernel scope link src 203.0.113.2
ちなみに ipsec.conf で VTI 関連の設定をしない場合には作成や経路の設定は自動的にはされない。 その場合は、次のように手動で設定することもできる。
$ sudo ip netns exec router1 ip tunnel add ipsec0 mode vti local 203.0.113.1 remote 203.0.113.2 key 42 $ sudo ip netns exec router1 ip link set ipsec0 up $ sudo ip netns exec router1 ip route add 198.51.100.0/24 dev ipsec0 $ sudo ip netns exec router2 ip tunnel add ipsec0 mode vti local 203.0.113.2 remote 203.0.113.1 key 42 $ sudo ip netns exec router2 ip link set ipsec0 up $ sudo ip netns exec router2 ip route add 192.0.2.0/24 dev ipsec0
動作を確認する
さて、ここまでで正常に IPsec VPN が確立されたようなので動作を確認しよう。
まずは、最初の方で確認した dummy0
インターフェイス同士の IP アドレスで疎通を確認しておく。
$ 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.188 ms 64 bytes from 198.51.100.1: icmp_seq=2 ttl=64 time=0.266 ms 64 bytes from 198.51.100.1: icmp_seq=3 ttl=64 time=0.316 ms ...
今度は、ちゃんと ping に疎通があることが確認できる。
では、次にパケットをキャプチャしてみる。
まずは router1
の ipsec0
インターフェイスから。
$ 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 10575, seq 29, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 10575, seq 29, length 64 IP 192.0.2.1 > 198.51.100.1: ICMP echo request, id 10575, seq 30, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 10575, seq 30, length 64 IP 192.0.2.1 > 198.51.100.1: ICMP echo request, id 10575, seq 31, length 64 IP 198.51.100.1 > 192.0.2.1: ICMP echo reply, id 10575, seq 31, length 64 ...
上記から ipsec0
インターフェイスの時点では平文で内容が見えることが分かる。
これは IPsec VPN のトンネルの出入り口を見ているため。
では、続いて router1
の rt1-veth0
インターフェイスをキャプチャしよう。
$ 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=0x2ef3568a,seq=0x30), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xe12a2591,seq=0x30), length 120 IP 203.0.113.1 > 203.0.113.2: ESP(spi=0x2ef3568a,seq=0x31), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xe12a2591,seq=0x31), length 120 IP 203.0.113.1 > 203.0.113.2: ESP(spi=0x2ef3568a,seq=0x32), length 120 IP 203.0.113.2 > 203.0.113.1: ESP(spi=0xe12a2591,seq=0x32), length 120 ...
すると、今度はパケットが ESP (Encapsulating Security Payload) になっており、中身が見えない。 これは IPsec VPN のトンネルで暗号化された部分を見ているため。
続いて IKE SA と Child SA を確立している部分の通信も確認してみよう。 一旦 ping は止めておく。 そして、ipsec auto コマンドを使って VPN のコネクションを一旦削除する。
$ sudo ip netns exec router1 ipsec auto \ --config /var/tmp/router1/ipsec.conf \ --ctlsocket /var/tmp/router1/pluto.ctl \ --delete myvpn
続いて、次のようにして IPsec と 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
準備ができたら IPsec VPN のコネクションを次のようにして張りなおす。
$ sudo ip netns exec router1 ipsec auto \ --config /var/tmp/router1/ipsec.conf \ --ctlsocket /var/tmp/router1/pluto.ctl \ --add myvpn $ sudo ip netns exec router1 ipsec auto \ --config /var/tmp/router1/ipsec.conf \ --ctlsocket /var/tmp/router1/pluto.ctl \ --start myvpn
すると 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 IP 203.0.113.1.500 > 203.0.113.2.500: isakmp: parent_sa ikev2_init[I] IP 203.0.113.2.500 > 203.0.113.1.500: isakmp: parent_sa ikev2_init[R] IP 203.0.113.1.500 > 203.0.113.2.500: isakmp: child_sa ikev2_auth[I] IP 203.0.113.2.500 > 203.0.113.1.500: isakmp: child_sa ikev2_auth[R]
isakmp: parent_sa
となっているのが IKE SA を確立している部分だろう。
ikev2_init[I]
や ikev2_init[R]
の [I]
と [R]
はイニシエータとレスポンダを表している。
そして isakmp: child_sa
が Child SA を確立している部分のはず。
Child SA が確立されると IPsec VPN が確立されたことになる。
まとめ
今回は次の内容を実施した。
- Network Namespace を使ってネットワークを作成する
- ネットワークに Libreswan を使って IPsec VPN を確立する
- IPsec に関する通信をパケットキャプチャする