使用wfp网络驱动实现局域网内设备代理上网


这里所说的局域网内的所有设备通过代理上网,并不是在每台设备上安装某个代理软件然后再通过代理服务端来上网。
而是所有这些设备的网关IP设置到某个主机上,这样所有设备的网络数据都会转发到这个主机上。
而这个主机的软件再通过代理服务端转发所有这些设备的网络数据。


这个主机与NAT路由器很像,但是又与NAT路由器有不同:
一般的NAT路由器是外网网卡直接把局域网内所有设备的网络数据发送出去,通常需要两块网卡,一块外网侧,一块内网侧。
而这个作为代理的主机,则是把局域网内的所有设备的网络数据全部转发到另外一个作为代理的服务端机器上面,
然后这个代理服务端的机器才是作为真正实现NAT路由器转发网络数据。
通常这个代理主机并不需要两块以上的网卡,只需要能与代理服务端正常连接通讯就可以了。

这么解释可能也不大容易理解,因此最好以实例来说明。
我们以游戏加速为例
假设我们在公司的或者家庭的局域网内,有一款很不错的Xbox或PS4游戏机,
但是奈何游戏的服务端网络通讯非常糟糕,游戏服务器在国外或者距离我们的城市太过遥远,数据响应很糟糕。
于是我们想到了使用游戏加速程序,加速程序的代理服务端机器在我们城市的机房中,速度自然很快,
同时代理服务端机器通过专线连接到真正的游戏服务器,速度自然也很快。
于是我们再在客户端安装游戏加速的客户端软件,这样就能顺利加速了。但是这里有个问题:
Xbox,PS4这些是专用的游戏机,不像PC那样可以随意安装游戏加速软件。
因此,该如何实现游戏机的游戏加速呢?
无法在Xbox或PS4上安装游戏加速程序,那就直接在网络路由器上打主意。我们把游戏机的网关IP直接设置到我们的代理主机上,
这样游戏机的所有网络请求全都转发到我们的代理主机上了,代理主机上的软件再把数据包转发到代理服务器上,
于是直接通过我们的代理主机就能实现Xbox,PS4等游戏机游戏加速了。
而且只要是在同一个局域网内,不管是什么网络设备(不管是手机,其他嵌入式设备,还是其他PC),只要设置他们的网关IP为代理主机。
所有的网络通讯都会自动转发到代理主机上。
这样大家都省事,也用不着在所有这些设备上专门安装游戏加速程序了。
不过这种做法也有个坏处:因为设置网关IP,是全局性的,所有程序的网络通讯都会走代理,这无疑增加了代理服务器的压力。
而且有些程序并不需要通过代理加速。

一般的游戏加速程序,或者V-P-N程序,都是在本机上安装相应的代理客户端软件,
然后把本机的应用程序的网络请求数据包转发到代理服务端上,从而实现网络数据加速的目的。
游戏加速程序的实现原理其实和一般的V-P-N程序原理基本一样。
中国境内对V-P-N限制比较严格,因此须遵守中国相关法律。
本文描述的内容,也比较容易迁移到普通V-P-N程序的实现上。
但是就比如菜刀是用来切菜的,如果非要用来行凶,谁也拦不住。

我们在实现游戏加速程序的时候,为了尽量避免被防火墙拦截,通常并不使用一些标准的代理协议,
比如IPSec,IKEV2, L2TP等,因为这些协议非常容易被防火墙识别,并且拦截。
因此,通常都是自定义的网络通讯协议,并且常常会封装到HTTPS/HTTP,FTP,DNS等这些防火墙不能拦截的协议中。

要封装实现自定义的通讯协议,拦截和处理IP数据包是最通用的做法。
我们知道TCP/IP有四层协议:
1,应用层,
2,TCP,UDP,ICMP等传输层
3,IP网际层
4,链路层
这里重点关注第三层:IP层。其实我们在其他层也能实现代理加速。
比如windows平台的LSP,就是在应用层拦截数据。再比如SOCKS5也是应用层代理协议。
但是应用层和传输层对网络数据的处理,都会存在一定得局限性。
链路层会牵涉到与网卡硬件相关的信息,比如MAC地址等。
只有在IP层,各种网络通讯数据都被打散成一个一个的IP数据包,且与具体的硬件无关。

