虚拟专用网络(Virtual Private Network,VPN)可通过一个公用网络建立一个临时的、安全的链接,克服了公共网络缺乏保密性的弱点。借助VPN隧道可将物理分离的网络通过 internet
进行逻辑上的直接连接,延展了企业的内网网络,为远程办公、移动办公提供支持。
目前有很多用于搭建VPN的开源软件,例如 Shadowsocks
(小飞机),但是好像逐渐的都失去了维护,本文基于 OpenVPN
介绍 VPN 的搭建过程。
一、OpenVPN 介绍
OpenVPN 官网(可能不一定能访问通):https://openvpn.net/
OpenVPN 提供了两种商业版的 VPN 服务:
OpenVPN Cloud
:一种 VPN 托管服务,连接OpenVPN
提供的 VPN 服务,可免费提供 3 个连接;OpenVPN Access Server
:一个商业版的 VPN 程序,简称(openvpn-as),相比于openvpn
增加了 web 管理界面,可通过可视化对 VPN 服务和用户进行管理,可免费提供 2 个连接。
商业版在 OpenVPN
官网有很多介绍,但是免费版的部署和讲解就比较少。本文主要描述的还是免费版的部署方法,不对 openvpn-as
的部署进行赘述,官网有很详细的部署文档,见如下链接:
- 安装包下载:https://openvpn.net/download-open-vpn/
- 入门手册:https://openvpn.net/vpn-server-resources/finishing-configuration-of-access-server/
在这里可以找到 OpenVPN 的一些开源库:https://openvpn.net/source-code/
目前手机上都已经移除了谷歌服务框架和谷歌应用市场,可通过
Github
开源库下载release
的最新Android
客户端安装包。
二、CentOS 7 部署 OpenVPN
2.1 脚本方式部署
脚本地址:https://github.com/Angristan/OpenVPN-install
一键执行脚本,输入几个简单的配置项即可完成 OpenVPN 的部署,本文不详细描述,部署方法见脚本的 REMADE 文档。
2.2 CentOS 7 部署
2.2.1 安装 OpenVPN
-
检查是否可以找到对应的软件包。
yum list easy-rsa yum list openvpn
easy-rsa
是一个用于生成证书的工具。 -
如果找不到软件包,则需要安装
epel
第三方源,安装完成后再使用yum list *
命令查询软件包。yum -y install epel-release # 查询安装的源列表 yum repolist
-
准备好软件好之后,执行如下命令进行安装。
yum -y install easy-rsa yum -y install openvpn
本文安装的
easy-rsa
版本为3.0.8-1.el7
,openvpn
版本为2.4.12-1.el7
。
2.2.2 证书生成
-
在生成证书前需要准备路径用于生成证书,本文证书环境路径为
/opt/easy-rsa
。mkdir -p /opt/easy-rsa cd /opt/easy-rsa # 文件拷贝 cp -r /usr/share/easy-rsa/3.0.8/* . cp -r /usr/share/doc/easy-rsa-3.0.8/vars.example vars
-
vars
文件包含了默认的证书配置,需要进行部分配置调整,取消配置的注释。set_var EASYRSA_DN "cn_only" set_var EASYRSA_REQ_COUNTRY "CN" set_var EASYRSA_REQ_PROVINCE "Shanghai" set_var EASYRSA_REQ_CITY "Shanghai" set_var EASYRSA_REQ_ORG "nineya" set_var EASYRSA_REQ_EMAIL "nineya@qq.com"
-
初始化 PKI 并生成相关的目录和文件。
./easyrsa init-pki
-
创建根证书,根证书用于对后续的
server
和client
证书签名时使用。./easyrsa build-ca
-
创建服务端证书申请信息,其中
server
为秘钥名称,nopass
表示不加密私钥文件,生成过程中直接回车默认。./easyrsa gen-req server nopass
-
使用上面
server.req
的申请,颁发server
证书。./easyrsa sign server server
-
创建
Diffie-Hellman
文件,秘钥交换时的Diffie-Hellman
算法。./easyrsa gen-dh
-
创建
client
端的证书和私钥文件,操作和创建server
端的相同。./easyrsa gen-req client nopass ./easyrsa sign client client
client
为客户端证书名称,可自定义。
2.2.3 基础配置
配置参数说明文档:https://build.openvpn.net/man/openvpn-2.5/openvpn.8.html
-
服务器路由转发配置
# 开启ipv4数据转发功能 echo 'net.ipv4.ip_forward=1' >/etc/sysctl.d/99-openvpn.conf sysctl --system # 添加网络策略白名单 iptables -t nat -I POSTROUTING 1 -s 10.8.0.0/24 -o eth0 -j MASQUERADE iptables -I INPUT 1 -i tun0 -j ACCEPT iptables -I FORWARD 1 -i eth0 -o tun0 -j ACCEPT iptables -I FORWARD 1 -i tun0 -o eth0 -j ACCEPT iptables -I INPUT 1 -i eth0 -p udp --dport 1194 -j ACCEPT iptables-save
-
OpenVPN
服务端server.conf
配置文件# 配置参考:https://build.openvpn.net/man/openvpn-2.5/openvpn.8.html # 指定日志文件的记录详细级别,可选0-9,等级越高日志内容越详细 verb 3 # 端口 port 1194 # 协议 proto udp # 采用路由隧道模式(TUN) dev tun # TUN模式下运行时配置虚拟寻址拓扑 topology subnet # ca证书文件位置 ca ca.crt # 服务端证书位置 cert server.crt # 服务端私钥名称 key server.key # 交换证书 dh dh.pem # 证书吊销,当特定密钥被泄露但整体 PKI 仍然完好无损时使用 # crl-verify crl.pem # 为客户端分配地址池,不能与VPN服务器内网网段相同 server 10.8.0.0 255.255.255.0 # 允许客户端访问的内网网段 # push "route 192.168.0.0 255.255.255.0" # 推送dns服务器配置到客户端 push "dhcp-option DNS 94.140.14.14" push "dhcp-option DNS 94.140.15.15" # 推送流量转发配置(服务器代理上网的配置)到客户端 push "redirect-gateway def1 bypass-dhcp" # 地址池记录文件 ifconfig-pool-persist ipp.txt # 心跳,10秒ping一次,60秒超时时间 keepalive 10 60 # 最多允许50个客户端连接 max-clients 50 # 客户端之间支持通信 client-to-client # 连接状态日志文件 status openvpn-status.log # 日志文件 log /var/log/openvpn.log # 通过keepalive检测超时后,重新启动VPN时,不重新读取keys,保留第一次 # 使用的keys persist-key # 检测超时后,重新启动VPN时,一直保持tun是linkup的。否则网络会先 # linkdown然后再linkup persist-tun # 允许具有相同公共名称的多个客户端同时连接。如果没有此选项,OpenVPN将 # 在连接具有相同通用名称的新客户端时断开客户端实例。 duplicate-cn
-
OpenVPN
客户端client.ovpn
配置文件所有客户端都可使用同一份
*.ovpn
配置文件。# 日志级别 verb 3 # 指定当前VPN是客户端 client # 使用tun隧道传输协议 dev tun # 使用udp协议传输数据 proto udp # 服务器IP地址端口号 remote v.nineya.com 1194 # 断线自动重新连接,在网络不稳定的情况下非常有用 resolv-retry infinite # 不绑定本地特定的端口号 nobind #通过keepalive检测超时后,重新启动VPN,不重新读取keys,保留第一次使用的keys persist-key # 检测超时后,重新启动VPN,一直保持tun是linkup的。否则网络会先linkdown然后再linkup persist-tun # 检查服务器端证书 remote-cert-tls server ca ca.crt #指定CA证书的文件路径 cert client.crt #指定当前客户端的证书文件路径 key client.key #指定当前客户端的私钥文件路径
-
配置完成之后,服务端可以使用
openvpn --config server.conf
命令进行启动,也可以将OpenVPN
做成service
。客户端存在多个平台,多个版本,不同的平台和版本启动方式可能略有些不同,但是都很简单。本文不再赘述。需要注意,
Windows
版的客户端,低于2.4
版本可能无法正常连接服务端,测试时使用2.3.10
版本连接服务端失败了。
以上示例提供的是最简化的配置,能够实现 VPN 服务端和客户端连接,客户端通过服务端代理上网。但,也许你想要了解更多的配置,那还需要继续往下看。
2.2.4 进阶配置
-
多服务端配置
可能一个 VPN 服务不是很稳定,你希望配置多个 VPN 服务器?那么你可以直接配置多个
remote
参数实现。remote v.nineya.com 1194 remote v2.nineya.com 1194
可能只是自定义服务端地址不够符合你的要求,那么可以使用
connection
标签定义连接块,为每一个服务端自定义更多的配置。<connection> remote vpn.nineya.com 1194 tcp </connection> <connection> remote v2.nineya.com 1194 tcp http-proxy 192.168.0.8 8080 </connection>
connection
标签内支持 bind, connect-retry, connect-retry-max, connect-timeout, explicit-exit-notify, float, fragment, http-proxy, http-proxy-option, link-mtu, local, lport, mssfix, mtu-disc, nobind, port, proto, remote, rport, socks-proxy, tun-mtu 和 tun-mtu-extra 这些参数。
这些参数并不是都可以放在connection
标签内部的,只是和客户端连接有关联,具体见文档。
还有,这里面的参数在有些客户端上可能不生效,例如Windows
版本的OpenVPN Commect V3
。默认将以自上而下的顺序连接服务端,添加
remote-random
参数可以实现随机选择服务端访问。 -
内联证书文件
使用路径引用证书文件时,同时需要准备多个文件,不是很方便,可以采用内联的方式将证书文件内容直接添加到
*.ovpn
文件中。这个功能在比较低版本的客户端中貌似不被支持。
<ca> -----BEGIN CERTIFICATE----- MIIBwjCCAWegAwIBAgIJAMw8W2zvjCt/MAoGCCqGSM49BAMCMB4xHDAaBgNVBAMM ...... Jq2y/Hzl+EmGDy8R3iomTAVETAHP3A== -----END CERTIFICATE----- </ca>
ca、cert、dh、extra-certs、key、pkcs12、secret、crl-verify、http-proxy-user-pass、tls-auth 和 tls-crypt 选项支持通过内联的方式将证书添加到主配置文件。
-
使用 TLS 增强安全性
OpenVPN
提供tls-auth
和tls-crypt
两种 TLS 握手策略,相对而言,tls-crypt
更加的安全。官方的解释是,
tls-auth
对数据包进行认证,tls-crypt
对数据包进行加密和认证。tls-crypt
将使用预共享密钥对所有消息进行加密,它隐藏了与OpenVPN服务器进行的TLS握手的初始化,能够防止 TLS 拒绝服务攻击。使用tls-auth
,攻击者可以同时打开数千个 TLS 连接,但不提供有效的证书,从而阻塞了可用端口。使用tls-crypt
,服务器将在最初的步骤上预先拒绝连接。使用
openvpn --genkey --secret tls.key
创建 TLS 秘钥。tls-auth
配置方式。# 服务端 tls-server tls-version-min 1.2 tls-auth tls.key 0 # 客户端 tls-client tls-version-min 1.2 tls-auth tls.key 1 ## 或者内联配置 tls-client tls-version-min 1.2 key-direction 1 <tls-auth> ...... </tls-auth>
tls-crypt
配置方式。# 服务端 tls-server tls-version-min 1.2 tls-crypt tls.key # 客户端 tls-client tls-version-min 1.2 <tls-crypt> ...... </tls-crypt>
可能你会想通过
tls-cipher
指定 TLS 使用的算法,但是这个貌似和证书的算法相关,如果证书的算法不匹配的话,这个 TLS 指定的算法就会抛出TLS error: The server has no TLS ciphersuites in common with the client. Your --tls-cipher setting might be too restrictive.
异常。 -
部分 IP 不通过 VPN 进行转发
如果大部分的 IP 都是需要进行转发的,那么可以对无须使用 VPN 进行转发的地址使用以下命令绕过。
route 网段 子网掩码 net_gateway # 示例 route 116.224.0.0 255.255.255.0 net_gateway
如果大部分的 IP 都是不需要转发的,那么可以设置默认不转发,然后通过
route
指定需要进行转发的 IP。# 默认不转发 route-nopull route 网段 子网掩码 vpn_gateway # 示例 route-nopull route 172.168.0.0 255.255.255.0 vpn_gateway
默认可以配置 100 条路由规则,如果需要更多的路由规则,可以通过
max-routes
参数进行配置。# 最多1000条 max-routes 1000
三、客户端部署
OpenVPN connent v3.x
有较大的改版,但依旧和 2.x
版本的服务端兼容,由于安装包可能较难下载,本文准备了部分系统的安装包。
安装包的版本信息对应如下:
版本 | |
---|---|
Win10客户端 | 3.3.6.2752_x64 |
Mac客户端 | 3.3.6.4368 |
Android客户端 | 0.7.37 |
3.1 Windows 客户端
Windows 客户端分为了商业版(openvpn connect v3)和社区版(openvpn 2.x),目前两个版本都无须付费,只是商业版支持了 openvpn cloud
这种通过 https
配置的方式,可视化的方式导入 *.ovpn
文件,而社区版只能通过将文件拷贝到 Program Files\OpenVPN\config\client
目录进行配置。
还有就是,商业 UI 做了比较大的改变,多了一些配置项,然后不支持中文了,社区版本是支持多语言的。
目前大部分客户端应该都分了商业版(OpenVPN Connect)和社区版,官方正在主推商业版。
商业版下载:https://openvpn.net/download-open-vpn/
社区版下载:https://openvpn.net/community-downloads/
社区版需要手动将文件拷贝到
Program Files\OpenVPN\config\client
目录,然后才能启动程序。
3.2 Android 客户端
商业版:
谷歌应用商店下载:https://play.google.com/store/apps/details?id=net.openvpn.openvpn
社区版:
目前大部分手机已经移除了谷歌应用商店,下载比较麻烦可直接到 Github
进行下载:https://github.com/schwabe/ics-openvpn/releases
相比较而言,社区版的 Android 客户端更好用,配置项更丰富,且支持部分应用绕过VPN,而商业版的不行。
Android 手机由于权限限制,有可能读取配置文件会失败,可以换种文件打开方式导入。
3.3 Linux 客户端
我们使用的 openvpn
除了可以作为服务端以外,也可以作为客户端程序来使用。
openvpn --config xxx.ovpn
目前 OpenVPN
提供了 3.0 的 Linux 客户端,只能当做客户端来使用,无须使用 root
启动,详情见链接:https://openvpn.net/openvpn-client-for-linux/
3.0客户端官方安装说明文档:https://community.openvpn.net/openvpn/wiki/OpenVPN3Linux
3.0 属于商业版,提供了通过程序去修改配置文件的 api 接口。
3.4 Mac 客户端
mac 客户端可直接从官网下载:https://openvpn.net/download-open-vpn/
3.5 iOS 客户端
苹果手机的客户端下载地址:https://apps.apple.com/us/app/openvpn-connect/id590379981
但是 OpenVPN
在中国区没有上架,所以国内的 Apple ID
无法下载,可以注册或寻找一个美国的 Apple ID
进行下载,或者手机链接下载了 XY 助手等软件的电脑,用助手安装。
四、问题解决
4.1 路由和桥接
OpenVPN 支持路由(TUN)和桥接(TAP)两种模式,OpenVPN建议使用 TUN 模式,非必要不使用 TAP。
TAP 优点:
- 表现得像一个真实的网络适配器(除了它是一个虚拟网络适配器);
- 可以传输任何网络协议(IPv4、IPv6、Nettalk、IPX 等);
- 在第 2 层工作,这意味着以太网帧通过 VPN 隧道传递;
- 可用于网桥。
TAP 缺点:
- 在 VPN 隧道上导致更多的广播开销;
- 在通过 VPN 隧道传输的所有数据包上添加以太网标头的开销;
- 扩展性差;
- 不能用于 Android 或 iOS 设备。
TUN 优点:
- 较低的流量开销,仅传输发往 VPN 客户端的流量;
- 仅传输第 3 层 IP 数据包;
TUN 缺点:
- 广播流量不正常传输(如 LAN 广播类的游戏);
- 只能传输 IPv4(OpenVPN 2.3 增加了 IPv6);
- 不能用于网桥。
关于路由和网桥的介绍,可参见以下官方文档:
-
桥接和路由介绍
https://community.openvpn.net/openvpn/wiki/BridgingAndRouting -
就配置而言,桥接和路由之间的根本区别是什么?
https://openvpn.net/faq/what-are-the-fundamental-differences-between-bridging-and-routing-in-terms-of-configuration/ -
确定应该使用路由和网桥。
https://openvpn.net/community-resources/how-to/#vpntype
4.2 有线连接时VPN未生效
在无线网络中 VPN
能够正常生效,连接有线网络后 VPN
没有生效。这是网络链路竞争的结果,计算机有多个网络适配器,在计算机访问网络时,就同时有多个选择。在默认情况下,操作系统有自己的判断机制,来按照最优线路访问网络。在实际的测试情况下,windows系统在默认的情况下,几个网络适配器的优先级是:有线网络 > openvpn > 无线网络。
这个优先级就导致了有线网络连接后 VPN 不生效的问题,解决这类问题的通常方法是手动为 VPN 和有线连接的网络设置优先级来实现的,配置方法如下:
通过控制面板或者网络配置界面进入更改网络适配器选项的界面,然后“右击某网络适配器->属性->选择ipv4协议->属性->高级”打开高级tcp/ip设置,在这个界面,撤销掉“自动跃点”的选项,给此适配器手动配置一个接口跃点数。
这个跃点数配置得越高,该适配器在其余适配器中的优先级就越靠后。
4.3 偶尔无法连接
本文上述使用了 udp
的传输模式,VPN
使用不稳定,偶尔可能连接失败,客户端在不断的重试连接,查看服务端日志,抛出了 TLS key negotiation failed to occur within 60 seconds (check your network connectivity)
异常。
具体原因未知,最初我也是在检查网络配置、防火墙配置,但是没什么成效(既然是偶发性的问题,理论上不应该是服务器防火墙的原因造成的),最后将传输模式改为了 tcp
解决。
服务端和客户端都需要修改配置,如果客户端添加了
explicit-exit-notify
配置,需要注释掉。
# 使用tcp协议传输数据
proto tcp
4.4 No match for argument: openvpn
在使用 yum
安装 OpenVPN
时出现 No match for argument: openvpn
报错,表示当前 yum
找不到 OpenVPN
对应的安装包,可执行如下命令解决。
yum install http://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm
注意,不同系统对应的路径不同,如
linux 9
对应的是epel-release-latest-9.noarch.rpm
安装完成后从新执行安装 OpenVPN
的命令。
4.5 iptables: command not foun
该问题源于 iptables
没有安装,执行以下命令安装 iptables
即可。
# 安装
sudo yum install iptables-services -y
# 查看服务状态
systemctl status iptables
# 启动服务
systemctl start iptables