1. 问题描述

最近一段时间在家里办公,所以需要在家访问公司的服务器;另外还要查资料,科学上网问google(虽然deepseek已经解决了绝大多数的问题,但很难对后端联调时候的报错给出正确的解答)。目前的解决办法是,macos笔记本上,安装两个vpn:一个openvpn访问公司网络,一个国外付费的xxx_vpn(此处略去名称)。

在同一时刻,我只能连接openvpn或者xxx_vpn,这意味着,我使用vscode在公司的服务器上远程写代码,写着写着,需要断开openvpn,然后连接xxx_vpn去google上查资料;查完了再回过头来断开xxx_vpn,继续连接openvpn,然后重新连接vscode在公司服务器上写代码。一来二回的真的让人难受。

2. 解决方案

之前有想过再加一个无线网卡,两个网卡各自走各自的网络,但目前版本的MacOS却会要求对网卡(包括以太网卡)的优先级进行排序,也就是说即使有了两个网卡,要让哪个目的IP走哪个网络还得仔细配置。而且日常外出去星巴克写代码还要多带一个无线网卡。

问题来了:如果两张网卡可以做到的事儿,我们是否可以直接使用MacbookPro自带的无线网卡也能做到?

最终尝试下来的答案是:可以

总的思路就是:“俩vpn互相谦让,然后配置好主动路由”,这句话听起来挺抽象。下面我们慢慢给大家介绍。

在正式介绍之前我们先回顾一下预备知识:我们先来看看一个vpn的建立大概过程是怎么样的。

2.1 vpn网络的建立过程

所以vpn,其实就是virtual private network的简称。假设我们有两台机器A和B,A是办公环境下你的一个台式机;B是你们家里你打游戏的外星人笔记本;显然B不可能通过网络直接访问A(否则公司网络毫无安全性可言)。我们希望建立一个虚拟的局域网,使得A和B都在同一个局域网,例如在这个虚拟局域网中:让A的IP为10.8.0.2, B的IP为10.8.0.3; 这样A和B之间就能直接通信了;可问题是A和B无法直接彼此联系并建立这个虚拟网络,于是需要一个A和B都能访问到的公网的机器C来提供帮助,使得A和C连接后,在一个虚拟局域网W中;然后B也和C通过公网连接,然后成为这个虚拟局域网的另外一个成员。于是,A和B之间就能通过10.8.0.*这个网段来互相通信了。

其次我们再来重新梳理一下我们的网络访问需求,看看是否可以明确让两个vpn各自的服务边界。

2.2 我们的网络访问需求

虽然背景介绍中,我们已经简单介绍了两个vpn各自的目的。我们这里再细化一下两个vpn各自的服务目标。首先openvpn用来访问公司内网,就意味着只有一个或者几个固定的IP地址/网段,需要让openvpn来进行处理,而像访问搜索引擎、Stack Overflow等其他宽泛的网络访问我们都使用xxx_vpn来处理。这么一看,问题就变简单了,对于两个vpn来说,他们只要负责好各自的部分,同时规定好各自不需要处理的部分就可以。

2.3 vpn配置的设置

通常来说,vpn的配置文件,都会提供一些规则来做以下两件事儿:

  • 允许特定的IP,网段被这个vpn服务;
  • 排除特定的IP,网段,使得这些IP/网段不被服务;

具体哪种vpn提供了哪种配置需要视具体情况而定。

2.3.1 openvpn的路由设置

openvpn通过提供路由配置来满足这两种要求。例如你可以在你的openvpn客户端配置文件(*.ovpn)文件中提供以下信息:

# 保证不会从vpn服务器上拉下来我们所不知道的其他路由设置
route-nopull
# 将访问目的地址1.2.3.4的请求发送到下一站 <net_gateway>
route 1.2.3.4 255.255.255.255 <net_gateway>
# 将访问目的地址网段 1.2.3.*的请求发送到下一站 <vpn_gateway>
route 1.2.3.0 255.255.255.0 <vpn_gateway>

这里的<net_gateway>通常就是,我们在无vpn连接的时候所接入的局域网的网关,例如:192.168.22.1等;而<vpn_gateway>则是我们连接vpn之后,在这个虚拟网络之下,我们的本地机器的IP;例如:10.8.0.2等;

在你的*.ovpn文件中加入上面的这种路由配置,你就可以选择哪些IP请求可以从这个vpn服务走;哪些IP请求可以依然走你所在的物理局域网的网关。