如何拦截和处理操作系统中各个应用程序的网络IP数据包,各个操作系统有不同的做法,
通常都会牵涉到底层的驱动处理。除非是像iOS或Android专门提供了相关的应用层组件来拦截IP数据包。
windows中,使用WFP驱动来拦截处理IP数据包,
当然使用NDIS协议驱动或NDISFilter驱动也能处理IP数据包,
不过NDIS驱动是工作在链路层中,因此NDIS驱动中我们还得多进行一步操作,去掉链路层的信息。
在linux系统中,我们可以在应用层直接使用 PF_PACKET协议,
通过socket直接获取链路层数据包,然后再过滤掉链路层信息,留下IP数据包。
也可以在linux内核层,实现netfilter驱动拦截处理IP数据包。
反正方法很多,都能截获和处理IP数据包。

iOS和Android系统中,系统在应用层提供了专门的组件来截获和处理IP数据包。
如果没有专门的组件,我们也无法在iOS或Android系统中拦截处理IP数据包。
因为iOS和Android本身的限制,我们无法直接操作系统驱动层。
Android使用VpnService组件,来获取到某个app的IP数据包,然后我们的代理加速App程序中截获到IP数据包,
再封装一下发送到代理服务器上,代理服务器回复对应数据,然后我们的代理加速App程序再解封成IP数据包,
再通过VpnService传递给Android内核,Android内核再让这个IP数据包进入正常的IP通讯栈。
iOS系统中对应的组件则是 NEAppProxyProvider 和 NEPacketTunnelProvider,具体使用可查阅apple开发文档:
https://developer.apple.com/documentation/networkextension/app_proxy_provider
https://developer.apple.com/documentation/networkextension/packet_tunnel_provider

其实iOS和Android中对IP数据包的截获和处理大致都差不多,
当某个app程序的IP数据包进入真实网卡时候,如果确定需要拦截这个IP数据包,则会被转发到一个虚拟网卡中,
虚拟网卡驱动再把这个Ip数据包发送到应用层,
于是应用层的组件(Android的是VpnService,iOS对应的是 NEPacketTunnelProvider)就会接收到这个IP数据包,
于是我们的VPN的App程序再把数据包封装转发到代理服务器上。
接着从代理服务器接收到IP数据包,通过Android的VpnService 或iOS的NEPacketTunnelProvider组件把他发送到虚拟网卡中。
于是虚拟网卡把这个IP数据包投递到正常IP通讯栈中进行正常的传输处理。
这个是不是与我在讲述 WFP驱动的时候,把IP数据包转发到应用层来处理的做法很像!异曲同工之妙。
不同的是windows中我们必须非常麻烦的自己处理整套流程,iOS和Android都是帮我们封装好了的。
Windows7以上使用WFP驱动框架实现IP数据包截取(一) - 信易达 - 博客园 (cnblogs.com)
Windows7以上使用WFP驱动框架实现IP数据包截取(二) - 信易达 - 博客园 (cnblogs.com)

那如何实现主机代理,让局域网内的设备都能通过这个主机代理上网呢?
这个主机可以是windows系统,可以是linux系统,也可以是嵌入式linux(比如openwrt等路由器)。
但是排除iOS和Android系统,因为这两个系统并没有提供应用层组件来接收来自局域网的IP网络数据,
由于手机系统的限制(除非是特殊定制机器或root了的机器),我们也无法深入到iOS和Android的驱动层去拦截IP数据包。

正如上面描述的,linux系统中可以使用PF_PACKET的socket来获取IP数据包,也可以使用netfilter在驱动层获取IP数据包。
windows系统可以使用WFP驱动,或者NDIS驱动来获取IP数据包。
在这里也并不需要拦截IP数据包,所谓拦截,就是不再继续把IP数据包朝上层传输。
只需要获取到从局域网其他设备发来的IP数据包就可以,不用关心和担心是否这个IP数据包是否会再次被传递到上层会造成其他影响。
因此这里才可以直接使用NDIS协议驱动。
这个与实现NAT有点不同,NAT的外侧网卡必须做到拦截处理IP数据包,
否则会被上层通讯协议栈发个RESET什么的数据包来扰乱NAT处理流程。
当然使用NDISfilter驱动拦截不必要的IP数据包继续上传会更好,因为这样可以节省资源占用。
毕竟使用NDIS驱动需要过滤链路层信息,因此这里还是建议使用WFP驱动,同时WFP驱动也能做到拦截,不继续传递。
使用WFP驱动,也能同时一起处理本机的应用程序的代理加速。

