各种VPN客户端实现都离不开流量拦截与转发,那么各个客户端如何拦截流量,以及转发给指定的安全通道就成为了各个客户端所面临的重要问题。首先看下图:
启动VPN一般流程为建立虚拟网卡,配置虚拟网卡IP、MTU、修改路由表,配置系统DNS,之后操作虚拟网卡文件描述符,拦截用户指定的流量进行转发。
先说移动端:
//////////////////////////////////Android//////////////////////////////////////
Android 客户端流量拦截,主要依赖VpnService类服务拦截流量,其本质是建立了一个虚拟网卡”/dev/tun“文件,可在启动网卡建立连接前设置路由表,分包规则等。
Intent intent = VpnService.prepare(this);
StartActivityForResult(intent,0) //启动VPN申请权限
VpnService.Builder builder = new VpnService.Builder();
builder.addAddress(VPN_ADDRESS, 32);//vpn网卡的ip builder.addRoute(VPN_ROUTE, ROUTE_PREFIX);//设置路由规则
builder.setMtu(15000);//数据超过多少之后分包
vpnInterface = builder.setSession(app_name).setConfigureIntent(pendingIntent).establish();
FileDescriptor vpnFileDescriptor=vpnInterface.getFileDescriptor(); //获取文件描述符
拿到设备的文件描述符之后,就可以读取网卡拦截到的所有流量
//////////////////////////////////IOS/////////////////////////////////////
IOS 客户端流量拦截,Apple提供了 NetworkExtension 框架,让开发者可以在iOS、Mac os中进行VPN开发。iOS中的VPN开发分为 个人VPN 和 非个人VPN 开发。个人VPN开发比较简单,可以直接使用系统提供的IPSec、IKEv2协议来进行VPN连接。而在iOS9之后,Apple开放了新的api,可以让开发者开发自己的私密协议的VPN。
个人VPN主要依赖 NEVPNManager 类来进行开发,NEVPNManager 用于创建和管理VPN配置并控制最终的VPN隧道连接。参考地址:https://developer.apple.com/reference/networkextension/nevpnmanager 。
然而我们在实际的开发中往往需要开发安全程度更高的私密协议的VPN,以满足客户需求,这就需要用到NetworkExtension框架的另外一个类 NEPacketTunnelProvider,利用该类建立虚拟网卡拦截流量。
NEPacketTunnelProvider
,是真正的 vpn 核心代码。项目中 PacketTunnelProvider
是其子类,并且以下两个方法必须实现。
/////////////////////////////////开启隧道//////////////////////////////////////////////////
- (void)startTunnelWithOptions:(NSDictionary<NSString *,NSObject *> *)options completionHandler:(void (^)(NSError * _Nullable))completionHandler {
[self addObserver:self
forKeyPath:@"defaultPath"
options:0
context:NULL];
if (!_tmfPacketTunnel) {
_tmfPacketTunnel = [[TMFPacketTunnel alloc] initWithData:self];
}
NETunnelProviderProtocol *protocol = (NETunnelProviderProtocol*) self.protocolConfiguration;
[_tmfPacketTunnel startTunnelWithOptions:protocol completionHandler:completionHandler];
}
/////////////////////////////////停止隧道//////////////////////////////////////////////////
- (void)stopTunnelWithReason:(NEProviderStopReason)reason completionHandler:(void (^)(void))completionHandler {
// NEProviderStopReasonUserInitiated:用户主动断开或切换了别的VPN
// NEProviderStopReasonSuperceded:被其他VPN抢占
if (_tmfPacketTunnel) {
[_tmfPacketTunnel stopTunnelWithReason:reason completionHandler:completionHandler];
}
}
这一步就只做了一件事情: 将配置信息传给系统, 也就是调用setTunnelNetworkSettings方法, 完成建立 VPN 的过程.
[_tunnelProvider setTunnelNetworkSettings:settings completionHandler:^(NSError * _Nullable error) {
if (error) {
if (completionHandler) {
completionHandler(error);
}
} else {
if (completionHandler) {
completionHandler(nil);
}
}
}];
官方已经说了, 通过packetFlow来收发系统/app 与服务器进行的通讯数据包.理论上, 只要 VPN 一建立就需要监听往返的数据包了
代码语言:javascript复制/// Make the initial readPacketsWithCompletionHandler call.
func startHandlingPackets() {
packetFlow.readPackets { inPackets, inProtocols in
self.handlePackets(inPackets, protocols: inProtocols)
}
}
代码语言:javascript复制// 读数据包进行解析
self.packetFlow.readPackets { inPackets, inProtocols in
self.handlePackets(inPackets, protocols: inProtocols)
}
代码语言:javascript复制// 将从服务端接收到的数据包写入虚拟网卡
override func sendPackets(_ packets: [Data], protocols: [NSNumber]) {
packetFlow.writePackets(packets, withProtocols: protocols)
}
IOS本地读到数据包后想怎么处理就怎么处理,随后可以连接TCP,建立相关通道进行转发之类。
代码语言:javascript复制let tunFd = self.packetFlow.value(forKeyPath: "socket.fileDescriptor") as! Int32;
同样拿到虚拟网卡描述符也很重要,可以自定义读取tunFd
PC 端与移动端的最大不同再于建立虚拟网卡,移动端有专门的API给用户创建虚拟网卡,修改路由表,然而PC端创建虚拟网卡,修改路由表,要靠自己编写代码实现,稍微复杂一点。
Windows主要用到命令有:
netsh、route、ipconfig等,具体使用方法可以自行查阅。
Linux主要用到命令有:
ifconfig、route等其它相关系统调用
MAC主要用到有:
系统调用:syscall.AF_SYSTEM、syscall.AF_IOCTL、syscall.AF_CONNECT等
命令行:route、ifconfig、networksetup等
然而无论是移动端还是PC端都能获取其虚拟网卡的文件描述符,因此底层流量拦截库可以统一实现。
下图为TCP状态机
虚拟网卡拦截的数据包均为IPV4包,被TCP解析后如果是Syn包,且是白名单拦截目标,则建立TCP加密连接,之后向服务端发送HTTP-Connect协议建立VPN通道。
当网关返回TCP数据给TCP状态机时,TCP状态机将TCP回复包重新组装为IPV4包,写入虚拟网卡,虚拟网卡转回给用户APP 请求。
服务端必须支持http-connect协议,如果是nginx打包编译时必须添加http-connect模块。