折腾笔记:架设OpenConnect Server给iPhone提供更顺畅的网络生活

something-special

(credit: pixiv)

换而言之,让我们来谈谈有中国特色的互联网生活。

更新2015-05-31:新服务器,正好又跑了一遍设置,修正了一些流程。

更新2015-01-23:新服务器,正好又跑了一遍设置,修正了一些流程。

更新2014-10-22:补充了配置上的具体步骤,修正和最新版不同的设定。

事出有因,最近我的主力手机从自由度较高的Android平台切换为土豪度较高的iOS平台,遇到的第一个问题就是科学上网的难题。和Android平台八仙过海的客户端不同,要在iOS上使用非VPN服务非常困难,更别说没有越狱的设备。

鉴于我个人受够了Android的版本碎片化(硬件原因无法升级OS),让iOS留在旧版显然不合我趣味。找个靠谱的VPN解决方案成为唯一选项。

用什么?OpenVPN作为流行的选项,似乎已被老爷们摸透看清,连流量混淆或Stunnel外套都已经被当成主要攻略对象来推倒(或者说流行教程的配置倍受照顾)。看来是时候选个不怎么流行的解决方案。

OpenConnet Server(ocserv)是朋友给我的建议,它通过实现Cisco的AnyConnect协议,用DTLS作为主要的加密传输协议。我认为它的主要好处在于——

  • AnyConnect的VPN协议默认使用UDP DTLS作为数据传输,但如果有什么网络问题导致UDP传输出现问题,它会利用最初建立的TCP TLS通道作为备份通道,降低VPN断开的概率。
  • AnyConnect作为Cisco新一代的VPN解决方案,被用于许多大型企业,这些企业依赖它提供正常的商业运作,这些正常运作对应的经济效益(读作GDP),是我们最好的伙伴。
  • OpenConnet的架设足够麻烦,我的意思是,如果你不是大型企业,你会用AnyConnect的概率无限趋近于零。再者,如果它足够简单,我就不用写这篇文章了。

至于它的自定义路由表支持,我觉得都是次要了。

介绍到此,让我们按步骤干好事情。

(下文选用最新的Ubuntu 14.04 LTSOCServ 0.10.5作为标准环境,但我会尽量提供不依赖版本的步骤与建议。)

编译OCserv

官方站点找最新的OpenConnect Server版本。

curl -O ftp://ftp.infradead.org/pub/ocserv/ocserv-0.10.5.tar.xz

tar xvf ocserv-0.10.5.tar.xz

cd ocserv-0.10.5

看下README文件提及的编译依赖。理论上只有libgnutls-dev和libreadline-dev是必须的,但我们还是把可选的功能都带上。

(顺便一提,如果你在Ubuntu 14.04或更早版本上,libgnutls-dev的版本还是2.x,需要用libgnutls28-dev获取3.x的GnuTLS才能支持OCserv。)

sudo apt-get install build-essential pkg-config libgnutls28-dev libreadline-dev libseccomp-dev libwrap0-dev libnl-nf-3-dev liblz4-dev

编译并安装。

./configure

(在我测试的环境里,最终报告只有systemd、dbus、PAM、Radius、GSSAPI的结果为no,如果你发现其他项目显示no,不必担心,你的环境可能安装了可选的包导致出现不同结果。目前只有libgnutls28-dev是必须的模块。)

(在Ubuntu 14.04上,liblz4-dev缺了pkg-config文件,所以即便安装了liblz4-dev,LZ4支持仍显示no。LZ4支持是ocserv 0.10下的新功能,装不装对使用没大碍,如果需要,你得想办法手动安装LZ4。)

make

sudo make install

配置OCserv

我们希望做到的,是无需用户名与密码的客户端证书验证登陆。但在此之前,让我们先测通更简单的密码登录模式。首先让我们把CA证书与服务器证书生成好,具体步骤官方文档也有——

先准备好certtool命令。

sudo apt-get install gnutls-bin

创建CA

mkdir certificates

cd certificates

CA模板,创建ca.tmpl,按需填写,这里的cn和organization可以随便填。

cn = "Your CA name"
organization = "Your fancy name"
serial = 1
expiration_days = 3650
ca
signing_key
cert_signing_key
crl_signing_key