如下连接:
NDIS协议驱动应用之(另类的NAT路由程序开发) - 信易达 - 博客园 (cnblogs.com)
与本文需要实现的主机代理有点类同,但是上面链接里边实现的把局域网的IP数据包转成外网一侧的socket通讯,却是要复杂。
复杂在转化,尤其是对TCP的处理,我记得当时是实现了一个非常简单TCP的超时重传和组包的算法,
在局域网通讯良好的情况下,还能使用。但是在局域网丢包严重,无线信号差等情况下,TCP传输就会时常断线。
这主要是对TCP组包算法处理得太简单的原因。
好在本文我们只需要关心IP数据包如何传输就行了,不需要关心复杂的TCP组包算法。

我们可以直接在驱动内部处理数据包,然后再驱动内部封装成某个协议格式的数据包,
然后再在驱动里把数据包发送到代理服务器上。也就是整个过程,都可以在驱动内部完成。
但是这种做法对于要封装成复杂的通讯格式非常不利,比如我们想把IP数据包封装成 HTTPS,那就得在驱动里实现SSL加密算法。
我不清楚各个系统的驱动内核是否都提供了SSL接口函数,但是为了通用性,最好的办法是把OpenSSL移植到驱动中,
这。。。我可没移植过。
于是把IP数据包发送到应用层,然后再来进行各种封包,可能会更好。
但是正如以前的文章中阐述关于如何把windows中的虚拟网卡驱动,NDIS驱动等数据包发送到应用层来处理,都有一个问题:效率。
如果处理不好,效率会很低。效率低的时候,整个的传输带宽可能还不及千兆网卡的25%。
但是我们做游戏加速器,也并不是为了把传输数据包占满整个网卡带宽。
也就是根据具体情况来选择不同的做法,
为了能做各种协议格式的封装,同时对海量传输数据没啥要求,采用传输到应用层也是一个不错的选择。

首先抓取数据包
不管是linux系统,还是windows系统的NDIS还是WFP驱动,
我们经过漫长的各种处理之后,在应用层最终形成一个简单而又适用的专门截取IP数据包的接口函数集合:

void* iplayer_open ( .... /*可能的其他参数*/
void (*recv_ip_packet_callback)(char* ip_packet_data, int data_len, void* param),
void* param);
void iplayer_close (void* handle);
int iplayer_write (void* handle, char* ip_packet_data, int datalen);

iplayer_open是打开对应的驱动,返回void*类型的 handle,提供给下面两个函数使用。
recv_ip_packet_callback是接收到IP数据包的回调函数,已经滤除了发给本机的IP数据包,
剩下的就是局域网内其他设备发给本机进行中转传输的IP数据包。
iplayer_close关闭驱动。
iplayer_write是从代理服务器接收到回复的IP数据包之后,传输给驱动的接口函数。

其实做到了这一步,我们在代理主机客户端做的工作基本完成了一大半,剩下的就是如何封装成功各种协议,然后发给代理服务器了
比如下面的伪代码,在回调函数中:

static void recv_ip_packet_callback(char* ip_packet_data, int data_len, void* param)
{
void* https = pack_https_packet(ip_packet_data, data_len); ///封装成 https协议
post_proxy_server(https); 发送到代理服务器
}

然后开启一个线程,接收服务端的数据:

void* thread(void*)
{
while(1){
void* https = recv_proxy(); ///从代理服务器接收封装成https的数据包
unpack_ip_packet(https, ip_packet, &data_len ); 把https解封成IP数据包
iplayer_write(handle, ip_packet, data_len); IP数据包写到驱动中。
}
}

以上,客户端就完成了,然后就是如何处理代理服务器端的事情了。
其实代理服务器端也并不是十分复杂,这里采用linux作为代理服务器端,因为linux系统有现成虚拟网卡驱动和现成NAT路由程序。
用不着再我们再去开发这两样东西。
首先在linux中创建好 tap虚拟网卡驱动,然后通过iptables命令设置 NAT路由,当然tap虚拟网卡是内网一侧,linux上真实网卡是外网一侧。
然后就是开发一个应用层程序,接收代理客户端发来的封装成https的数据包,再解封成IP数据包,然后发送给tap虚拟网卡驱动。
接下来的事情就全都是linux内核处理的事情,接着就是等待tap虚拟网卡的回应的IP数据包,然后再封装成https发给代理客户端。
于是整个过程就如此跑通了,原理够简单吧。

posted @ 2022-11-04 15:29  信易达  阅读(1189)  评论(0编辑  收藏  举报