CUBE SUGAR CONTAINER

技術系のこと書きます。

Network Namespace と Libreswan で IPsec VPN を試す (Route-based / VTI デバイス)

今回は 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 を使ってネットワークを作成する。 作成するネットワークの論理構成を以下に示す。

ネットワーク構成 (論理)

上記で router1203.0.113.1router2203.0.113.2 の間に IPsec VPN のトンネルを作る。 router1router2dummy0 インターフェイスは検証を簡単にするためにホストの代わりとして作っている。 なお、後述する 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

以上で、先ほど示したネットワークの論理構成が完成した。

ひとまず router1203.0.113.1router22023.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.1198.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

セッションが確立されると自動的に router1router2ipsec0 という名前で 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 に疎通があることが確認できる。

では、次にパケットをキャプチャしてみる。 まずは router1ipsec0 インターフェイスから。

$ 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 のトンネルの出入り口を見ているため。

では、続いて router1rt1-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 に関する通信をパケットキャプチャする

参考

libreswan.org