iOS HTTPDNS集成,结合AFNetwork进行“ip直连

2020-12-03  本文已影响0人  Waynee

        本期是通过NSURLProtocol拦截的方式替换ip,包括SNI的处理。后期有时间会有一章通过hook网络框架AFN的方式,这种方式也无需改变AFN源码,包括在HTTPS证书校验的过程。而本文主要针对HTTPS协议进行说明,HTTP协议没有SSL/TLS的证书验证的过程,处理起来比较简单,也无需用到CFNetwork,所以暂时不做讲解。还有一篇结合SDWebImage对HTTP协议进行"ip直连"的文章。无论是HTTP还是HTTPS都可结合AFNetwork和SDWebImage进行"ip直连",其中原理都是相通的。若有不足或误区,盼请指正。

        大概说一下本章内容:本文对HTTPDNS做一个较为详细的讲解,在看本文之前,您需要对系统偏底层的CFNetwork网络框架有一个比较基础的认知。整体总结为以下几点。

一、什么是HTTPDNS?

        首先,DNS解析的目的就是通过某个域名:host(www.xxx.com)找到ip地址(111.111.1.1)。对于HTTPDNS的含义,用通俗简单一点语言解释。就是跳过运营商的DNS解析,通过ip地址直接精准对目标服务器进行连接。可避免被劫持,并大大缩短DNS解析时间,这也是对网络请求的一个重要优化环节(DNS优化)。

        我们都知道一般的request发出去之后的第一步就是对请求域名进行解析,而一般情况下我们的解析都是走运营商的LocalDNS,首先LocalDNS会对请求过的数据做缓存,因为缓存的时效性,我们想拿到最新最准确的数据时就会受限。其次,在这个DNS解析的过程中容易被外界劫持,注入一些未知的内容(像广告一类)后返回,甚至返回一个全新的ip给用户,可能危及到用户的个人隐私。而HTTPDNS是让用户绕过LocalDNS,在发送请求之前在本地就把host换成ip,直接对准目标服务器,这样就无需去问运营商服务器索要ip了,本来耗时不短的DNS的解析过程耗已在本地客户端完成了,所以DNS解析的时间就大大缩短了。   

        至于怎么拿到host对应的ip呢,因为客户端没法拿到当前host对应的最优ip(比如根据发送请求的地域不同等原因,对应的最优ip也可能不同),所以最好不要写死在客户端,而是通过外界拿到。两种方案:1、服务器直接下发,此种情况也是比较复杂,因为涉及的很多容灾的处理,比如在ip链接失败的时候,需要用LocalDNS做兜底,并向服务器上传错误日志,询问是否需要更新本地ip集合等等一系列复杂的操作。2、通过三方拿到,比如DNSPOD,阿里等。个人建议选择支持预处理的三方,在启动app的时候,预先拿到需要的ip集合,比如阿里。


二、结合AFNetwork的普通网络请,通过NSURLProtocol对网络进行拦截,然后进行"ip直连"。

        此文主要通过NSURLProtocol拦截请求的方式进行“ip直连”,以及post请求时,body“丢失”问题处理,SNI场景处理,利用CFNetwork对SNI的处理。先简单介绍一下NSURLProtocol,NSURLProtocol是苹果提供给开发者们专门用来劫持网络请求的一个抽象类,所以一般用到的时候选择用继承NSURLProtocol的方式,本文中的WYCustomURLProtocol就只这样一个子类。先了解几个重要的api:       

1、+ (BOOL)canInitWithRequest:(NSURLRequest *)request;// 通过协议或是域名判断哪些域名的请求需要拦截的就返YES,反之返NO。   

2、+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;  // 上面方法的返YES时,会走到这个方法,你可以直接返回request,也可以重定向一个request返回。

3、- (void)startLoading;// 重定向的request就是从此发出去的

4、- (void)stopLoading; //  当cancel请求的时候,会调用此方法。

接下来就开始附上code了,要用UrlProtocol之前需要对之进行注册。

第1步、在初始化AFHTTPSessionManager的时候,对其进行注册:如图 1

图 1

第2步、若你使用到了YTK框架,那么可以这样初始化,此处为了便于理解所以引用了YTK,实际操作的时候,建议对外提供一个注册接口,不要直接用此种直接依赖TYK的方式,以免污染HTTPDNS部分。 图 2

