虚拟专用网络(Virtual Private Network,VPN)可通过一个公用网络建立一个临时的、安全的链接,克服了公共网络缺乏保密性的弱点。借助VPN隧道可将物理分离的网络通过 internet 进行逻辑上的直接连接,延展了企业的内网网络,为远程办公、移动办公提供支持。

目前有很多用于搭建VPN的开源软件,例如 Shadowsocks (小飞机),但是好像逐渐的都失去了维护,本文基于 OpenVPN 介绍 VPN 的搭建过程。

一、OpenVPN 介绍

OpenVPN 官网(可能不一定能访问通):https://openvpn.net/

OpenVPN 提供了两种商业版的 VPN 服务:

  1. OpenVPN Cloud :一种 VPN 托管服务,连接 OpenVPN 提供的 VPN 服务,可免费提供 3 个连接;
  2. OpenVPN Access Server:一个商业版的 VPN 程序,简称(openvpn-as),相比于 openvpn 增加了 web 管理界面,可通过可视化对 VPN 服务和用户进行管理,可免费提供 2 个连接。

商业版在 OpenVPN 官网有很多介绍,但是免费版的部署和讲解就比较少。本文主要描述的还是免费版的部署方法,不对 openvpn-as 的部署进行赘述,官网有很详细的部署文档,见如下链接:

  1. 安装包下载:https://openvpn.net/download-open-vpn/
  2. 入门手册: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

  1. 检查是否可以找到对应的软件包。

    yum list easy-rsa
    yum list openvpn
    

    easy-rsa 是一个用于生成证书的工具。

  2. 如果找不到软件包,则需要安装 epel 第三方源,安装完成后再使用 yum list * 命令查询软件包。

    yum -y install epel-release
    
    # 查询安装的源列表
    yum repolist
    
  3. 准备好软件好之后,执行如下命令进行安装。

    yum -y install easy-rsa
    yum -y install openvpn 
    

    本文安装的 easy-rsa 版本为 3.0.8-1.el7openvpn 版本为 2.4.12-1.el7

2.2.2 证书生成

  1. 在生成证书前需要准备路径用于生成证书,本文证书环境路径为 /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
    
  2. 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"
    
  3. 初始化 PKI 并生成相关的目录和文件。

    ./easyrsa init-pki
    
  4. 创建根证书,根证书用于对后续的 serverclient 证书签名时使用。

    ./easyrsa build-ca
    

    创建根证书

  5. 创建服务端证书申请信息,其中 server 为秘钥名称,nopass 表示不加密私钥文件,生成过程中直接回车默认。

    ./easyrsa gen-req server nopass
    

    创建服务端秘钥

  6. 使用上面 server.req 的申请,颁发 server 证书。

    ./easyrsa sign server server
    

    颁发服务端证书

  7. 创建 Diffie-Hellman 文件,秘钥交换时的 Diffie-Hellman 算法。

    ./easyrsa gen-dh
    
  8. 创建 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

  1. 服务器路由转发配置

    # 开启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
    
  2. 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
    
  3. 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 #指定当前客户端的私钥文件路径
    
  4. 配置完成之后,服务端可以使用 openvpn --config server.conf 命令进行启动,也可以将 OpenVPN 做成 service 。客户端存在多个平台,多个版本,不同的平台和版本启动方式可能略有些不同,但是都很简单。本文不再赘述。

    需要注意,Windows 版的客户端,低于 2.4 版本可能无法正常连接服务端,测试时使用 2.3.10 版本连接服务端失败了。

以上示例提供的是最简化的配置,能够实现 VPN 服务端和客户端连接,客户端通过服务端代理上网。但,也许你想要了解更多的配置,那还需要继续往下看。

2.2.4 进阶配置

  1. 多服务端配置

    可能一个 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 参数可以实现随机选择服务端访问。

  2. 内联证书文件

    使用路径引用证书文件时,同时需要准备多个文件,不是很方便,可以采用内联的方式将证书文件内容直接添加到 *.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 选项支持通过内联的方式将证书添加到主配置文件。

  3. 使用 TLS 增强安全性

    OpenVPN 提供 tls-authtls-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. 异常。

  4. 部分 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 版本的服务端兼容,由于安装包可能较难下载,本文准备了部分系统的安装包。

OpenVPN 客户端安装包

安装包的版本信息对应如下:

版本
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 优点:

  1. 表现得像一个真实的网络适配器(除了它是一个虚拟网络适配器);
  2. 可以传输任何网络协议(IPv4、IPv6、Nettalk、IPX 等);
  3. 在第 2 层工作,这意味着以太网帧通过 VPN 隧道传递;
  4. 可用于网桥。

TAP 缺点:

  1. 在 VPN 隧道上导致更多的广播开销;
  2. 在通过 VPN 隧道传输的所有数据包上添加以太网标头的开销;
  3. 扩展性差;
  4. 不能用于 Android 或 iOS 设备。

TUN 优点:

  1. 较低的流量开销,仅传输发往 VPN 客户端的流量;
  2. 仅传输第 3 层 IP 数据包;

TUN 缺点:

  1. 广播流量不正常传输(如 LAN 广播类的游戏);
  2. 只能传输 IPv4(OpenVPN 2.3 增加了 IPv6);
  3. 不能用于网桥。

关于路由和网桥的介绍,可参见以下官方文档:

  1. 桥接和路由介绍
    https://community.openvpn.net/openvpn/wiki/BridgingAndRouting

  2. 就配置而言,桥接和路由之间的根本区别是什么?
    https://openvpn.net/faq/what-are-the-fundamental-differences-between-bridging-and-routing-in-terms-of-configuration/

  3. 确定应该使用路由和网桥。
    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