CA密钥

certtool --generate-privkey --outfile ca-key.pem

CA证书

certtool --generate-self-signed --load-privkey ca-key.pem --template ca.tmpl --outfile ca-cert.pem

同理,我们用CA签名,生成服务器证书。先创建server.tmpl模板。这里的cn项必须对应你最终提供服务的hostname或IP,否则AnyConnect客户端将无法正确导入证书。

cn = "Your hostname or IP"
organization = "Your fancy name"
expiration_days = 3650
signing_key
encryption_key
tls_www_server

Server密钥

certtool --generate-privkey --outfile server-key.pem

Server证书

certtool --generate-certificate --load-privkey server-key.pem --load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem --template server.tmpl --outfile server-cert.pem

将CA,Server证书与密钥复制到以下文件夹

sudo cp ca-cert.pem /etc/ssl/private/my-ca-cert.pem

sudo cp server-cert.pem /etc/ssl/private/my-server-cert.pem

sudo cp server-key.pem /etc/ssl/private/my-server-key.pem

剩下的就是OCServ配置文件了。同样的,参考官方文档是最佳选项,但为了方便起见,这是你需要注意的一些设置。回到ocserv-0.10.5的文件夹下,将配置文件复制到OCserv默认读取的位置。

sudo mkdir /etc/ocserv

sudo cp doc/sample.config /etc/ocserv/ocserv.conf

确保以下配置正确

# 登陆方式,目前先用密码登录
auth = "plain[/etc/ocserv/ocpasswd]"

# 允许同时连接的客户端数量
max-clients = 4

# 限制同一客户端的并行登陆数量
max-same-clients = 2

# 服务监听的IP(服务器IP,可不设置)
listen-host = 1.2.3.4

# 服务监听的TCP/UDP端口(选择你喜欢的数字)
tcp-port = 9000
udp-port = 9001

# 自动优化VPN的网络性能
try-mtu-discovery = true

# 确保服务器正确读取用户证书(后面会用到用户证书)
cert-user-oid = 2.5.4.3

# 服务器证书与密钥
server-cert = /etc/ssl/private/my-server-cert.pem
server-key = /etc/ssl/private/my-server-key.pem

# 客户端连上vpn后使用的dns
dns = 8.8.8.8
dns = 8.8.4.4

# 注释掉所有的route,让服务器成为gateway
#route = 192.168.1.0/255.255.255.0

# 启用cisco客户端兼容性支持
cisco-client-compat = true

创建一个登陆用的用户名与密码。

sudo ocpasswd -c /etc/ocserv/ocpasswd your-username

这样OCserv就基本配置好了。但如果你和我一样强化过服务器安全,还得为服务器上开些端口才行。以Linode的安全配置为例,我们需要加入和修改以下内容。

sudo nano /etc/iptables.firewall.rules

打开OCserv对应的TCP/UDP端口(别忘了对应你选择的端口数)

-A INPUT -p tcp -m state --state NEW --dport 9000 -j ACCEPT
-A INPUT -p udp -m state --state NEW --dport 9001 -j ACCEPT

注释这行,允许转发

# -A FORWARD -j DROP

启用NAT

*nat
-A POSTROUTING -j MASQUERADE
COMMIT

完成之后导入新配置并检查配置正确。

sudo iptables-restore < /etc/iptables.firewall.rules

sudo iptables -L

sudo iptables -t nat -L

如果你之前没有配置服务器启动时自动导入这个设置

sudo nano /etc/network/if-pre-up.d/firewall

输入以下内容

#!/bin/sh
/sbin/iptables-restore < /etc/iptables.firewall.rules

最后我们需要打开IPv4的流量转发。

sudo nano /etc/sysctl.conf

启用此项

net.ipv4.ip_forward=1

并刷新配置

sudo sysctl -p /etc/sysctl.conf

测试OCserv

在服务器端启动OpenConnect Server。

sudo ocserv -f -d 1

如果服务没错误退出,是时候来测测客户端了。假设你使用iOS,下载Cisco AnyConnect

