フリーWi-Fi下でのトンネル用途と、屋外でも自宅サーバに接続することを目的にIPSecサーバを構築した。
swanctlでの構築文献が少なかったので記録として残す。
この記事について
strongswanはかつてのパッケージだと ipsec
コマンドや ipsec.conf
による構成が主流だった。
本記事ではそれらを用いず、最新の swanctl
や swanctl.conf
による構成を目指す。
当時 ipsec
時代の構築にあたっては下記リンクを参考にしていた。
https://www.digitalocean.com/community/tutorials/how-to-set-up-an-ikev2-vpn-server-with-strongswan-on-ubuntu-20-04
マイグレーションにあたっては下記公式ドキュメントを参考にした。
https://wiki.strongswan.org/projects/strongswan/wiki/Fromipsecconf
本記事の手順はコマンドベースで説明しているが、実環境では最終的にansibleを用いて構築した。
そのため若干の差異や誤りを含む可能性がある。
構成
下図のようにグローバルにあるVPSでVPNの通信を受け付け、自宅のVPNサーバ、屋外の移動端末からそれぞれVPSに向けて通信を確立する。
移動端末からグローバルに出る際にトンネリングしたり(赤矢印)、移動端末から自宅内のサーバにアクセスしたり(青矢印)することを想定した構成である。

