家里运行了一些服务,希望在外面也可以方便的访问它们。
旧办法是通过路由器端口映射到内网端口,但这种方式已经很难满足我的场景,因为例如hadoop服务会开放很多web页面端口,实在难以逐一映射。
于是我选择了在家里自建 VPN 服务器,然后只把 VPN 服务的端口映射到路由器,这样只要在外面连接 VPN 即可像在家里一样任意访问内部网络了。
搭建Openconnect
openconnect 是开源 VPN 项目,包含了服务端和客户端(PC/MAC/Android),是商业产品思科 Cisco Anyconnect的开源版本。
为什么选择它呢?因为它能够从服务端自定义下发路由规则到客户端,这样就可以控制客户端哪些流量需要送往VPN隧道、哪些流量不需要经过VPN隧道,实现类似于同时兼顾访问家庭内网和公司内网的目的。
搭建方式参考官方手册:https://gitlab.com/openconnect/recipes,以 Debian 为例进行安装配置,非常方便:
1 2 3 |
$ sudo apt-get update $ sudo apt-get install ocserv |
这样就安装完成 openconnect 服务端了。
配置Ocserv
接下来配置VPN服务端,分为2大部分:
- 自签一下SSL证书,因为这个VPN协议需要SSL加密。(自签没关系,只要客户端登录时选择信任证书即可)
- 配置ocserv.conf配置文件,主要是定义VPN如何认证用户、VPN网段、还有更高级的路由表下发。
SSL证书
首先搞证书,按官方教程来即可:https://gitlab.com/openconnect/recipes/-/blob/master/ocserv-configuration-basic.md#certificate-management-self-signed
第一步:
1 2 3 |
$ mkdir ~/certificates $ cd ~/certificates |
第二步(原样敲入即可,信息都不重要):
1 2 3 4 5 6 7 8 9 10 |
# vim ca.tmpl cn = "Your CA name" organization = "Your organization name" serial = 1 expiration_days = 3650 ca signing_key cert_signing_key crl_signing_key |
第三步(原样敲入即可,信息都不重要):
1 2 3 4 5 6 7 8 9 |
# vim server.tmpl cn = "Your hostname or IP" organization = "Your organization name" serial = 2 expiration_days = 3650 signing_key encryption_key tls_www_server |
第四步(生成CA证书):
1 2 3 |
$ certtool --generate-privkey --outfile ca-key.pem $ certtool --generate-self-signed --load-privkey ca-key.pem --template /etc/ssl/ca.info --outfile ca-cert.pem |
第五步(用CA签VPN证书):
1 2 3 |
$ certtool --generate-privkey --outfile server-key.pem $ 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 |
把VPN证书拷到ocserv配置目录下:
1 |
$ sudo cp server-cert.pem server-key.pem /etc/ocserv/ |
Ocserv.Conf
先修改登录方式,采用用户密码进行登录:
1 2 3 4 |
$ sudo vim /etc/ocserv/ocserv.conf auth = "plain[passwd=/etc/ocserv/ocpasswd]" #设置用户密码 |
创建密码文件
1 |
$ sudo touch /etc/ocserv/ocpasswd |
创建用户
1 |
$ sudo ocpasswd -c /etc/ocserv/ocpasswd user |
删除用户(如果需要的话),此处只是留下删除用户的命令,不是要求必须执行,否则刚刚创建的用户就被删除了:
1 |
$ sudo ocpasswd -c /etc/ocserv/ocpasswd -d user1 |
然后VPN监听端口有需要可以改一下:
1 2 3 |
tcp-port = 443 udp-port = 443 |
然后就是VPN网段的配置了,我家里是192.168.1.x网段,我们要让VPN使用一个不同的网段,因为VPN server要给每个连接过来的VPN client分配一个VPN网段的IP,如果这个IP和192.168.1.x网段的某个IP冲突就有问题了,所以一定是不同网段的,同样道理也不能和客户端所在的局域网段冲突。
1 2 |
ipv4-network = 192.168.50.0 ipv4-netmask = 255.255.255.0 |
所以我配置了192.168.50.x的一个冷门网段,足够分配254个VPN client,为什么不是255个呢?因为VPN服务端会先占1个IP。
暂时我们配置这些,现在我们启动ocserv来介绍一下VPN是如何工作的。
分析Ocserv工作原理
大家自行在路由器映射VPN端口到公网,然后下载openconnect gui客户端(https://openconnect.github.io/openconnect-gui/),然后连接这样的地址:
https://公网IP:路由器映射端口
即可连接到家里内网的VPN服务端,输入任意正确的linux账号密码即可登录。
连接成功后,在客户端打开命令行查看路由表(我是windows电脑,使用route print命令),会发现2条本就存在的路由记录:
1 2 |
0.0.0.0 0.0.0.0 192.168.2.1 192.168.2.103 25 192.168.2.0 255.255.255.0 在链路上 192.168.2.103 281 |
这是因为windows电脑也在家里,所以本身就被局域网下发了默认网关路由到192.168.2.1路由器、以及192.168.2.x网段的路由记录,这台电脑的自身IP是192.168.2.103。
但是连接VPN后,则多了2条VPN网段的记录:
1 2 |
0.0.0.0 0.0.0.0 192.168.50.1 192.168.50.250 2 192.168.50.0 255.255.255.0 在链路上 192.168.50.250 257 |
又来了一条优先级更高(2)的默认网关路由,指向了192.168.50.1,也就是VPN server的IP地址,同时也多了一条192.168.50.x 这个VPN网段的路由记录,此时电脑上也出现了一张虚拟的网卡,其IP就是192.168.50.250,这是VPN server给分配的,由VPN client负责虚拟出本地网卡。
此时发往192.168.50.x网段的流量会直接从VPN网卡送出,发往其他IP的流量也会命中192.168.8.1的默认网关(同样是从VPN网卡送出),而不是电脑所在局域网原本下发的192.168.2.1的默认网关。
所以VPN网卡已经劫持了所有外出流量,经过VPN虚拟网卡封包为物理IP送往VPN server,再由VPN server拆包后得到真实IP地址,继续进行转发。
此时VPN server也多了一条该客户端的路由记录:
1 2 3 4 5 6 7 8 9 |
root@debian:/home/work# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.2.201 0.0.0.0 UG 0 0 0 ens18 192.168.2.0 0.0.0.0 255.255.255.0 U 0 0 0 ens18 192.168.50.250 0.0.0.0 255.255.255.255 UH 0 0 0 vpns0 # macOS # netstat -nr |
也就是192.168.50.250这个vpn client的流量全部送给vpns0虚拟网卡,也就是说回给该vpn client的包也需要经过vpn server的虚拟网卡封包送走。
vpn基本就是这个原理。
这点原理还不够
不要以为理解上面就万事大吉了,当VPN client请求baidu.com时,baidu的IP地址会命中默认网关发往192.168.50.1默认网关,经过封包送到VPN server的监听端口。
然后VPN server会把封包拆开,linux协议栈看到真实目标IP是baidu.com,那么显然该IP地址不是本机,需要forward给百度,此时需要linux的netfilters进行流量forward才能再次送给192.168.2.1家庭物理网关到达百度,这一步需要我们在服务端开启ipv4 forward特性:
sudo vim /etc/sysctl.conf
net.ipv4.ip_forward=1
net.ipv6.ip_forward=1
然后执行sysctl -p生效。
配置防火墙的IP Masquerading
请使用以下命令检查服务器的主网卡接口
1 |
$ ip addr |
例如,可以检查到网卡接口名字是eth0,则执行
1 |
$ sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE |
这里命令-A
表示添加到nat
表的POSTROUTING
链。这样就可以把VPN网络连接到Internet,并且把你的网络对外隐藏起来。这样Internet只能看到你的VPN服务器IP,但是不能看到你的VPN客户端IP,类似于你家中路由器隐藏起家庭网络。
现在可以检查一下NAT表的POSTROUTING链,可以看到目标是anywhere的源为anywhere的都执行MASQUERADE。
1 |
$ sudo iptables -t nat -L POSTROUTING |
显示输出:
1 2 3 4 5 6 7 |
Chain POSTROUTING (policy ACCEPT) target prot opt source destination MASQUERADE all -- anywhere anywhere |
为了持久化iptables规则(默认重启后就没了),所以我们需要安装一下工具:
sudo apt install iptables-persistent
然后保存一次:
sudo netfilter-persistent save
最后
更高级的特性就是配置ocserv.conf的路由下发,下发一些路由规则到VPNclient ,这样可以控制哪些网段的流量不要发给VPN隧道,可以兼顾客户端本地局域网和家庭局域网两路一起访问的需求,但是这个要求客户端本地局域网段、VPN网段、家庭局域网段三者不能冲突。
大家可以试一下ocserv.conf中的route和no-route配置项,比如我希望VPN client不要让公司网段走VPN,那么就可以加一条这样的配置(假设公司网段是172.26.201.x):
no-route = 172.26.201.0/255.255.255.0
这样VPN client侧的虚拟网卡就会判断目标IP是这个网段的就不发往VPN server了,而是走本地其他路由规则寻址。
如果你家里的网络是路由器/旁路由FQ或者绿色dns的话,那么可以令vpn server的默认网关指向fq路由器,同时让vpn client使用你家里的绿色dns,这样就可以让vpn客户端透明fq了。
比如我家里旁路由上有绿色DNS,所以我可以令ocserv.conf下发它的IP地址作为客户端使用的DNS服务器地址(家里的192.168.2.201运行着绿色DNS,然后vpn client可以通过隧道访问到它):
在ocserv.conf中指定dns服务器地址,下发给vpn client:
dns = 192.168.2.201
这样vpn client就会请求192.168.2.201进行域名解析,得到无污染IP。
理解上述原理需要对路由表、netfilters有清晰认识,否则可能比较困难,希望对你有点帮助。
智能分流
https://github.com/don-johnny/anyconnect-routes
国内IP不进行代理,参考如下开源项目:
https://github.com/CNMan/ocserv-cn-no-route
遇到的问题
1. 对于 ubuntu 22.04 系统,目前(2023.09.01) 系统自带的 ocserv 1.1.3 会在启动之后崩溃,出现如下日志:
1 2 3 4 5 6 7 8 9 10 |
$ sudo tail -f /var/log/kern.log Sep 1 11:01:39 localhost kernel: [ 62.471583] traps: ocserv-worker[9587] general protection fault ip:7fa35c1db898 sp:7ffdf42e29a0 error:0 in libc.so.6[7fa35c1db000+195000] $ ocserv --version ocserv 1.1.3 Compiled with: seccomp, tcp-wrappers, oath, radius, gssapi, PAM, PKCS#11, AnyConnect GnuTLS version: 3.7.3 (compiled with 3.7.1) |
解决此问题的方法就是安装 ocserv 1.1.6以及以后的版本,可以从源代码编译安装。这里有个简单的解决方法,就是从 ubuntu 23.04 的安装源中下载已经编译好的安装包,直接进行安装,如下:
1 2 3 4 5 |
$ wget http://cz.archive.ubuntu.com/ubuntu/pool/universe/o/ocserv/ocserv_1.1.6-2_amd64.deb $ sudo dpkg -i ocserv_1.1.6-2_amd64.deb $ sudo systemctl restart ocserv |
2. 对于腾讯云购买的海外 轻量应用服务器 需要在管理控制台配置防火墙规则,放开端口访问,否则无法进行正常的通讯访问。