在Connections下加入新的VPN配置,在服务器地址栏目上填入对应的IP/Hostname和TCP端口(我们的例子就是1.2.3.4:9000)

然后到设置标签页下暂时禁用“阻止不信任的服务器”选项。首次连接,AnyConnect会提示你这是不信任证书,如果你之前的服务器证书模板的cn没写错的话(我们的例子是1.2.3.4),你可以接受并导入该证书(可在诊断标签页的证书菜单里的服务器证书列表看到)。以后即便启用“阻止不信任的服务器”选项,也不会报错了(和SSH首次登陆类似)。

确定VPN连接正常并可以科学上网后,我们可以接着提高网络生活质量。

自动化OCserv

假如现有的配置有哪里让人不大满意,大概是这两点——

  1. OCserv的服务最好会自动跑,进程挂了也自动恢复。
  2. AnyConnect每次都要输入密码很麻烦,最好用客户端证书验证。

为OCserv写个简单的upstart脚本。

cd /etc/init.d

sudo ln -s /lib/init/upstart-job ocserv

cd /etc/init

sudo nano ocserv.conf

放入以下内容

#!upstart
description "OpenConnect Server"

start on runlevel [2345]
stop on runlevel [06]

respawn
respawn limit 20 5

script
    exec start-stop-daemon --start --pidfile /var/run/ocserv.pid --exec /usr/local/sbin/ocserv -- -f >> /dev/null 2>&1
end script

这样就可以用以下方式启动/暂停服务

sudo service ocserv start

sudo service ocserv stop

为AnyConnect建个客户端证书

和服务器端证书的步骤基本相同。回到之前的certificates文件夹。

创建user.tmpl

cn = "some random name"
unit = "some random unit"
expiration_days = 365
signing_key
tls_www_client

User密钥

certtool --generate-privkey --outfile user-key.pem

User证书

certtool --generate-certificate --load-privkey user-key.pem --load-ca-certificate ca-cert.pem --load-ca-privkey ca-key.pem --template user.tmpl --outfile user-cert.pem

然后要将证书和密钥转为PKCS12的格式。

certtool --to-p12 --load-privkey user-key.pem --pkcs-cipher 3des-pkcs12 --load-certificate user-cert.pem --outfile user.p12 --outder

然后我们要通过URL将user.p12文件导入AnyConnect,具体位置在诊断标签页的证书栏目下。如果你的服务器已经有Nginx/Apache服务,只要传到一个可以访问的URL路径下即可。另一个选择是将user.p12复制到本地,建立一个HTTP服务(例如用node.js)。

导入成功之后,将对应的VPN设置的高级设置部分的证书栏目,改为导入的这张证书。

最后我们要调整下OCserv的配置——

sudo nano /etc/ocserv/ocserv.conf

修改以下内容

# 改为证书登陆,注释掉原来的登陆模式
auth = "certificate"

# 证书认证不支持这个选项,注释掉这行
#listen-clear-file = /var/run/ocserv-conn.socket

# 启用证书验证
ca-cert = /etc/ssl/private/my-ca-cert.pem

重启OCserv服务,确认VPN无需密码就可以正常登陆。

小结

这篇笔记是在完成配置之后靠回忆写下的,仅仅做到“可用”,没做更多优化与安全配置。如有什么疏漏(很可能有,毕竟环境不可能完全一致),欢迎在留言栏相互协助。祝各位的iPhone生活美满~

参考阅读:

Author: 店长

The Master of BitInn

