L2TP/IPsec について
L2TP/IPSec と一言でいっても、Linux上では、次の3つの機能によって成り立ってる。
- ipsec ( ikev1 )
- xl2tpd
- pppd
このうち、xl2tpd + pppd はペアで1つのPPP接続(≒vpn)
を作る、ipsecは暗号化である。ipsecで指定したIP/ポートを暗号化するトンネル(xfrm)を作る。ipsecのESP(カプセル化)の中身にL2TPを通してパケットを安全に転送する機能である。xfrm のfrm はtransform のfrm である
なので、次の2つが独立して動作している。
- ipsec
- xl2tpd+pppd
ipsec とペアに使うが、独立して動作するxlt2pdとpppd である。ipsecの設定を抜いても動くはずである。
xl2tpd のみでppp通信を行う。
最初には、xl2tpd(+pppd)を使ってPPPデバイスを作り通信をすることをやってみる。
つぎに、ipsec でパケットを暗号化するトンネルを作り、ESPを見てみる。
最後に、L2TP/IPsec ( エルツーティピー オーバ アイピーセック)を試すことにする。
xl2tpd には、LAC/LNS双方で認証する仕組みがあり、Google検索で見つかった記事では双方を同名にして誤魔化しがあったので、その部分を詳しく見直してみた。また、LAC/LNSでLAC(クライアント)がLNS(サーバ)を認証する機構をスキップすることもできるので、併せて検証した。
準備1:仮想マシン・コンテナでマシンを作る
コンテナだと、PPPデバイスがうまく作れない(特権を入れてもうまく行かないかもしれません)
lxc の場合、明示的にPPPデバイスを作ってあげると動く。Dockerの場合は知らん。
lxc config device add vpn-c dev_ppp unix-char path=/dev/ppp
NAME=u2204-02 UEFI=/usr/share/AAVMF/AAVMF_CODE.fd NVRAM=/var/lib/libvirt/qemu/nvram/$NAME_VARS.fd DISK=/var/lib/libvirt/images/$NAME.qcow2 virt-install \ --name=$NAME \ --machine=virt\ --arch=aarch64 \ --virt-type=kvm \ --cdrom=$ISO_IMG \ --serial pty \ --disk path=$DISK,format=qcow2,device=disk,bus=virtio,cache=none \ --boot loader=$UEFI,loader_ro=yes,nvram=,loader_type=pflash \ --boot cdrom,hd \ --network type=direct,source=eth0,source_mode=bridge,model=virtio \ --memory=2048 \ --vcpu=2 \ --os-variant ubuntu22.04
クライアント側とサーバー側で2台用意しました。
使った環境
$ uname -a Linux vpn-server 5.15.0-78-generic #85-Ubuntu SMP Fri Jul 7 15:29:30 UTC 2023 aarch64 aarch64 aarch64 GNU/Linux $ cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=22.04 DISTRIB_CODENAME=jammy DISTRIB_DESCRIPTION="Ubuntu 22.04.2 LTS"
ソフトウェア
ネットワーク構成を確認するためのツール群
sudo apt install tcpdump iputils-ping iperf3 bind9-dnsutils
サーバー側の構成
サーバ側では、次の設定を行う
- インストール
- xl2tpd設定
- /etc/xl2tpd/xl2tpd.conf
- ppptd設定
- /etc/ppp/options.l2tpd
- /etc/ppp/chap-secrets
- ip route 設定
インストール
sudo apt install xl2tpd
デバッグのためにxl2tpd 自動起動を停止
xl2tpd の設定周りは、設定ミスがあるときに、systemd / journalctl で状況を見づらいために、停止してしまう。
systemctl stop xl2tpd systemctl disable xl2tpd
停止してから、手動で起動する。
/usr/sbin/xl2tpd -D \ -c /etc/xl2tpd/xl2tpd.conf\ -C /var/run/xl2tpd/xl2tpd-control
コマンドのオプションについては次の通り。
/usr/sbin/xl2tpd -h xl2tpd version: xl2tpd-1.3.16 Usage: xl2tpd [-c <config file>] [-s <secret file>] [-p <pid file>] [-C <control file>] [-D] [-l] [-q <tos decimal value for control>] [-v, --version]
/etc/xl2tpd/xl2tpd.conf
xl2tpd の設定 /etc/xl2tpd/xl2tpd.conf
サーバー構成なので[lns]
cat << EOF > /etc/xl2tpd/xl2tpd.conf [global] port = 1701 [lns default] ip range = 10.0.3.100-10.0.3.200 local ip = 10.0.3.1 refuse chap = yes refuse pap = yes require authentication = yes ppp debug = yes pppoptfile=/etc/ppp/options.l2tpd EOF
/etc/ppp/options.l2tpd
上記で指定した/etc/ppp/options.l2tpd
cat <<EOF > /etc/ppp/options.l2tpd ms-dns 192.168.1.1 mtu 1358 mru 1358 refuse-pap refuse-chap refuse-mschap require-mschap-v2 asyncmap 0 name user EOF
/etc/ppp/chap-secrets
PPPパスワードを/etc/ppp/chap-secrets
にかく
cat <<EOF > /etc/ppp/chap-secrets user * shoi7Za6Po * EOF
デバッグ有効でフロント起動
/usr/sbin/xl2tpd -D \ -c /etc/xl2tpd/xl2tpd.conf\ -C /var/run/xl2tpd/xl2tpd-control
起動したら、その端末画面を開いたまま、クライアント設定を行う。
クライアント側の構成
クライアント側の設定も、サーバー側とだいたい同じである。
ただ、ファイル名は意図的に違うものにしている。
- インストール
- xl2tpd設定
- /etc/xl2tpd/xl2tpd.conf
- ppptd設定
- /etc/ppp/options.l2tpd.lac-vpn.conf
- /etc/ppp/chap-secrets
- ip route 設定
インストール
sudo apt install xl2tpd
自動起動を解除する
sudo systemctl stop xl2tpd sudo systemctl disable xl2tpd
/etc/xl2tpd/xl2tpd.conf
cat << EOF > /etc/xl2tpd/xl2tpd.conf [global] port = 1701 [lac vpn] lns = 10.17.238.246 refuse chap = yes refuse pap = yes require authentication = yes ppp debug = yes length bit = yes pppoptfile = /etc/ppp/options.l2tpd.lac-vpn.conf EOF
/etc/ppp/options.l2tpd.lac-vpn.conf
上記で指定した、ファイル/etc/ppp/options.l2tpd.lac-vpn.conf
cat <<EOF >/etc/ppp/options.l2tpd.lac-vpn.conf name user mtu 1380 mru 1380 nodefaultroute EOF
/etc/ppp/chap-secrets
サーバー側と共通のパスワードにする。
PPPパスワードを/etc/ppp/chap-secrets
にかく
cat <<EOF > /etc/ppp/chap-secrets user * shoi7Za6Po * EOF
クライアント→サーバーへ接続
起動する。(サーバー側と同じコマンドになる。)
/usr/sbin/xl2tpd -D \ -c /etc/xl2tpd/xl2tpd.conf\ -C /var/run/xl2tpd/xl2tpd-control
専用コマンドから接続
起動したら、xlt2pd を操作するために、専用のコマンドを使う。
別の端末でログインしてxl2tpdコントローラーを使う、
xl2tpd-control -d -c /var/run/xl2tpd/xl2tpd-control available xl2tpd-control -d -c /var/run/xl2tpd/xl2tpd-control connect-lac $LAC xl2tpd-control -d -c /var/run/xl2tpd/xl2tpd-control status-lac $LAC
xl2tpd-controlで xl2tpd に命令を与えて、接続切断を行わせる。
設定ミスが有る場合
pppデバイスが起動しなくて落ちたり、接続エラーが画面に出てくる
Terminating pppd: sending TERM signal
xl2tpd[2067]: Call established with 192.168.2.190, PID: 2074, Local: 61079, Remote: 17360, Serial: 4 xl2tpd[2067]: control_finish: Connection closed to 192.168.2.190, port 1701 (Server closing), Local: 58560, Remote: 6568 xl2tpd[2067]: Terminating pppd: sending TERM signal to pid 2074
接続したことを確認
ping
を使って疎通確認する。
サーバー側から
ping 10.0.3.100
クライアント側から
ping 10.0.3.1
デバッグ用の設定
/etc/xl2tpd/xl2tpd.conf
[lns default] ppp debug = yes
割当アドレスを固定する
サーバー側のchap-secret を使って、クライアント割当アドレスを固定できる。
/etc/ppp/chap-secrets
user * shoi7Za6Po 10.0.3.102
LNS-LAC間の相互認証 ( name 共通にしたとき )
LNS-LAC間は、お互いに相手を認証するようになっているらしい。
「SSHにおけるサーバー公開鍵のチェック」をイメージするとわかりやすいかもしれない。
サーバー側の設定は次のようになっている。
vpn-server:/etc/xl2tpd/xl2tpd.conf
[lns default] require authentication = yes pppoptfile=/etc/ppp/options.l2tpd
ファイル vpn-server: /etc/ppp/options.l2tpd
require-mschap-v2 name user
ファイル vpn-server:/etc/ppp/chap-secrets
user * shoi7Za6Po * EOF
クライアント側
ファイル client:/etc/xl2tpd/xl2tpd.conf
[lac vpn] lns = 10.17.238.246 require authentication = yes pppoptfile = /etc/ppp/options.l2tpd.lac-vpn.conf
ファイルclient:/etc/ppp/options.l2tpd.lac-vpn.conf
name user EOF
ファイル client:/etc/ppp/chap-secrets
user * shoi7Za6Po *
双方で name XXX
を共通にして、お互いに同じ名前で認証を通している。
- LAC は LNS が
name user
であると認識し chap-secret を見る - LNS は LAC が
name user
であると認識し chap-secret を見る
chap-secrets は次のようになっている。
# Secrets for authentication using CHAP # client server secret IP addresses
このときの「クライアント側のログ」
using channel 8 Using interface ppp0 Connect: ppp0 <--> Overriding mtu 1500 to 1380 PPPoL2TP options: debugmask 0 Overriding mru 1500 to mtu value 1380 sent [LCP ConfReq id=0x1 <mru 1380> <asyncmap 0x0> <auth eap> <magic 0xfb873186>] rcvd [LCP ConfReq id=0x1 <mru 1358> <asyncmap 0x0> <auth chap MS-v2> <magic 0x8b662d0a>] sent [LCP ConfAck id=0x1 <mru 1358> <asyncmap 0x0> <auth chap MS-v2> <magic 0x8b662d0a>] rcvd [LCP ConfAck id=0x1 <mru 1380> <asyncmap 0x0> <auth eap> <magic 0xfb873186>] PPPoL2TP options: debugmask 0 sent [LCP EchoReq id=0x0 magic=0xfb873186] sent [EAP Request id=0xc4 Identity <Message "Name">] rcvd [LCP EchoReq id=0x0 magic=0x8b662d0a] sent [LCP EchoRep id=0x0 magic=0xfb873186] rcvd [CHAP Challenge id=0x9e <8069033abb134e530e69959ad24c9139>, name = "user"] added response cache entry 0 sent [CHAP Response id=0x9e <aea6b01f224095eda07b4bf98ec0f5d30000000000000000436400e2b7ec862d4c45fea947a5937b2bc471484018d6e000>, name = "user"] rcvd [LCP EchoRep id=0x0 magic=0x8b662d0a] rcvd [EAP Response id=0xc4 Identity <Name "user">] EAP: unauthenticated peer name "user" EAP id=0xc4 'Identify' -> 'MD5Chall' sent [EAP Request id=0xc5 MD5-Challenge <Value a4 bd 6d db 79 34 da 12 56 7d c4 0b 67 58 2b 11 d5 94 b2 bf e8 c6> <Name "user">] rcvd [CHAP Success id=0x9e "S=D6F9FEC17FB41730A770D03AAD76928EFD2087D0 M=Access granted"] response found in cache (entry 0) CHAP authentication succeeded
このときの「サーバー側のログ」
Connect: ppp0 <--> Overriding mtu 1500 to 1358 PPPoL2TP options: lnsmode tid 58986 sid 32582 debugmask 0 Overriding mru 1500 to mtu value 1358 sent [LCP ConfReq id=0x1 <mru 1358> <asyncmap 0x0> <auth chap MS-v2> <magic 0x8b662d0a>] rcvd [LCP ConfReq id=0x1 <mru 1380> <asyncmap 0x0> <auth eap> <magic 0xfb873186>] sent [LCP ConfAck id=0x1 <mru 1380> <asyncmap 0x0> <auth eap> <magic 0xfb873186>] rcvd [LCP ConfAck id=0x1 <mru 1358> <asyncmap 0x0> <auth chap MS-v2> <magic 0x8b662d0a>] Overriding mtu 1380 to 1358 PPPoL2TP options: lnsmode tid 58986 sid 32582 debugmask 0 sent [LCP EchoReq id=0x0 magic=0x8b662d0a] sent [CHAP Challenge id=0x9e <8069033abb134e530e69959ad24c9139>, name = "user"] rcvd [LCP EchoReq id=0x0 magic=0xfb873186] sent [LCP EchoRep id=0x0 magic=0x8b662d0a] rcvd [EAP Request id=0xc4 Identity <Message "Name">] EAP: Identity prompt "Name" sent [EAP Response id=0xc4 Identity <Name "user">] rcvd [LCP EchoRep id=0x0 magic=0xfb873186] rcvd [CHAP Response id=0x9e <aea6b01f224095eda07b4bf98ec0f5d30000000000000000436400e2b7ec862d4c45fea947a5937b2bc471484018d6e000>, name = "user"] sent [CHAP Success id=0x9e "S=D6F9FEC17FB41730A770D03AAD76928EFD2087D0 M=Access granted"] rcvd [EAP Request id=0xc5 MD5-Challenge <Value a4 bd 6d db 79 34 da 12 56 7d c4 0b 67 58 2b 11 d5 94 b2 bf e8 c6> <Name "user">] sent [EAP Response id=0xc5 MD5-Challenge <Value 2c 78 e3 33 90 c7 e0 06 1e 56 01 83 41 70 21 d9> <Name "user">] rcvd [EAP Success id=0xc6] EAP authentication succeeded
この設定では、相互ともに name=user を送り合うので認証が通る。
「相互」にPeerを認証している(auth)
- phase 1 と書いた部分は、サーバーがクライアントを Authenticate している
- phase 2 と書いた部分は、クライアントがサーバーを Authenticate している。
どうやらこういうことらしい。
LNS-LACでそれぞれname を設定する。
サーバー側の設定は次のようになっている。
vpn-server: /etc/ppp/options.l2tpd
require-mschap-v2 name server
vpn-server:/etc/ppp/chap-secrets
server user secretA user server secretB
クライアント側
client:/etc/ppp/options.l2tpd.lac-vpn.conf
name user
client:/etc/ppp/chap-secrets
server user secretA user server secretB
- phase 1 で、サーバーがクライアントを Authenticate(user server secretB ) で認証
- phase 2 で、クライアントがサーバを Authenticate(server user secretA ) で認証
ということになるのかな。
CHAPのmd5の仕組みの
auth は peer がお互いにお互いを認識しているようです。
Challenge-Handshake Authentication Protocol (CHAP)をみると CHAPではmd5 と name を使って相手を認証する仕組みであるとわかる。
noauth にする。片側認証にする。
xlt2pd はLAC-LNSが相互に相手を認証する仕組みであったが、相互だと煩雑である。
PPPクライアントからみてサーバーは認証を通してくれて、接続さえできてしまえば良い。
PPPサーバーは固定ですし、L2tp/ipsecなら、なりすましや改竄はipsecで防げるはずなので、片側だけでも十分であるはずである。
クライアントはサーバをわざわざ区別しない。
この設定が noauth
であるらしい。
クライアント側(lac) /etc/ppp/options.l2tpd.lac-vpn.conf
refuse-pap refuse-chap refuse-mschap require-mschap-v2 noauth #name server # サーバを区別しない name user
/etc/ppp/chap-secrets
#server user secretA * user server secretB *
サーバー側(lns) /etc/ppp/options.l2tpd
refuse-pap refuse-chap refuse-mschap require-mschap-v2 name server
/etc/ppp/chap-secrets
#server user secretA * # サーバー認証に行かない。 user server secretB *
単純にパスワードを書くだけで通るはずである。
「SSHにおけるサーバー公開鍵のチェックを無効化」をイメージするとわかりやすいかもしれない。
複数ユーザーに対応する
ここまでわかった通り、クライアント側がnoatuh を入れるのであれば、サーバーがユーザのパスワード(シークレット)を複数持っていればOKである。
サーバー側(lns) /etc/ppp/options.l2tpd
require-mschap-v2 name server
/etc/ppp/chap-secrets
#server user secretA * # サーバー認証に行かない。 user1 server secretB * user2 server secretC *
UNIXパスワードを使う。
PAMに認証を丸投げしてしまえば、PPPのCHAPはLinuxユーザのパスワードを使えるはずである。
ただ、PAMを通さなくても login だけで使えてしまう。
サーバーの設定は次のように。(参考資料
/etc/xl2tpd/xl2tpd.conf
unix authentication = yes
/etc/ppp/options.l2tpd
login name myserver
/etc/ppp/pap-secrets
* myserver ""
ppp は古くからあるので、tty 接続と同じように扱うことができるからのようです。
ユーザーの管理が、/etc/passwd
(getent / 名前サービス)に移行できるのは管理が便利ですよね。ldapとかnsswtichとかPAMとか
Openvpnやwireguardやipsec ikev2 でVPNを作ると、PAMが面倒でユーザー管理に難があるので、xlt2pのユーザ管理は一日の長があるかもしれない。
xlt2pd の設定
細かい設定はマニュアルを参考にする。
# デフォルト値 # listen-addr = 0.0.0.0 # port = 1701 # debug network # auth file = /etc/l2tpd/l2tp-secrets
ppp の設定
参考サイト - https://wiki.archlinux.org/title/PPTP_Client - https://man.openbsd.org/pppd.8
# ポートをロックする lock # bsd compression を使わない? nobsdcomp # パケット圧縮を使用しない nodeflate