绕过网管搭建公司内网vpn

武汉肺炎期间搭建的公司内网vpn

以前在外网vps上搭建过openvpn,过程很复杂,主要是验证这一块,各种秘钥证书让openvpn架设起来很不友好。可是最后搭建成功后被防火长城严重干扰,基本上没法用。近期武汉的肺炎导致公司不能正常开工,公司的网管又不能及时搭建vpn,为了大家能够有一个良好的远程办公体验,我绕过了网管控制的公司外网出口路由器自行搭建了部门的vpn。

OpenVPN

总体的思路大概是上图这样,在部门10.0.17.0/24网段内的一台内网服务器10.0.17.48搭建openvpn服务,采用tap模式将内网ip地址分配给openvpn客户端,并通过外网端口转发将内网的服务映射到外网vps上

OpenVPN的安装

网上大把openVPN的安装教程文章,基本上都是手动配置各种证书和秘钥,非常的繁琐烦人,当然不止我觉得烦,所以已经有人做了自动安装的脚本,github上有两个好几K星星的安装脚本,我选了其中一个:https://github.com/angristan/openvpn-install
按照脚本提供的使用方法,直接运行以下命令即可:

curl -O https://raw.githubusercontent.com/angristan/openvpn-install/master/openvpn-install.sh
chmod +x openvpn-install.sh
./openvpn-install.sh

安装过程中需要手动输入一些配置,比如tcp/udp模式的选择,对外提供服务的ip和port,以及其他一些关于证书和秘钥的自定义参数。 我们这里只设置模式和ip端口,因为是内网服务器,脚本会直接帮你输入内网ip,这里我们要改为最后对外提供服务的外网ip,上图中的120.100.1.1,端口随便定义。
安装结束后会让你输入第一个用户的用户名,之后为这个用户创建ovpn权限文件。
如果想增加或者删除用户,再次运行./openvpn-install.sh 脚本即可。

OpenVPN的配置

这个脚本提供的是tun模式的服务器,使用tun模式的话,openvpn客户端会得到另外一个内网网段的地址而不能与公司内网10.0.17.0/24直接交互,如需交互就要配置复杂的路由表,操作起来稍微有些不太友好。最初我使用了tun模式,10.0.17.48启动tun模式后会虚拟一张拥有10.8.0.1地址的网卡,并自动成为10.8.0.0/24网段的网关,openvpn客户端会得到10.8.0.0/24网段的ip地址,如果公司内的电脑想要访问10.8.0.0/24网段的机器,比如一个服务器开发人员远程通过openvpn连入公司内网,得到ip地址10.8.0.5,在自己的开发机上启动了一个服务器程序,处于公司内的测试人员想要访问该远程开发人员的服务器程序就需要在本地配置路由表:

linux:route add -net 10.8.0.0 netmask 255.255.255.0 gw 10.0.17.48 eth0 (对应的删除命令route del -net 10.8.0.0 netmask 255.255.255.0,显示路由命令route)
windows:oute add 10.8.0.0 mask 255.255.255.0 10.0.17.48(对应的删除命令route delete 10.8.0.0,显示路由命令route print)

这样公司内网10.0.17.0/24网段的机器才知道如何找到10.8.0.0/24网段。 但是我们有一些服务器没有在10.0.17.0/24网段,而是在10.0.30.0/24网段,尝试在10.0.30.0/24网段的机器上配置路由后发现不能双向通信,没有再接着深入研究,决定放弃tun模式使用tap模式,而使用tap模式需要首先创建网桥。

进行桥接配置

同样的使用tap模式的教程文章一大把,看起来都非常的繁琐也写的不清楚,最后还是求助于OpenVPN官网的教程:https://openvpn.net/community-resources/ethernet-bridging/

按照官方教程,我简单总结了一下使用方法:

首先找一台有两张网卡的服务器(一张网卡不知道行不行,没有深入研究),选其中一张做桥接。按照自己的网络情况修改bridge-start.sh中的参数。

执行这些防火墙规则(不开防火墙不知道需不需要执行,反正我执行了,也许其实没啥用):
iptables -A INPUT -i tap0 -j ACCEPT
iptables -A INPUT -i br0 -j ACCEPT
iptables -A FORWARD -i br0 -j ACCEPT

每次启动和关闭openvpn服务器都要按照这样的顺序:
run bridge-start
run openvpn
stop openvpn
run bridge-stop

官方给出的创建网桥脚本(经过修改参数适用于我的网络环境):

sample-scripts/bridge-start

#!/bin/bash