41 thoughts on “折腾笔记:架设OpenConnect Server给iPhone提供更顺畅的网络生活”

  1. 我这里在CentOS上也部署成功了(编译安装部分参考https://botu.me/install-ocserv-on-centos6/)。其余启用证书部分按照店长的步骤做下来基本没有问题,就是在启用证书这里需要修改服务器端配置文件ocserv.conf,把里面的session-control = true 改成session-control = false,否则会导致worker进程异常中止

    1. 奇怪了,session-control默认不是被注释掉的么?

      大多数配置都不需要用它,它的默认值不是false?

      1. 我从下载的source包的doc目录里找的ample.config , 默认是启用auth = “plain[./sample.passwd]”,并且session-control = true 开启

  2. 使用证书登录的话需要取消以下项目的注释, 0.8.4 版如此
    cert-user-oid = 0.9.2342.19200300.100.1.1
    cert-group-oid = 2.5.4.11

    1. Hmm,UID并不是必须的X.509证书模板选项,例如我们的例子里就只设了CN。

      (cert-user-oid = 0.9.2342.19200300.100.1.1对应的是X.509里的UID)

      PS:在本文对应的配置场景下,并没有特殊的user/group设置,读取UID或CN并没有什么实质作用,默认注释也不影响使用。

  3. ocserv的路由表设置非常奇怪

    在windows下,如果设置为default gateway模式,连上vpn后,其路由表设置会导致windows下client无法直接与vpn服务器进行通信(比如不能ssh vpn服务器或者访问架设在vpn服务器上的网站)

    但linux,android,ios则不会出现这种问题,不知道有没有解决方法

    1. 查到解决方法了

      要全局路由的话,设置里不要直接注释掉全部route
      而是改成
      route = 0.0.0.0/128.0.0.0
      route = 128.0.0.0/128.0.0.0
      来避开cisco 客户端的奇怪行为

  4. 准确的说,OCServ需要的是3.1的GnuTLS,而libgnutls28-dev只能获取到3.0的
    ubuntu 12.04下尝试升级GnuTLS到3.1,结果陷入了依赖包升级地狱,有不少东西都需要升级,折腾了半天没有搞定,准备直接上14.04的ubuntu了

  5. 奇怪,我可以连接上VPN,但无法访问任何外部网站。
    已经打开IPv4的转发了,net.ipv4.ip_forward=1,使用PPTP连接是可以访问外网的。
    唯一和店长设置不同的地方就是没设置iptables, 因为我没有设置防火墙。
    请问这种情况该从哪里开始找原因呢?

  6. 这里的cn项必须对应你最终提供服务的hostname或IP,否则AnyConnect客户端将无法正确导入证书。
    我这里写上我VPS的IP地址也不能导入,换成hostname之后也不能导入,问一下是什么原因?谢谢

  7. android如何配置证书登录?pc ios端都可用了 剩下android总是登录不了 只能帐户密码登录

  8. 学习下最新版本的编译配置
    certtool生成p12文件要加上参数–outder 才能导入到android 和 IOS

      1. 不是兼容问题吧,只是这儿:https://support.google.com/nexus/answer/2844832?hl=zh-Hans说了Android 支持以扩展名为 .crt 或 .cer 的文件格式保存的 DER 编码 X.509 证书。加了–outder会输出 DER 编码的证书 导入就正常了 IOS大概也一样吧 其实用那种生成都一样

        1. 我和ocserv作者(也是gnutls作者)折腾了半天没搞懂iOS上为什么无法导入certtool的p12证书,他手里又没iOS设备,所以干脆走openssl。

  9. 店长,sudo iptables-restore < /etc/iptables.firewall.rules 一步,总是提示Bad argument `*nat',是什么问题?ubuntu 14.04, DigitalOcean

  10. 你好,非常感谢你写了这篇文章,我按照这个教程一路下来,几乎就要成功了,在最后遇到了一点问题:我用 `sudo ocserv -f -d 1` 成功的启动了 ocserv ,并且用 iOS 上的 AnyConnect 客户端连上了服务器,但是打不开任何网站了,请问你遇到过这个问题吗?是我哪里出了疏漏吗?

    对了,在配置过程中我发现 certtool 并不是本来就在 Ubuntu 里安装好的,你可以在安装依赖那小节里再加一个 gnutls-bin

    谢谢

    1. 啊,不好意思,我后来看了评论,发现自己也犯了和 @笑笑猫 一样的错误,即使之前 iptables 什么都没有配置过,文中的步骤还是要执行的

  11. 友情提示下,当前的ocserv 0.9.0和0.9.0.1有一个连接断开后无法用auth cookie自动重连的bug。git master上已经修好两周了(我测试有效)。但未见官方发0.9.1包,所以使用时请注意。

  12. 我配置完之后,ios手机能正常连接,但是电脑anyconnect客户端连接上自动断开,显示MTU太小,有解决方案么?

Comments are closed.