基于 strongSwan 配置 IPsec IKEv2 VPN

2022-09-28 ⏳3.3分钟(1.3千字)

IPsec 是标准的 VPN 技术,主流系统都支持,不需要单独安装客户端。但是 IPsec 概念非常多,配置起来很繁杂。经过一番折腾,终于摸索出了一套基于 strongSwan 的简便配置方法,支持 iOS 和 macOS 拨入。

服务器是 ubuntu 22.04,首先安装 strongSwan 相关组件:

sudo aptitude install strongswan strongswan-swanctl

启动 strongSwan 服务的命令是:

sudo systemctl start strongswan-starter.service

服务名是 strongswan-starter,跟老版本的系统不一样。

然后需要添加配置。跟网上其他资料不同,这次使用最新的 swanctl 配置 strongSwan。它的配置文件在 /etc/swanctl/

首先创建 /etc/swanctl/conf.d/foo.conf,内容结构如下:

connections {
  foo {
    local_addrs = X.X.X.X
    local {}
    remote {}
    children {}
    version = 2
  }
}
secrets {}
pools {}

connections 下面可以添加多条配置,每个配置指定一个名字。对于每一个 connection,我们需要指定以下配置:

网上的资料多使用证书来认证双端身份,这种方式最安全,但在配置和使用上也最繁琐。我们可以使用 PSK 模式来简化配置。

所以 PSK 就是 Pre-Share Key,双端预先共享一个密钥。跟证书方式相比,PSK 更简单,但安全性要差一些。所以不要使用简单的 PSK 密钥。建议使用 openssl 自动生成随机密钥:

$ openssl rand -hex 32
9817df17e110d120b7470026cfdb700b91b375cc33cbee66260125f23f3cbe8b

除了需要生成共享密钥,PSK 还要为每个密钥指定一个 ID。所以 local 和 remote 的配置为:

local {
  auth = psk
  id = bob
}
remote {
  auth = psk
}

这里服务器的 PSK 标识指定为 bob,大家可以随便选。客户端也指定使用 PSK 认证,但用户信息需要在 secrets 中添加:

secrets {
  ike-tom {
    id = tom
    secret = "..."
  }
}

配置中的 secret 也有多种格式。带双引号的表示不需要任何处理,“foo” 的意思就是 foo。不带双引号的分两种:以 0x 开头的表示后面为十六进制字符串,以 0l 开头的表示后面为 base64 字符串。但我测试下来发现 iOS/macOS 不支持后两种。

以上就完成了认证部分的配置,下面配置网络部分。

首先,我们要为客户端自动分配IP地址。为此要先在 pools 中添加客户端网段:

pools {
  rw_pool {
    addrs = 10.9.8.0/24
  }
}

rw_pool 是配置名,可以随便取。有了网段之后,需要在 connection 配置中指定要使用哪个网段:

connections {
  foo {
    pools = rw_pool
    # ...
  }
}

最后就是允许客户端流量传入,这需要配置 children 参数。

children {
  bar {
    local_ts = 0.0.0.0/0
    remote_ts = 10.9.8.0/24
  }
}

我们可以把 children 简单理解为路由表或者防火墙规则。从客户端角度看,local_ts 表示目标网段,remote_ts 表示来源网段。bar 这段配置的意思是允许 10.9.8.0/24 网段的设备访问任意网络地址。也就是说 VPN 服务器为客户端中转所有网络流量。我们也可以根据实际情况设置 local_ts 来限制客户端访问的资源。

到这里所有的配置就结束了😄是不是很简单。完整配置如下:

connections {
  foo {
    version = 2
    local_addrs = X.X.X.X
    local {
      auth = psk
      id = bob
    }
    remote {
      auth = psk
    }
    children {
      bar {
        local_ts = 0.0.0.0/0
        remote_ts = 10.9.8.0/24
      }
    }
    pools = rw_pool
  }
}
secrets {
  ike-tom {
    id = tom
    secret = "..."
  }
}
pools {
  rw_pool {
    addrs = 10.9.8.0/24
  }
}

我们需要使用 swanctl 加载配置:

swanctl --load-all

在 iOS 上添加 VPN 配置,类型选 IKEv2。服务器填服务端IP地址。远程ID填在 local 中指定的 ID,本地ID填在 secrets 指定的 ID。用户坚定选无,不使用证书,然后填入 secrets 中的 secret。

这样就可以远程接入 VPN 了。拨入成功后会发现无法正常访问网络。经过调试,发现了两个问题。

第一,服务器没有推送 DNS 配置。

iOS 不支持指定 DNS,macOS 虽然可以指定,但不起作用。最简单的办法就是让 strongSwan 自动下发。这需要修改 /etc/strongswan.conf

charon {
  dns1 = 8.8.8.8
  # ...
}

修改配置后需要重启 strongswan-starter 然后调用 swanctl 加载配置。

第二,服务器需要开启 NAT 功能。

iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

这里的 eth0 是服务器能访问外网的网卡名,请按需调整。

解决这两个问题后客户端就可以通过服务端转发访问外网了🎉

为了方便后续维护,我们可以把上面的两条命令写成一个脚本,比如 /etc/ipsec-load.sh

#!/usr/bin/sh

/usr/sbin/iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
/usr/sbin/swanctl --load-all

再给脚本开启可执行权限

chmod a+x /etc/ipsec-load.sh

最后添加 systemd 描述文件(/etc/systemd/system/ipsec-load.service)

[Unit]
Description=load ipsec rules
After=network.target
Requires=strongswan-starter.service

[Service]
Type=oneshot
ExecStart=/etc/ipsec-load.sh
RemainAfterExit=true

[Install]
WantedBy=multi-user.target

关键点之一是 Type 需要设置成 oneshot,表示一次性任务。关键点之二是 RemainAfterExit 需要认为 true,告诉 systemd 不要重复执行 ExecStart 指定的脚本。

另外值得一提的是 Requires=strongswan-starter.service,这个配置是让 systemd 在 strongswan-starter 启动之后再运行 ipsec-load.sh 脚本,不然 swanctl 会报错。

以上就是本文的全部内容。IPsec VPN 的好处是标准化,主流系统都支持。但缺点也很明显,除了配置复杂之外,因为应用太广泛,也很容易被网络审查系统干扰。但在国内使用则完全没有问题。