故事的起因
2015年苹果在开发者大会上,首次提到iOS 9和OSX 10.11将公开底层网络接口给第三方developer,这组新的接口叫做Network Extension API。简单地讲,苹果将在iOS和OSX里植入虚拟网卡,允许第三方app控制虚拟网卡并修改系统路由表。
在此之前的8年里,苹果从未公开过这组API。在没有jailbreak的iOS上,只有三家公司曾做到过控制系统路由表:Cisco, Juniper,以及iOS 6.0之后的OpenVPN社区。他们能做,因为他们和Apple签了某种协议,从而获得苹果定制的entitlement。
现在,苹果决定开放这个权限给全世界。
于是技术狂悄悄动手了。ss在7月中开始coding全新的proxy代码,可惜8月底git repo被清空了。那时基于新接口的ss还没完工。
为苹果免费debug的艰难历程
我和ss几乎同时开始做这块。不过,我的打法是开发全新VPN协议,这和ss走sock5代理是两条路。
要调用Network Extension,先要问苹果拿一个特殊的entitlement。否则只能得到无止尽的NSError。拿这个许可大约花费两周时间。
我们很早就在Android上开发了新的VPN协议并投入使用。
原以为拿到许可之后,简单地把C代码搬到XCode里,再加个OC或Swift的UI层就搞定了。后来的经历证明苹果的坑很多,官方文档和stackoverflow对这块技术也接近空白,一切全靠试错。
第一个坑,NWTCPConnection不work
开篇提到的苹果开发者大会Session 717,当时Tommy Pauly演示了一个swift写的vpn demo,用的是NWTCPConnection。三个月后苹果放出来的sample code也用了NWTCPConnection。
But, 实测下来,NWTCPConnection根本不work,connect一直timeout,不知道Tommy Pauly当时怎么做的。
Workaround还是有的,就是用C开TCP socket,然后用BSD的kqueue自己写异步IO。
第二个坑,NWUDPSession每个包最多只能传送1472字节
这很容易理解,大部分WiFi环境下iOS网卡MTU配置成1500,减掉IP header共20字节,再减掉UDP header共8个字节,剩下数据部分1472个字节。
但是iOS的行为非常古怪。
之前我们在Windows, OSX,Linux和Android上获得的经验是这样的,系统IP层会自动分解超大的UDP数据,这个过程叫fragment,送到另一端时IP层又自动合成出原始UDP包,这个过程叫defragment。整个过程对应用层app是透明的。所以在其他几个操作系统上用UDP传送超过MTU的数据不会有问题。
iOS呢?实测下来,iOS不会自动fragment/defragment,也不会把超过MTU的包整个扔掉,而是传送1472字节给应用层。
于是从network extension拿到的只是部分数据,当然无法跑通VPN。这问题花了几周时间才搞清楚。
强烈要求苹果改改iOS TCP/IP内核代码,起码也要和OSX一样吧。
第三个坑,iOS Provision Profile带来的问题
终于,iOS上搞定了,自己用的很开心。
交给苹果review,等到D疼,终于approve了。从app store下载安装,居然不能用!
dev版本是可以用的,production版本不能用,问题在哪里?
先连夜赶工,VPN核心模块切换到老代码,然后加个switch,默认关闭。
接下去,找始作俑者,也就是开发Network Extension的那位大神。
非常不幸的是,苹果在安全保密方面极端严格。通过这个渠道去询问技术问题,绝对无可奉告。
上论坛试试吧。
最终我们得到大神回复,删掉本机所有profile,然后新建一组profile,重新build,试试!
我们照做了,搞定!
不过坦率承认,这招我们时常用,在此贴之前,不管怎么弄profile都没用。苹果微调了啥呢?
更艰难的Mac之旅
有了iOS的code base,port到Mac岂不是秒杀?
Yes and No.
在UI层,大部分工作就是把UI改成NS,比如把UIView改成NSView。这要多谢Cocoa和Cocoa Touch结构基本相同。
但底层的app extension里还是有细节上的差异。要注意,Xcode不会显示app extension里的NSLog,得这样看。
又瞎忙了N天,看起来差不多了。在自己Mac上跑的甚好。
事后证明,还需要被折磨6周,才能最终把它推到Mac app store。
这次,还是provision profile的问题。见图。
原来,调用Network Extension要求先取得苹果的许可,然后可以新建一个provision profile使用network extension扩展entitlement,其中包括三种权限,分别是:
HotspotHelper,允许app控制wifi连接,这是wifi万能钥匙这种app必须的功能
AppProxy,让第三方app在应用层过滤其他app的网络通信,适合做内容过滤和监控
PacketTunnelProvider,这是VPN相关的,在IP层拿到虚拟网卡和路由表控制权
但是,OSX不支持HotspotHelper,所以,Xcode打包项目后做validation时报错了。
解决方案呢?
先依靠自己的力量,找遍所有相关资料,一个一个试,无解。
然后,去Apple开发者论坛发帖,无解。
最后几经周折,在确信这问题99%出在苹果的前提下,硬着头皮找到苹果Core OS团队的人。
嗯,进了他们的ticket system,也算为OSX和Network Extension做点贡献了。
然后,等。
顺便说下,OSX里有个pluginkit命令,开发app extension时要用到。
列出所有plugin
pluginkit -m -A
强制安装plugin
pluginkit -a <filename>
强制删除指定的plugin
pluginkit -r <filename>
在开发阶段,Xcode有时不能正确安装或刷新app extension,此时就要手工操作了。
完工
后来,Apple fix了这个问题。幸好,不需要升级XCode。
我们有幸成为全球第一个,也是唯一一个遇到这个问题的人。
这是Mac版成品的样子。
这是iOS版。
整个开发过程零零碎碎历经几个月。我们总体感觉是,苹果开放的Network Extension接口没有达到最成熟的阶段,可能的原因是苹果开发流程很封闭而且环节很多,涉及Apple Developer Program里各种权限,涉及XCode,还有就是code,任何一环出错都会造成产品无法上线。
而且我们认为,苹果自己的Core Network部门并没有像第三方developer一样从头到尾把流程跑一遍,才会出现各种细节问题。
Anyway,we did it。
现在,我们需要更多人来测试这个产品。当时iOS那个profile引发的bug至今没有得到苹果的官方解释。同时,在Mac上,我们也发现pluginkit不是每次都能自动加载app extension。
任何新发现的问题我们都会及时和苹果Core Network团队沟通,相信大部分都是他们的错。
如果你有iOS 9.0和OSX 10.11以上的设备,正好需要完全免费的VPN,请试用这两个app。
iOS: https://itunes.apple.com/us/app/hideme-free-vpn-proxy-unlimited/id879905781?ls=1&mt=8
Mac: https://itunes.apple.com/us/app/hideme-free-vpn-proxy-unlimited/id1084098222?ls=1&mt=12