#################################
# Set up Ethernet bridge on Linux
# Requires: bridge-utils
#################################

# Define Bridge Interface
br="br0"

# Define list of TAP interfaces to be bridged,
# for example tap="tap0 tap1 tap2".
tap="tap0"

# Define physical ethernet interface to be bridged
# with TAP interface(s) above.
eth="ens18"
eth_ip="10.0.17.48"
eth_netmask="255.255.255.0"
eth_broadcast="10.0.17.255"

for t in $tap; do
    openvpn --mktun --dev $t
done

brctl addbr $br
brctl addif $br $eth

for t in $tap; do
    brctl addif $br $t
done

for t in $tap; do
    ifconfig $t 0.0.0.0 promisc up
done

ifconfig $eth 0.0.0.0 promisc up

ifconfig $br $eth_ip netmask $eth_netmask broadcast $eth_broadcast

官方给出的停止网桥脚本:

sample-scripts/bridge-stop

#!/bin/bash

####################################
# Tear Down Ethernet bridge on Linux
####################################

# Define Bridge Interface
br="br0"

# Define list of TAP interfaces to be bridged together
tap="tap0"

ifconfig $br down
brctl delbr $br

for t in $tap; do
    openvpn --rmtun --dev $t
done

通过外网端口转发将内网的服务映射到外网vps上

/usr/sbin/openvpn –cd /etc/openvpn/ –config server.conf 启动openvpn服务后,10.0.17.48就开始提供vpn服务了,但是不在公司内的外网工作人员并不能访问内网地址,就需要一个固定的外网地址进行转发,因为一直有使用ssh的端口转发功能,所以最先想到了ssh,由于ssh端口转发只支持tcp,所以只能使用tcp模式启动openvpn:

根据网络情况修改参数:ssh -fN -R19028:127.0.0.1:19028 root@120.100.1.1 -p22

这样客户端通过连接120.100.1.1:19028 就可以访问vpn服务了。

至此整套vpn就搭建完了,即使不在公司内,在家也能接入公司的网络使用svn,redmine等等开发工具了。但是在使用过程中发现一个问题,vpn经常会断开,不是某个客户端自己断开,而是整个服务器停止服务,所有客户端全部都会掉线。阅读了官网关于tcp/udp模式的介绍:https://openvpn.net/faq/why-does-openvpn-use-udp-and-tcp/ 原来tcp模式并不是正常的模式,官方的原话是tcp并不稳定,并不推荐使用tcp,但是本着不好用总比没有强还是支持了tcp模式,因为tcp-over-tcp会造成tcp meltdown,两层tcp造成的超时叠加重传会导致tcp熔断,整个连接就over了。
又回到寻找转发工具的路上,经过比较后选择了frp:https://github.com/fatedier/frp 这个国人开发的工具(貌似codis也是同一个人开发的)支持udp的转发,并支持底层传输使用kcp(kcp基于udp),这样整个链路连通后最多只有一层tcp协议,不会发生tcp meltdown。frp的使用也很简单,需要在外网vps上启动frps:./frps -c frps.ini,内网openvpn服务器启动frpc:./frpc -c frpc.ini

frps.ini:

[common]
bind_port = 19027
kcp_bind_port = 19027

frpc.ini:

[common]
server_addr = 120.100.1.1
server_port = 19027
protocol = kcp
[openvpn]
type = udp
local_ip = 127.0.0.1
local_port = 19028
remote_port = 19028

这样frp通过19027作为底层传输19028端口的内容,实现udp协议的转发。

最后把openvpn的配置贴一下备忘 /etc/openvpn/server.conf:

port 19028
proto udp
dev tap0
user nobody
group nobody
persist-key
persist-tun
keepalive 10 120
topology subnet
server-bridge 10.0.17.48 255.255.255.0 10.0.17.150 10.0.17.253
ifconfig-pool-persist ipp.txt
push "dhcp-option DNS 114.114.114.114"
push "dhcp-option DNS 114.114.114.115"
push "route 10.0.0.0 255.255.0.0"
client-to-client
dh none
xxx
xxx
xxx
...

客户端权限文件xxx.ovpn:

client
proto udp
remote 120.100.1.1 19028
dev tap
resolv-retry infinite
nobind
persist-key
persist-tun
remote-cert-tls server
verify-x509-name server_B5pwaF04ayWqXebM name
auth SHA256
auth-nocache
cipher AES-128-GCM
tls-client
tls-version-min 1.2
tls-cipher TLS-ECDHE-ECDSA-WITH-AES-128-GCM-SHA256
verb 3
xxx
xxx
xxx
...