我们再来看一下一个真实的openvpn的配置,这个名叫config.ovpn文件的配置如下

# config.ovpn
client
dev tun
proto udp
remote <your_remote_public_host> <port>
resolv-retry infinite
route-nopull
route 10.8.0.0 255.255.255.0
nobind
persist-key
persist-tun
remote-cert-tls server
auth SHA512
ignore-unknown-option block-outside-dns
verb 3

在这个文件中<your_remote_public_host>就是我们在2.1节中提到的公网机器C的IP,<port>是你的openvpn服务器对外提供的访问端口。(这两块儿的信息通常都是直接由程序生成好的,不需要你手动调整)而接着的第7和第8两行:

route-nopull
route 10.8.0.0 255.255.255.0

则是唯一规定了只允许10.8.0.*这个网段的访问,可以走openvpn。

看完了openvpn的配置方法,我们再看看科学上网的xxx_vpn的配置。

2.3.2 xxx_vpn的路由设置

原理都是一样的,只是我们这里使用xxx_vpn通过图形化的界面,为我们提供了openvpn类似的路由配置方法。例如哪些目的IP的访问不走xxx_vpn

还提供了,只有哪些IP的访问走xxx_vpn,也就上面的选项从Exclude these sites 变成了Tunnel only these sites,其本质也都是对目的IP的路由做调整;对于要使用xxx_vpn访问google等网站,我们显然要配置Exclude these sites,从而将我们计划通过openvpn访问的一些公司内网IP给屏蔽掉。

2.4 两个vpn同时打开,“打架“的原因和解决方案

网络上对于讨论两个不同的vpn,哪个先打开,哪个后打开才能顺利上网,也有一些问答。但本质其实都是路由的问题没有弄好。弄好了两个vpn的路由,应该就不存在谁先打开谁后打开导致后果不一样的现象了。

我们在2.1节中提到了vpn网络建立然后通过vpn访问特定局域网,至少涉及到两个IP的访问,一个是vpn网络建立的时候要访问的公网机器C的IP,另外一个是vpn网络建立成功之后,vpn内的目标机器B的IP的访问。

所以,在我们的场景下,我们要确保:

  • 第一、无论是openvpn还是xxx_vpn都首先能够连接到他们各自的公网服务器;
  • 第二、只让走公司局域网的流量走openvpn;而其他网络访问走xxx_vpn;

但由于xxx_vpn是是封装好的客户端,我们比较懒,也暂时不知道它的公网服务器IP是什么(以下简称Q),所以我们没有办法让openvpn的配置中屏蔽这个Q。所以在要启动这两个vpn的时候:

我们需要先启动xxx_vpn;然后我们不希望公司局域网的流量(例如访问公司局域网IP:Z)走xxx_vpn,同时希望openvpn能够连接到其自己的公网openvpn server(以下简称P),所以我们需要将xxx_vpn的Exclude these sites中添加上Z和P;以保证openvpn的客户端可以顺利连接到它的公网服务器P;

启动完xxx_vpn之后,我们再启动openvpn的客户端(连接到P),然后为了能够让面向公司局域网的地址(例如M)访问(例如ssh等)必须走openvpn,我们需要在其客户端配置中加入只允许M走openvpn的路由。

如果,你按照上面的做法配置两个vpn的客户端了,发现两个vpn都启动之后,公司机器M(例如:10.8.0.4)无法访问到,你还可以使用:

#Macos上的命令,linux上可能有所不同
netstat -nr

来查看所有的路由情况,然后显式地通过:

sudo route -n add 10.8.0.4  10.8.0.2

将一10.8.0.4的下一跳设置为你的本地机器在openvpn设定的局域网ip地址:10.8.0.2

3. 总结

以上内容更多的像是我个人的笔记,目前看起来配上一个图可能解释的更清楚,后续慢慢补上;总之懂一些网络知识,有时候能给自己的工作带来非常多的便利。有类似困扰的同学也欢迎留言、讨论交流。

最后,码字不易,请大家点赞、收藏、关注;

参考文献

  • https://stackoverflow.com/questions/70224509/exclude-ip-from-openvpn-route
  • https://joeywang.github.io/posts/how-to-configure-openvpn-to-allow-access-to-specific-ips-only/
  • https://discussions.apple.com/thread/8524737