IPsec 配置备忘 Part6 - Android 客户端

接 Part4,终于要开始准备搭一个能用的 VPN 服务器了。另外,如果你要拿这个配置去翻墙请自便,但是不对实际效果做任何保证,本文只考虑传统的 VPN 使用场景。

基本上,服务端的配置和 Part4 中的一致,这里着重介绍如何配置客户端。配置使用 Play Store 上的 strongSwan客户端,版本为 2.3.1。

服务器配置

首先,这个客户端似乎还不支持 ed25519 证书,因此我们需要改为使用 ecdsa 证书。完整的证书生成操作请看 Part2,在生成私钥时指定--key-type ecdsa即可。现在假设你已经生成好了所有 6 个文件:ca-key.pem,ca-cert.pem,client-key.pem,client-cert.pem,server-key.pem,server-cert.pem

另外,这次我们会在服务器上配置 NAT,所以不再需要手动在服务端配置一个 IP 了。假设服务端eth0接口的公网 IP 是2000::1。首先将ca-cert.pem,server-key.pem,server-cert.pem三个文件移动到服务器上正确的地方。然后编写配置文件,同样的,这里只注释和 Part4 中不同的地方。

server.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
connections {
android-connection {
version = 2
# 服务器的地址。根据官方文档,服务器内核版本需要大于等于 5.8
# 才能支持 Android 客户端使用 IPv6 地址连接
local_addrs = 2000::1
# 接受客户端来自任何地方
remote_addrs = %any
proposals = aes256gcm128-sha512-x25519
# 只分配 IPv4 的虚拟 IP
pools = ip4pool
local {
# 请按照实际情况修改 id
id = CN=server common name
auth = pubkey
}
remote {
id = %any
auth = pubkey
}
children {
child_sa {
# 允许客户端将去往所有目的地的流量发送给服务端
local_ts = 0.0.0.0/0
remote_ts = dynamic
mode = tunnel
esp_proposals = aes256gcm128-sha512-x25519
}
}
}
}

pools {
# 只保留 IPv4 池
ip4pool {
addrs = 10.10.10.100-10.10.10.150
}
}

配置完毕后systemctl restart strongswan

客户端配置

在客户端上,我决定创建一个可以直接导入的 Profile 文件,避免手工输入一大堆地址(要记得我在用 IPv6)。首先需要把 client-key.pem 和 client-cert.pem 合并成一个文件,注意 bash 本身是不支持多行命令和注释穿插写的,在执行的时候需要手动移除注释:

1
2
3
4
5
6
7
8
9
10
11
certtool \
--load-privkey client-key.pem \
--load-certificate client-cert.pem \
# 名称随意
--p12-name "Key Cert Bundle" \
# 空密码
--empty-password \
# 合并成 PKCS12 格式
--to-p12 \
# 需要指定 3des-pkcs12,否则 Android 系统无法正确导入
--pkcs-cipher 3des-pkcs12 > client-p12bundle.pem

新生成的文件是client-p12bundle.pem。然后我们参照 strongSwan 的文档创建可以直接导入应用的 Profile 文件。当然你也可以手动导入证书再手动配置 Profile。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
jq -n \
# 生成一个随机的 UUID
--arg uuid `uuidgen -r` \
# Profile 的名称,可以随意写
--arg name "My server"\
# 认证类型
--arg type "ikev2-cert"\
# 需要手动指定 remote id,否则服务器的地址将被当作 id,导致认证失败
# 当然你也可以在创建证书的时候就把服务器地址写在证书里,避免手工指定 DN
# 注意:id 和服务器地址和证书有复杂的检验关系,填写不当容易造成各种奇怪的
# 身份验证错误。
--arg remoteid "CN=server common name"\
# 服务器的地址,我这里使用 2000::1 代替了实际地址,使用 IPv4 和域名也是可以的。
--arg addr "2000::1"\
# 嵌入服务器证书,注意这里我直接嵌了服务器本身的证书。嵌 CA 证书应该也是
# 可行的,但是我没试。
--arg servercert "`sed '/-----/d' server-cert.pem`"\
# 嵌入客户端私钥和证书
--arg localbundle "`sed '/-----/d' client-p12bundle.pem`"\
'{
uuid:$uuid,
name:$name,
type:$type,
remote:{
id:$remoteid,
addr:$addr,
cert:$servercert
},
local: {
p12:$localbundle
},
"ipv6-transport": true
}' > profile.sswan
# 根据官方文档,服务器内核版本需要大于等于 5.8 才能支持客户端使用 IPv6 地址
# 连接。同时需要设定 ipv6-transport 为 true。如果你使用 IPv4 则不需要此设置。
# 另外这里没有指定 local.id,根据文档,在没有指定 local.id 的情况下,客户端程序
# 会自动使用客户端证书的 DN 作为 id

最后将生成的profile.sswan文件拷贝到手机上并导入即可。然后试一下能否成功建立连接。

NAT 配置

然后这样配置完了,虽然能连上服务器,但是依然不能访问网络,我们还需要在服务器上配置一下 NAT。一般是一条防火墙的masquerade规则。这里只简单把配置放出来给自己做个备忘,不做细讲:

nftables.conf
1
2
3
4
5
6
7
8
9
10
table ip nat {
chain prerouting {
type nat hook prerouting priority 0;
}

chain postrouting {
type nat hook postrouting priority 100;
ip saddr 10.10.10.0/24 meta oifname eth0 masquerade
}
}