VPNはIKEv2のIPSecを使用し、L2TPは用いずL3トンネリングで接続する。
ちなみに拠点間VPNのことをSite-to-Site VPNと呼ぶのは有名だが、移動端末と拠点を結ぶ(図の左側の)VPNは「Roadwarrior」と呼ぶらしい。
本記事のRoadwarrior VPNは、クライアント証明書(TLS)方式と、ID+password(EAP)方式の両方を受けられるように設定する。
IPアドレス
IPアドレスは下記の前提とする。実際の構築の際は適宜差し替える。
- VPSグローバルアドレス:203.0.113.1
- 自宅NWアドレス:192.168.1.0/24
- ゲートウェイ:192.168.1.1
- VPNサーバ:192.168.1.2
- Roadwarriorクライアントアドレス帯:192.168.2.0/24
- EAP接続クライアント:192.168.2.0/25
- TLS接続クライアント:192.168.2.128/25
自宅NWについて
私の自宅のONU/HGWの自由度がかなり低く、
- グローバル向けデフォゲ以外のルーティング設定ができない
- ONUのみの動作モードはできない(ブリッジにしてルーティングを他機器に移譲することができない)
- 当然IPSecもしゃべれない
- MAP-E方式のため、グローバルアドレスが変化する可能性がある
という状態だったので、自宅内NWのleafにあたる機器(192.168.1.2/24)をNAT状態でVPNサーバにした。
この構成による課題は下記になる。
- グローバルサーバから自宅内サーバ方向に接続することはできないため、VPN確立は常に自宅サーバから始動する必要がある(厳密なSite-to-Site VPNではない)
- 本記事ではどちらのサーバを再起動しても自動でVPNが復旧するよう、自律再接続ができることを目標に構築した
- グローバルから自宅に接続する際、1段階余計にNAPTを嚙ませる必要がある
- 自宅NW(192.168.1.0/24)のデフォゲはHGW(192.168.1.1)に向いており、VPNセグメント(192.168.2.0/24)の通信だけVPNサーバ(192.168.1.2)に向けるのは骨が折れる
- そのため、192.168.1.2がVPN通信を終端した後、192.168.1.2から同セグ内通信をするようにふるまうNAPTを実装する必要がある
- (IKEv2はNAT処理を良しなにしてくれるので、パススルーの考慮は不要だった)
機器バージョン
- VPS
- Ubuntu Server 22.04→24.04
- 自宅VPNサーバ
- Ubuntu Server 22.04→24.04
- 移動端末
- Ubuntu Desktop 24.04
- iOS 17→18
CA、サーバ証明書の作成
ipsec
時代と変わらないため、本記事では割愛する。
下記記事のStep2, Step3を参照されたい。
https://www.digitalocean.com/community/tutorials/how-to-set-up-an-ikev2-vpn-server-with-strongswan-on-ubuntu-20-04
本記事では以下がすでに出来上がっているものとする。
- CA証明書
ca-cert.pem
- VPSのサーバ証明書
server-cert.pem
- VPSのサーバ秘密鍵
server-key.pem
- 自宅のサーバ証明書
server-cert.pem
- 自宅のサーバ秘密鍵
server-key.pem
Ubuntuクライアントで使用可能なTLS接続用のクライアント証明書・秘密鍵もサーバ証明書と同じ要領で作成できる。
iOS対応の証明書作成(もしくはバンドル)については実施していないため記載しない。
グローバルサーバ(VPS)の設定
パッケージインストール
$ sudo apt install charon-systemd strongswan-swanctl
証明書等の配置
作成した証明書等を下記パスに配置する。
VPSにはVPSの証明書だけ設置すればよく、自宅サーバの証明書は不要である。
ファイル | 配置パス |
---|---|
CA証明書 | /etc/swanctl/x509ca/ca-cert.pem |
サーバ証明書 | /etc/swanctl/x509/server-cert.pem |
サーバ秘密鍵 | /etc/swanctl/private/server-key.pem |
カーネルパラメータの設定
$ sudo vim /etc/sysctl.conf
# 以下を追記
net.ipv4.ip_forward=1
net.ipv4.conf.all.accept_redirects=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.ip_no_pmtu_disc=0
$ sudo sysctl -p
ufwの許可ルール設定
$ sudo ufw allow 500,4500/udp
XFRMインタフェースの作成
VPNサーバ以外の機器でルーティングをしっかり設定している場合は不要だが、今回の構成のようにVPNサーバ自身がルーティング処理をする場合には専用の論理インタフェースを作成する必要がある。
かつてはVTIというインタフェースが用いられていたようだが、現在のLinuxではXFRMというインタフェースを用いる。
詳細は下記を参照されたい。
https://docs.strongswan.org/docs/latest/features/routeBasedVpn.html
本構成では下記のように設定する。
I/F IDは、後ほどswanctlのコンフィグと紐づけるときに用いる。
(名前とI/F IDの数字ずれは、特に他意があるわけではない)
XFRMインタフェースそのものにIPアドレスのアサインは要らない。
- xfrm0 (I/F ID:1) EAP接続クライアント用プール(192.168.2.0/25)
- xfrm1 (I/F ID:2) TLS接続クライアント用プール(192.168.2.128/25)
- xfrm2 (I/F ID:3) 自宅ネットワーク(192.168.1.0/24)
まずはxfrmインタフェース作成スクリプトを作成する/usr/local/sbin/setup-xfrm.sh
#!/bin/bash
ip link add xfrm0 type xfrm if_id 1
ip link set xfrm0 up
ip route add 192.168.2.0/25 dev xfrm0
ip link add xfrm1 type xfrm if_id 2
ip link set xfrm1 up
ip route add 192.168.2.128/25 dev xfrm1
ip link add xfrm2 type xfrm if_id 3
ip link set xfrm2 up
ip route add 192.168.1.0/24 dev xfrm2
$ chmod +x /usr/local/sbin/setup-xfrm.sh
次に上記スクリプトをOS起動時にキックするよう、systemdに登録する/etc/systemd/system/xfrm-setup.service
[Unit]
Description=Setup xfrm interface
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/setup-xfrm.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
$ sudo systemctl daemon-reload
$ sudo systemctl enable --now xfrm-setup.service
iptablesの編集
Ubuntu Serverでは、ufwの設定ファイルからiptables設定を更新する。
コンフィグ内の eth0
は、マシンによってグローバルアドレスが割り振られているインタフェース名が異なるため、適宜差し替える。/etc/ufw/before.rules
# 初期状態だとnatテーブルの行はないため新設する
*nat
-A POSTROUTING -s 192.168.2.0/24 -o eth0 -m policy --pol ipsec --dir out -j ACCEPT
# VPNクライアントからの通信は、VPNサーバからの通信としてNAPTしてフォワードする
-A POSTROUTING -s 192.168.2.0/24 -o eth0 -j MASQUERADE
COMMIT
# 初期状態だとmangleテーブルの行はないため新設する
*mangle
-A FORWARD --match policy --pol ipsec --dir in -s 192.168.2.0/24 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
COMMIT
# filterテーブルは初期ルールが書いてある。下記のルールに続き最後の2行を追記する
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines
# ここから下を追加
-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 192.168.2.0/24 -j ACCEPT
-A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 192.168.2.0/24 -j ACCEPT
上記は ufw
を再起動しないと反映されない。 sudo ufw disable && sudo ufw enable
してもいいが、マシンごと再起動するのが確実。
swanctlコンフィグの作成
一部テンプレート表記 {{ var }}
を用いている。同部分は書き換えが必須となる。/etc/swanctl/conf.d/roadwarrior.conf
connections {
rw-eap {
local {
# サーバ認証は公開鍵認証
auth = pubkey
certs = server-cert.pem
id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
}
remote {
auth = eap-mschapv2
eap_id = %any
}
children {
rw-eap {
dpd_action = restart
if_id_out = 1 # xfrm0のI/F ID
if_id_in = 1 # xfrm0のI/F ID
life_time = 0
local_ts = 0.0.0.0/0
remote_ts = 0.0.0.0/0
}
}
dpd_delay = 60s
encap = yes
pools = pool-rw-eap
rekey_time = 0
send_cert = always
send_certreq = no
version = 2
}
rw-tls {
local {
auth = pubkey
certs = server-cert.pem
id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
}
remote {
auth = pubkey
cacerts = ca-cert.pem # クライアント証明書に署名したCA証明書
}
children {
rw-tls {
dpd_action = restart
if_id_out = 2 # xfrm1のI/F ID
if_id_in = 2 # xfrm1のI/F ID
life_time = 0
local_ts = 0.0.0.0/0
remote_ts = 0.0.0.0/0
}
}
dpd_delay = 60s
encap = yes
pools = pool-rw-tls
rekey_time = 0
send_cert = always
send_certreq = no
version = 2
}
}
pools {
pool-rw-eap {
addrs = 192.168.2.0/25
dns = {{ dns_ip }} # 自宅のDNSでも、8.8.8.8でもいい
}
pool-rw-tls {
addrs = 192.168.2.128/25
dns = {{ dns_ip }} # 自宅のDNSでも、8.8.8.8でもいい
}
}
secrets {
eap-{{ name }} { # eapユーザの分だけ作る
id = {{ name }} # ユーザ名
secret = {{ secret }} # パスワード
}
}
/etc/swanctl/conf.d/home.conf
connections {
home {
local {
auth = pubkey
certs = server-cert.pem
id = {{ vpn_id }} # サーバ証明書のCNに設定したドメイン/IPアドレス
}
remote {
id = {{ vpn_home_id }} # 自宅サーバ証明書のCNに設定したドメイン/IPアドレス
cacerts = ca-cert.pem
}
children {
home {
if_id_out = 3 # xfrm2のI/F ID
if_id_in = 3 # xfrm2のI/F ID
local_ts = 192.168.2.0/24 # 自宅への接続を許可するIP
remote_ts = 192.168.1.0/24 # 自宅のIPセグメント
start_action = trap
}
}
dpd_delay = 60s
encap = yes
rekey_time = 0
send_cert = always
version = 2
}
}
secrets {
rsa {
file = server-key.pem # サーバ秘密鍵。homeの方に書かなければいけないわけではないが、どこかに書いておく必要がある。
}
}
最後にstrongswanを起動する。
swanctlをインストールしていると、 strongswan.service
は strongswan-swanctl.service
のエイリアスになっているため、省略名で起動できる。
$ sudo systemctl enable --now strongswan
# すでに起動している場合はreloadをする
$ sudo systemctl reload strongswan
自宅VPNサーバの設定
パッケージインストール~カーネルパラメータの設定 までは、VPSサーバの設定と同じ。
証明書等の配置に関して、今回配置するのはVPSのサーバ証明書ではなく自宅サーバの証明書・秘密鍵であることに注意。
iptablesの編集
グローバルアドレス(203.0.113.1/32)は適宜グローバルサーバの実IPに書き換える
*nat
-A POSTROUTING -s 203.0.113.1/32 -o eth0 -m policy --pol ipsec --dir out -j ACCEPT
-A POSTROUTING -s 203.0.113.1/32 -o eth0 -j MASQUERADE
# 自宅サーバはVPNルータではないため、VPNサブネットから自宅サブネット内へ接続するときにNAPTをする必要がある(これをしないと戻りパケットが迷子になる)。
-A POSTROUTING -s 192.168.2.0/24 -d 192.168.1.0/24 -j MASQUERADE
COMMIT
*mangle
-A FORWARD --match policy --pol ipsec --dir in -s 203.0.113.1/32 -o eth0 -p tcp -m tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1361:1536 -j TCPMSS --set-mss 1360
COMMIT
*filter
:ufw-before-input - [0:0]
:ufw-before-output - [0:0]
:ufw-before-forward - [0:0]
:ufw-not-local - [0:0]
# End required lines
# ここから下を追加
-A ufw-before-forward --match policy --pol ipsec --dir in --proto esp -s 203.0.113.1/32 -j ACCEPT
-A ufw-before-forward --match policy --pol ipsec --dir out --proto esp -d 203.0.113.1/32 -j ACCEPT
-A FORWARD -s 192.168.2.0/24 -d 192.168.1.0/24 -j ACCEPT
# 下記はVPNセグメントへの戻りパケットのみを許可している。state設定が不要であれば上記行と同様に全ACCEPTをすることも可能(ただしルーティングは別途要検討)。
-A FORWARD -s 192.168.1.0/24 -d 192.168.2.0/24 -m state --state ESTABLISHED,RELATED -j ACCEPT
こちらも同様に ufw
再起動か、マシン再起動をする。
strongswanコンフィグの作成
/etc/swanctl/conf.d/tunnel.conf
connections {
tunnel {
local {
auth = pubkey
certs = server-cert.pem
id = {{ vpn_home_id }} # 自宅サーバ証明書のCNに設定したドメイン/IPアドレス
}
remote {
cacerts = ca-cert.pem
id = {{ vpn_id }} # VPSサーバ証明書のCNに設定したドメイン/IPアドレス
}
children {
tunnel {
# 常に自宅サーバから接続を開始しなければいけないため、接続が切れた際に再接続するよう設定する
dpd_action = start
local_ts = 192.168.1.0/24
remote_ts = 192.168.2.0/24
start_action = start
close_action = restart
}
}
dpd_delay = 60s
dpd_timeout = 120s
encap = yes
keyingtries = 0
mobike = yes
reauth_time = 10800s
rekey_time = 0
remote_addrs = 203.0.113.1 # サーバIP。FQDNでもOK
send_cert = always
version = 2
}
}
secrets {
rsa {
file = server-key.pem # 自宅サーバ秘密鍵
}
}
Systemdのoverrideコンフィグ作成
上記swanctlコンフィグでうまく接続してくれそうだが、マシンのブート時だけうまく接続されない事象を確認している。
これを解消するため、マシンブート時だけは強制的に swanctl
コマンドから接続を確立するよう、systemdのユニットを作成した。
以下は strongswan-swanctl.service
定義のオーバーライドとして機能する。/etc/systemd/system/strongswan-swanctl.service.d/override.conf
[Unit]
After=network-online.target
Wants=network-online.target
[Service]
ExecStartPost=/usr/sbin/swanctl --load-all --noprompt
ExecStartPost=/usr/sbin/swanctl --initiate --child tunnel # connection名の分だけ作成
Restart=on-failure
RestartSec=10
最後にstrongswanを起動する。
$ sudo systemctl enable --now strongswan
# すでに起動している場合はreloadをする
$ sudo systemctl reload strongswan
接続確認
下記のような出力を得られたら無事VPN確立が完了している。
$ sudo swanctl -l
tunnel: #24, ESTABLISHED, IKEv2, 2f99f99e927c7ac4_i* b9e8082623a716bd_r
local '<censor>' @ 192.168.1.2[4500]
remote '<censor>' @ 203.0.113.1[4500]
AES_CBC-128/HMAC_SHA2_256_128/PRF_HMAC_SHA2_256/ECP_256
established 5781s ago, reauth in 4132s
tunnel: #84, reqid 1, INSTALLED, TUNNEL-in-UDP, ESP:AES_GCM_16-128
installed 2509s ago, rekeying in 852s, expires in 1451s
in c5f339f1, 712 bytes, 11 packets, 1900s ago
out c0a7d9c0, 1842 bytes, 11 packets, 1900s ago
local 192.168.1.0/24
remote 192.168.2.0/24
移動端末の設定
iOS
iOSでのTLS接続は試していないため、EAP接続の前提とする。
- CA証明書をインストール
- HTTPサーバを立てたり、メール添付などで転送したものを開く
- iOSにインストールする場合は、証明書拡張子を.crtにするとよい
- 設定 > VPNとデバイス管理 > VPN > VPN構成を追加
- 下記の通り設定する
- タイプ:IKEv2
- 説明:任意
- サーバ:グローバルサーバのFQDNかIPアドレス
- リモートID:VPSサーバ証明書のCNに設定したドメイン/IPアドレス
- ユーザ名・パスワード:
roadwarrior.conf
のsecrets
に記載したもの
Ubuntu
EAP / TLS両方で接続を確認した。
- CA証明書をダウンロード
- ファイルシステムのどこかに置いていればよい
sudo apt install network-manager-strongswan
- Ubuntu DesktopのGUIでIKEv2のVPN設定ができる
- https://docs.strongswan.org/docs/latest/features/networkManager.html
- 設定 > ネットワーク > VPN > 「+」 > IPSec/IKEv2(strongswan)
- 下記の通り設定する
- 名前:任意
- Address:グローバルサーバのFQDNかIPアドレス
- Certrificate:CA証明書
- Identity:サーバ証明書のCNに設定したドメイン/IPアドレス
- Authentication:TLSなら「Certificate」、EAPなら「EAP」
- TLSの場合:
- Certificate:Certificate/private key
- Certificate file:クライアント証明書(同じCAによって署名されたもの)
- Private key:上記に対応するクライアント秘密鍵
- EAPの場合:
- Username・Password:
roadwarrior.conf
のsecrets
に記載したもの
- Username・Password:
- Optionsのうち以下を有効化
- Request an inner IP address
- Enforce UDP encapsulation
はまりポイント
strongswanのPoolは重複したIPを払い出すことがある
今回の設定で構築したVPNでは、同じ認証情報(証明書・EAP ID)を用いて複数機器から接続すると、同じPool IPが払い出されてしまい、通信が重複する。
1機器1IDを徹底するか、同じIDを割り当てた機器が同時に接続しないよう気を付ける必要がある。
また複数のconnectionで同じpoolを割り当ててしまうと、こちらもconnectionをまたいで同じIPアドレスが払い出される(こちらは独立したpoolを割り当てるようにコンフィグ設定した)。
特定条件下でMTU制約に引っ掛かり通信できなくなる
今回確認したのは下記事象である。
- Ubuntu Desktopを使用(インタフェースMTUは1500)
- あるフリーWi-Fi下からVPN接続
- HTTPS、SSHといったパケット長が長いIPパケットだけ通らなくなる
切り分けたところ、当該Wi-FiネットワークのMTUが厳しく設定されており、VPNを使用する場合は端末のインタフェースMTUを下げなければいけないことが分かった。
実際に、下記コマンドを打った場合はpingが通ったが、MTUを1267以上にすると通らなかった。
$ ping -c 1 -M do -s 1266 8.8.8.8
対策として、NetworkManagerの設定をいじり、当該SSID接続時のMTUを調整した。
下記コマンドにより、普段はインタフェース標準のMTU(1500)が用いられるが、特定SSIDに接続した場合に限りMTUが上書きされる。
# 現在の接続プロファイルを確認
$ nmcli connection show
# 該当するWi-Fiプロファイルに対してMTUを設定
$ sudo nmcli connection modify "Wi-FiのSSID" 802-11-wireless.mtu 1266
# 設定を反映するために接続を再接続
$ sudo nmcli connection down "Wi-FiのSSID"
$ sudo nmcli connection up "Wi-FiのSSID"
コメント