IPSec
A real beast.
1. Good reads
(我都没有看过)
"It's just an astonishingly-complex suite of protocols." – An Illustrated Guide to IPsec
Nftables 指南:packet flow ipsec expressions
2. Caveats
2.1. Let's Encrypt
Android 的 strongswan client 似乎需要:
- x509 下面的 cert 是 fullchain.pem
- x509ca 下面的 cert 是 ca cert(chain.pem)
iOS 的原生 IKEv2 Client 要求:
- certbot –key-type 选 rsa。现在默认是 ecdsa 了
- 按 chatgpt 的说法,iOS 支持 ecdsa 但是 必须要使用特定算法
- 但根据 https://github.com/strongswan/strongswan/discussions/1881 和 https://apple.stackexchange.com/questions/412089/ios-native-ikev2-client-and-ecdsa-server-certificates 看,iOS 可能根本不支持用 ecdsa 认证服务器
2.2. Proposals
strongswan 默认的配置会让它和 iOS 设备之间协商出来错误的 DH pubkey 长度(猜测是 两侧认为的长度不一致,对应下面的 "modp*")。加上(推测) iOS 遇到这种情况有半分 钟的重试间隔,导致会花很久时间才能等它换一个 proposal 重新来。
Jan 25 18:32:49 alarm charon-systemd[139173]: selected proposal: IKE:3DES_CBC/HMAC_SHA1_96/PRF_HMAC_SHA1/MODP_3072 Jan 25 18:32:49 alarm charon-systemd[139173]: DH public value is malformed Jan 25 18:32:49 alarm charon-systemd[139173]: applying KE public value failed
在 官方文档 没有找到合适的推荐,问了 DeepSeek,加上看了下自己之前的记录,我试了下
connections {
rw-eap {
children {
net {
esp_proposals = aes256gcm16-ecp384,aes256gcm16,aes256-sha256
}
}
proposals = aes256gcm16-sha384-prfsha384-ecp384,aes256-sha256-ecp256,aes256-sha256-prfsha256-ecp256
}
}
天知道我选对了没有,至少现在没有那个半分钟的等待了。
在改动之前,iOS 和 Android 最终都会选择 IKE:AESCBC256/HMACSHA2256128/PRFHMACSHA2256/ECP256 的 proposal,但是改 了之后, Android 会变成 IKE:AESGCM16256/PRFHMACSHA2384/ECP384。问了下 DeepSeek,说前者更快,后者更安全。
想了下感觉好像兼容性更重要,万一加了这个 GCM 以后又出现奇妙问题,所以最终把它去了。
connections {
rw-eap {
children {
net {
esp_proposals = aes256gcm16,aes256-sha256
}
}
proposals = aes256-sha256-ecp256,aes256-sha256-prfsha256-ecp256
}
}
3. 安装和配置
可以参考 ../bootstrap/ansible/roles/networking/tasks/strongswan.yml。Archlinux 安装 strongswan ,Debian 安装 charon-systemd 。
Strongswan 有旧的 ipsec.conf 的配置形式和新的 charon 配置形式,我只会新的。
参考的 Server 配置:
# /etc/swanctl/conf.d/example.conf pools { v4pool { addrs = 172.31.2.0/24 } v6pool { addrs = fdff:ffff:ffff:fff0::1:2:0/112 } } connections { # '-eap' 的后缀是有含义的!换成别的不行,我不知道为什么,或许是和下面 secrets 里的 'eap-' 对应, # 也或许是必须就要这么写。 # 下面的 local 是本机侧, remote 是远端侧。 rw-eap { local_addrs = example.com pools = v4pool, v6pool local { auth = pubkey certs = example.com.pem id = example.com } remote { # 用户名+密码的登录方式 auth = eap-mschapv2 eap_id = %any } children { net { local_ts = 0.0.0.0/0, ::/0 } } version = 2 send_cert = always send_certreq = no } # https://docs.strongswan.org/docs/latest/config/IPv6Ndp.html ndp { children { ns { local_ts = ::/0[ipv6-icmp/135] remote_ts = ::/0[ipv6-icmp/135] mode = pass start_action = trap } na { local_ts = ::/0[ipv6-icmp/136] remote_ts = ::/0[ipv6-icmp/136] mode = pass start_action = trap } } } } secrets { eap-yourname { id = yourname secret = yourpassword } }
指定 DNS 服务器
# /etc/strongswan.d/charon/attr.conf # Section to specify arbitrary attributes that are assigned to a peer via # configuration payload (CP). attr { load = yes # <attr> is an attribute name or an integer, values can be an IP address, # subnet or arbitrary value. # <attr> = dns = 8.8.8.8 }
证书文件:
# 私钥 /etc/swanctl/private: example.com.pem # 公钥,我选的是 letsecrypt 的 fullchain 证书 /etc/swanctl/x509: exmpale.comf.pem # CA 证书。不知道为什么加了 fullchain 还是不够,我从 client 认证失败的例子看说缺这些 CA 就加上了。 # openssl x509 -in x.pem -text -noout 也能用来看最上层的 CA 是谁。 # 从 https://letsencrypt.org/certificates/ 能下载 pem 格式的证书文件。 /etc/swanctl/x509ca: le-R11.pem le-R12.pem
4. 使用本地 DHCP 为 roadwarrier 提供 LAN 地址
dhcp + farp plugin 能让远端设备获得如同在本地 LAN 一样的二层接入,仅限 IPv4。这样的好处是能用本地 DHCP 服务为远端设备提供固定地址。
连接配置:v4 地址池改为特殊的 "dhcp" 地址池
connections { rw-eap { local_addrs = example.com pools = dhcp, v6pool } }
dhcp 插件配置:dnsmasq 不会响应本地回环设备来的请求,必须要发往它 LAN 网段的广播地址
# /etc/strongswan.d/charon/dhcp.conf dhcp { load = yes # 强行让 strongswan 到广播地址去发请求 force_server_address = yes # DHCP server unicast or broadcast IP address. server = 172.31.0.255 # 虽然可能没有必要,但是让 MAC 地址稳定或许更像一个标准的二层设备 # Derive user-defined MAC address from hash of IKE identity and send client # identity DHCP option. identity_lease = yes }
farp 插件配置:加载就好,我不确定它是否起到关键作用
bypass-lan 插件:先关闭,否则它默认在 route table 220 里增加对每个 netif 的网段的 throw 路由。 这是为了让 roadwarrier 能访问自己本地同网段的设备。我其实不太理解为什么在 router 上加 throw 路由 能影响 roadwarrier 的路由选择。
# /etc/strongswan.d/charon/bypass-lan.conf bypass-lan { load = no }