图 2

第3步、对于要过滤拦截的域名需在此方法判断 图 3

图 3

第4步、请求ip替换host的过程,并给header添加host, 如图 4。此处只要能正确替换,大家可以各显神通。

图 4

其中的wy_bodyForPost目的是为了取回body,方法体如图 5 

图 5

        此处需要说明一下,通过NSURLProtocol拦截的post请求的body并非真的丢失了,只是body数据在URL loading system中到达这里之前就已被转成stream了。可以在request.HTTPBodyStream中解析它。

第5步、利用CFNetwork重新构建C语言的request,并发送。如图 6和 图7

图 6 图 7

三、SNI场景。

        什么是SNI场景呢,SNI全称 Server Name Indication,是TLS的扩展。我们知道一个服务器可能对应了多个域名的情况,客户端与服务端在建立HTTPS连接的过程中要进行SSL/TLS握手。整个握手过程步骤分5步:

1、客户端发起握手请求,携带随机数、支持算法列表等参数。

2、服务端收到请求,选择合适的算法,下发公钥证书和随机数。

3、客户端对服务端证书进行校验,并发送随机数信息,该信息使用公钥加密。

4、服务端通过私钥获取随机数信息。

5、双方根据以上交互的信息生成session ticket,用作该连接后续数据传输的加密密钥。

而握手失败就发生在第3步,客户端验证后端给的证书过程如下:

1、客户端用本地的根证书解开证书链,确认服务端下发的证书是由可信任机构颁发。

2、客户端检查证书的domain域和扩展域,验证是否包含本次请求的host。

若是在握手过程中客户端要是没有指定需要访问的目标域名,这就会导致一个问题:如果一台服务机对应了多个域名,每个域名对应的证书也不一样,那服务端也不知道该返回给客户端哪一套证书进行校验,导致第3步的校验失败,以至握手失败。但若是我们指定该SSL/TLS的目标域名,那服务端就知道该返回那一套证书了,即可通过证书验证,握手成功。

那么如何指定目标域名呢?我们可以为SNI扩展字段添加一个host。而iOS的上层网络库NSURLConnection/NSURLSession没有提供对SNI字段进行配置的接口,所以要用到Socket层级的底层网络库CFNetwork,对SNI字段进行配置。如图 8

图 8

补充:1、为什么要在SNI的扩展里面添加传host,我不是在header传了host了吗?此处做一个简要说明:由于HTTP的host字段在header中。而SSL/TLS的握手以及证书验证都是在HTTP开始之前,所以header里面的host对于如何判断选取证书是没有关系的。这个时候,SSL/TLS协议的HELLO中增加了一个server name字段(SNI扩展字段),让HTTP的客户端可以提前设置host,从而使服务端在SSL/TLS的握手阶段可以选择正确的证书。在SSL/TLS握手过程中,若不是SNI的情况,意味着服务器只对应了一个域名,一套证书,所以可以直接返回即为正确的证书,这也是为什么不是SNI的情况下无需配置SNI的原因。前面我们说了SNI呢是一对多的情况,在拿不到host的时候不知道返回哪一套证书,就会返回默认的一套或不返回,这个时候我们就需要在配置SNI了,添加server name字段,对应的value即为host值。(比如:若是你需要通过HTTPS访问CDN节点资源,而CDN的节点往往服务了多个域名,所以需要通过SNI指定具体的域名证书进行通信。)

四、证书校验

        在证书校验的过程中解决了SNI的问题之后呢,服务端会返回给客户端一个与请求域名相匹配的证书,在客户端验证的时候若是默认取的请求地址中的host,而此时默认host早被我们换成了ip地址了,与证书是不匹配的,所以我们需要将header中的host取出进行证书验证。数据处理过程如图9 ;证书验证过程如图 10,一般SecTrustResultType的值为kSecTrustResultProceed或kSecTrustResultUnspecified表明验证通过。

图 9 图 10

下面主要是一些读取数据过程中对数据的处理,细节就不多说了,基本都是更CFNetwork相关部分。 如图 11

图 11
上一篇下一篇

猜你喜欢

热点阅读