`
hulianwang2014
  • 浏览: 690067 次
文章分类
社区版块
存档分类
最新评论
  • bcworld: 排版成这样,一点看的欲望都没有了
    jfinal

彻底征服Windows上OpenVPN客户端的源地址选择问题

 
阅读更多
一个问题困扰了我多年,相信也困扰了很多人多年!那就是Windows上使用OpenVPN时,通过隧道的包的源IP地址总是OpenVPN虚拟网卡网段的IP地址,由于Windows的路由选择是自动进行的,除非你在应用程序中bind一个地址,否则它选什么你用什么,配置路由时,你无法指定Linux的iproute2的src参数。因此只要是要通过TAP-Win32网卡走的包,其源地址均是TAP-Win32网卡上配置的虚拟IP地址!目前的解决方案有三种:
1.OpenVPN的2.3.0版本有NAT的配置选项:
--client-nat snat|dnat network netmask alias
但是我不敢用,怕应用程序的校验码用到了原始IP信息;
2.可以用Windows的LSP来强制bind物理网卡的IP,但是我还是觉得不够完美;
3.在服务端做SNAT。然而为了WIndows客户端做定制,也不完美;

网络的问题就要网络自己解决,不要依赖太多上层的东西!虽然Windows网络不给力,但是想降伏它还是有办法的,大不了就搞NDIS啊!客户端自己的事情自己解决,别麻烦服务端,为了解决客户端的问题,在服务端加配置算什么事啊!

写在前面

这篇文章描述了Windows选择源IP地址的详细过程,另外微软的文档中也有介绍,这里就不多说了。总之,不管是强主机模式还是弱主机模式,都是自动为你选择的,你无法通过配置来干预。
就是这个自动选择的问题让OpenVPN的虚拟子网地址泄露到了真实的子网中,如果容忍这个的发生,你不得不针对OpenVPN服务端后面的防火墙等网络设备进行修改,比如运行虚拟子网的IP地址通过等,你不得不对应用程序的IP地址审计策略进行修改;如果不容忍这个的发生,那么代价就是通信可能会变成单向发起的...不一而足。
为了解决这个问题,花过不少时间,但是都不彻底。如今,我想一劳永逸解决它,彻底征服它。好在当前没有什么急迫的事情,公司和家里都没有,正是征服它的好机会,于是,小小爸爸来了,熬过了一个夜晚,半个白天,有了结果。

TAP-Win32网卡设置和物理网卡同网段的第二个IP的方案

1.首先在TAP-Win32网卡上配置一个和物理网卡同网段的第二个IP地址
我的物理网卡地址是192.168.40.34/24,于是乎我就配置给TAP-Win32网卡一个192.168.40.35/24,TAP-Win32网卡上的另外一个地址是OpenVPN服务端推送下来的172.16.0.30/16,这样TAP-Win32网卡上就有了两个IP地址。注意,同网段的第二个IP可以随意选择,不会冲突,因为只要不从该TAP-Win32网卡发包,是不会有到该IP的arp的,反之,要从TAP-Win32网卡发包,下面的步骤可以保证该IP仅仅会作为数据包的源IP。它的作用也仅仅在此。
2.在TAP-Win32网卡上盗用原始默认网关的arp映射
默认网关的作用其实就是获取一个MAC地址用于封装以太帧的目标MAC地址,获得MAC地址后,网关的使命就完成了。Windows比较好的地方就是可以指定在哪个网卡上建立arp映射,虽然原先就有到默认网关192.168.40.254的arp映射,但是那条映射是建立在物理网卡上的,现在我建立一条同样到192.168.40.254的arp映射在TAP-Win32网卡上,只不过映射的MAC地址不是真正40.254的MAC地址,而是OpenVPN服务端tap0网卡的MAC地址:
arp -s 192.168.40.254 00-ff-c4-d3-2e-da 172.16.0.30
注意,上述命令的最后一个参数是TAP-Win32网卡的OpenVPN服务端推下来的IP地址,有了它,该条映射就会建立在TAP-Win32网卡上。
3.将OpenVPN服务端推下来的路由的gateway全部改在TAP-Win32网卡的Fake出来的192.168.40.254上:
route add a.b.c.d mask A.B.C.D 192.168.40.254 IF 0x3
注意上述命令的最后一个参数0x3,它是TAP-Win32网卡的Index ID,说明该条路由相关的IP是配置在TAP-Win32网卡的和物理网卡同网段的IP地址。
4.测试
在OpenVPN服务端的tap0上抓取到达a.b.c.d的包,发现源IP是192.168.40.35,也就是TAP-Win32网卡上的和物理网段同网段的第二个IP,不是OpenVPN服务端推送的虚拟IP地址172.16.0.30
缺点:
虽然达到了效果,但是不得不占用两个同网段的IP,即使不会冲突,也会很不爽,因为在VPN的尽头,如果有源IP审计的话,管理上就会很麻烦,你不得不把自己的主用IP设置在TAP-Win32网卡上,而物理网卡上设置一个随意的不冲突的IP,找到这个IP本身就是一个困难的事。于是乎,下一个想法就出来了,能不能在TAP-Win32网卡上设置一个和物理网卡相同的IP呢?

物理网卡和TAP-Win32网卡共享物理IP方案

这个小节会比较短,因为这个方案失败了,唯一的内容可能就是抱怨了。
有想法是好事,但是实现想法的过程往往是痛苦的,因为总是有很多你想不到的拦路虎,特别是在WIndows上折腾网络,更是如此!我在TAP-Win32网卡上配置第二个IP地址,即我的物理网卡的IP地址192.168.40.34时,直接弹出警告:
---------------------------
Microsoft TCP/IP
---------------------------
为这个网卡输入的 IP 地址 192.168.40.34 已被指派给这个计算机上的另一个网卡 MAC 桥微型端口。如果同样的地址被指派给两个网卡,并且两个网卡都在使用中,只有一个会使用这个地址。这可能会导致不正确的系统配置。

要从“高级”对话框中的 IP 地址列表中为该网卡输入一个不同的 IP 地址吗?
---------------------------
是(Y) 否(N)
---------------------------

我知道这是一个错误的配置,因为会冲突,我比谁都明白,但是我是故意这么搞的,因为我有办法解决冲突,于是我选择了“否”,确定后,弹出:
---------------------------
Microsoft TCP/IP
---------------------------
刚配置的静态 IP 地址已在网络上使用。请重新配置一个不同的 IP 地址。
---------------------------
确定
---------------------------

确定后,我发现这个IP已经加入TAP-Win32网卡了,然而没有生效,Windows根本不给我犯错误的机会...我们看下Linux上发生的情况:
root@Debian60b1-AMD64-DEV:~# ifconfig eth0 1.1.1.1/24;echo $?
0
root@Debian60b1-AMD64-DEV:~# ifconfig eth1 1.1.1.1/24;echo $?
0

...无语了!
Windows之所以不给你犯错的机会,是因为设计者默认你没有解决错误的能力,因此就阻止你犯错误,而UNIX系列的系统开放了巨大的权限给root用户,因为设计者认为如果你犯了错误,你一定有一个这么做的理由!Mac OS则介于二者之间,对于UNIX用户,它提供了一个强大的BSD命令行,对于普通用户,有很绚丽的UI,它比Windows更加严格,设置将“添加第二个IP”这种行为都禁止了,而暴露给用户“位置”的概念。
既然不让在UI上配,我觉得底层API肯定有办法强制生效,于是打开了久违的VS2005(太老的版本!),MSDN的例子各种尝试,熟悉Win32 API的同事各种请教,无果!
算了,既然不让我成功,又何必勉强呢?于是第三种想法出生,既然不让我在两个网卡配置同一个IP地址,那么我把两块网卡合并成一个逻辑网卡总可以了吧,于是上网搜各种方案,各种bonding不支持,要下载第三方软件...Windows自带的就一个:MAC桥微型端口!那么,就是它了。

物理网卡和TAP-Win32网卡的桥接方案

该方案是逐步思考的结果,因此方案过程中亦包含很多对Windows网络的抱怨。

1.连接OpenVPN服务器成功

为了确保桥接过程中隧道断开,要把keepalive时间设置足够长久,我给了桥接一分钟时间。

2.桥接TAP-Win32网卡与物理网卡

按下Ctrl键,左键点选TAP-Win32网卡和物理网卡“本地连接”;右键点选“桥接”。于是就生成了一个网桥。

3.配置网桥的IP地址

赶紧,用最快的速度,在keepalive到期前将IP地址配置好,其实也简单,就是将原来的“本地连接”的IP地址和OpenVPN服务端推送的IP地址统一加到网桥上,注意“本地连接”的默认网关以及DNS也要加到网桥上。接下来的事情够我喝一壶的了!

4.痛苦的历程

地址配置好了,问题又来了,如今怎么建立arp映射?不能再用物理网卡的原始默认网关了,因为此时TAP-Win32网卡和物理网卡bridge成了一个,不再分离,一旦盗取重定义了原始默认网关的arp映射,将导致本机应用全部断网,那么显而易见的就是选一个没人用的本网段的IP了,这个有点难度。
4.1.选取要建立arp映射的IP地址
其实就是选择一个将流量导入TAP-Win32网卡的网关,该地址的作用就是建立一条arp映射,然后让数据包通过网桥发出时,会选择物理IP地址作为源地址。本网段找个没人用的?难啊,涉及到管理问题,那么怎么办?还算比较懂网络的我此时有了办法,那就是网桥上设置的原始物理网卡上的IP地址掩码往后退1位,即缩小1位,这样原来网段的广播地址和网络地址不就可以用了吗?以下是计算过程:
IP地址:addr;原始子网掩码:prefix(mask的prefix形式)
a.设置新的子网掩码为prefix-1
b.如果addr的第prefix位为0,则所求的网关地址为原始网段的广播地址(因为新的广播地址要求其prefix位为1)
c.如果addr的第prefix位为1,则所求的网关地址为原始网段的网络地址(因为新的网络地址要求其prefix位为0)
d.如果addr的第prefix位为0,还可以使用将addr的prefix为设置为1后,后面全0的地址作为所求的网关地址
e.由于prefix缩小了1位,那么代价就是增加一条指向原始默认网关的路由,该路由覆盖和addr的prefix位不同的原始predfix路由

结果呢?Windows又设障了!我明明的掩码是255.255.254.0,可是在route print的时候还是会有:
192.168.40.255 255.255.255.255 192.168.40.34 192.168.40.34 20
按照我将prefix设置成23,它本应该是:
192.168.41.255 255.255.255.255 192.168.40.34 192.168.40.34 20
才对啊!可是不知Windows卖的什么药,开始以为是cache,可是禁用/启用网卡依然如故,这样我也就无法使用以上算法选择的网关了。网上也有人有类似疑问,既然解决不了,我也就不追了,在Windows上哪怕一个小问题一追就是好几天头大,因为你很难去debug它,很难去看个究竟!绕开它总是好的。
4.2.通过路由来解决
我怀疑WIndows的IP地址是有类和无类的混合,但是找不到什么配置方法去改变这种行为,注册表中也许有,但是我怕。于是我索性将prefix一下子退8位,即改成16位,于是路由表中有以下1项:
#链路路由
192.168.0.0 255.255.0.0 192.168.40.34 192.168.40.34 20
也就是说它将192.168.0.0/16全部当成链路直连路由了,而事实上只有192.168.40.0/24才是,于是需要将非40网段的全部指向原始的默认网关。天啊,Windows又很难做Policy Routing,不支持反掩码,这样添加多少条路由啊!!于是我只能靠Metric来覆盖掉上述的路由了,也就是说增加两条路由:
#增加真实的链路直连路由
route add 192.168.40.0 mask 255.255.255.0 192.168.40.34
#覆盖掉假的链路直连路由
route add 192.168.0.0 mask 255.255.0.0 192.168.40.254 metric 1
这样就OK了,为了防止人家自动生成的Metric就比你的小,最好关掉网桥卡TCP/IP高级属性中的“自动跃点计数”,然后手工添一个比较大的值。至于说选哪个做TAP-Win32的网关,在192.168.0.0/16中随意找一个即可,我还是找了那个原始的网络地址,即192.168.40.0。然后添加一条arp映射,事情就完了:
arp -s 192.168.40.0 00-ff-c4-d3-2e-da
MAC地址是OpenVPN服务端的tap0的MAC地址。测试,发包,通过虚拟网卡,在接收端看来,源IP是192.168.40.34这个真实地址,而不再是172.16.0.30这个VPN虚拟网段的地址了。

4.3.加密流量的泄漏问题
由于使用了网桥,而网桥在MAC无缓存的情况下默认是在所有接口广播包的,因此会有一些数据包同时送往物理网卡以及TAP-Win32虚拟网卡,造成本应该加密的流量泄露到物理网卡上,但是由于封装的目标MAC地址是OpenVPN服务端的tap0的MAC,因此只在本网段可能会泄漏,不会穿越原始默认网关。
事实上,想解决这个问题很容易,只需要设置一条永不过期的MAC/端口映射或者配置防火墙就可以了,然则这不是我的强项,我也不想再折腾Windows网络了,HOLD不住啊!另外这个问题也可以通过OpenVPN服务端来帮忙解决,在Windows客户端接入初始的holdon时间内,不断通过tap0往对应客户端发包,这样Windows上的桥接口就会记录下MAC/端口的映射,相当于学习到了MAC地址。然而如果Windows的网桥不是学习型的,那就悲哀了。

5.悲哀的结果

貌似Windows的网桥只能手工配置啊!这可怎么和OpenVPN的plugin结合啊,我本来想写一个脚本,在Windows客户端的up脚本中来创建网桥,配置IP,设置路由,添加arp映射的,然而Windows的脚本功能实在太弱,就准备写plugin,直接调Windows的API来做,可是如果只能通过手工的方式来搞的话,那就悲剧了,也许鼠标精灵之类的玩意儿要派上用场了吧...

带条件的缩小1位prefix的方案

怎么?又回来了?上一个方案不是说这个缩小1位的方案不可行吗?是的,上一个方案时是不可行,但是解决问题的过程是一个不断思考的过程,现在它真的就可行了,虽然这种可行是带条件的,幸运的是,50%的概率,我们可以符合这个条件!
Windows的IP地址和路由查找都是Classful的的,并不是VLSM的,所以上一个方案中明明把prefix缩小了1位,其广播地址却还是192.168.40.255,因为这是一个C类地址。但是即便是Classful地址,其网络ID也还是很明确的,即:
192.168.40.0 255.255.254.0 192.168.40.34 192.168.40.34 20
注意到下一跳算法中的:
d.如果addr的第prefix位为0,还可以使用将addr的prefix为设置为1后,后面全0的地址作为所求的网关地址
恰好对于192.168.40.34而言,其prefix即第24位就是0,因此按上述算法求得的地址是192.168.41.0,它显然不属于原有的物理网卡上的IP网段,但它却属于prefix后退1位后的该IP地址网段,可以作为下一跳使用:
route add a.b.c.d mask A.B.C.D 192.168.41.0
arp -s 192.168.41.0 00-ff-c4-d3-2e-da
# 补偿prefix后退1位的代价
route add 192.168.41.0 mask 255.255.255.0 192.168.40.254

就此结束,不用再去设置什么Metric更小的路由了,也不用再区分什么真的或者假的链路直连路由了。

思考的过程

其实思考的过程就是一个原创的过程,即便你用到了很多“巨人”的资料,然而没有人的思考过程和你是一样的。
1.当我知道了Windows选择源IP的逻辑时,我就决定在TAP-Win32网卡上添加一个和物理网卡同网段的IP,接下来就是如何把它选中;
2.虽然我期望把它配置成primary IP而不是secondary IP让它优先级更高一些,但是最终起决定性作用的还是下一跳地址是什么;
3.我知道所谓的下一跳的唯一重要作用就是获得一个被封装成目标MAC地址的MAC地址而已,所以它到底是什么并不重要;
4.于是我就伪装了一个和物理网卡IP同网段的IP地址作为下一跳,然后添加一条静态的arp映射到OpenVPN服务端的tap0网卡的MAC地址;
5.想法到此基本结束,接下来的实现过程一直都是上述的想法,但是为何最终却扯到网桥了呢?虽然基本思想不变,但是实现过程其实是一个不断试错并迂回前进的过程。错了,就要微调,微调到最后,其实就是成功;
6.第一个障碍是发现使用同网段的两个IP不好,我就想用一个,为了这个优化,逐步把我带入网桥。其实在此之前,都有一些小的障碍,比如如何针对网卡添加arp映射,如何配置路由时指定网卡等;
7.到了网桥之后,选取通过TAP-Win32网卡数据包的下一跳成了问题,为了最少冲突和最少费解,我决定使用原始物理网卡网卡的全1广播地址或者全0网络地址,遂决定prefix缩小1位,按照标准算法求得下一跳;
8.遂碰到了Windows路由表的奇怪问题,有类无类混淆,广播路由计算错误,我不是bug reporter,遂绕过,你爱怎么着我依你还不行吗?遂将prefix回退8位,这下终于和你要求的8,16,24对齐了;
9.prefix后退了8位,相当于一个大的汇总,链路直连路由覆盖了原本根本就不是直连网段的网络,于是需要刨开这些,但是又不能死磕,怎么办?
10.按照最长掩码匹配,先添加一条24位掩码的链路直连路由,它是真实的链路直连路由;
11.将原来的扩大了的16位掩码的假的链路直连路由用一条指向原始默认网关192.168.40.254的网段路由覆盖,但是假的直连路由又删不掉,怎么办?
12.你爱留着就留着,我用Metric更小的路由覆盖掉你!
13.将原始网段的网络地址192.168.40.0作为通过TAP-Win32网卡数据包的下一跳,然后添加一条该下一跳到OpenVPN服务端tap0网卡MAC地址的arp映射。
14.写下本文,备忘!

留下的问题

基本上问题都解决了,留下来的仅仅是如何和OpenVPN结合的问题了,也就是如何把上述的那一大堆手工操作写入到plugin(我已经彻底放弃脚本了),再者就是TAP-Win32的初始化问题,如果它不从网桥断开,它还能被初始化吗?即便不能也不难,修改OpenVPN Windows客户端的代码便是了,也就是ifdef WIN32那些段落。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics