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

iOS HTTPDNS集成,结合AFNetwork进行“ip直连”,第1张

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

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

        首先,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集合,比如阿里。

        此文主要通过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

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

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

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

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

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

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

        什么是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

补充: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表明验证通过。

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

服务器名称指示(英语:Server Name Indication,简称SNI)是一个扩展的TLS计算机联网协议,在该协议下,在握手过程开始时通过客户端告诉它正在连接的服务器的主机名称。这允许服务器在相同的IP地址和TCP端口号上呈现多个证书,并且因此允许在相同的IP地址上提供多个安全HTTPS网站(或其他任何基于TLS的服务),而不需要所有这些站点使用相同的证书。

基于名称的虚拟主机允许多个DNS主机名由同一IP地址上的单个服务器(通常为Web服务器)托管。为了实现这一点,服务器使用客户端提供的主机名作为协议的一部分(对于HTTP,名称显示在主机头中)。但是,当使用HTTPS时,TLS握手发生在服务器看到任何HTTP头之前。因此,服务器不可能使用HTTP主机头中的信息来决定呈现哪个证书,并且因此只有由同一证书覆盖的名称才能由同一IP地址提供。

所以,需要由SNI协议在握手时提供主机名的信息。

如果我们要对流量进行识别,做流量检测等,HTTPS流量是加密的,很难检测其内部的具体内容。SNI协议给出了一个切入点,在HTTPS协议建立加密连接的开始阶段检测出访问的域名信息。

抓取访问HTTPS的数据包,之后,通过使用显示过滤器语句 ssl.handshake 来过滤出想要的报文。分析报文发现server_name的扩展字段只存在于Client Hello这个过程中。

接下来,进一步过滤,使用显示过滤器语句 ssl.handshake.extensions_server_name ,提取出包含SNI协议的Client Hello报文。

下面这个箭头所指表示了SNI协议的格式,

Type(2B) | Length(2B) | Server Name list length(2B) | Server Nma Type(1B) | Server Name length(2B) | Server name

SNI: 实现多域名虚拟主机的SSL/TLS认证: https://shansing.com/read/355/

RFC6066: https://tools.ietf.org/html/rfc6066#page-5

如果您使用的是华为手机,建议您按以下办法处理:

1、请尽量将手机放置在干燥通风处并用纸巾吸干手机表面水渍。

2、如果手机在开机状态,请按电源键关机;如果手机已关机,请不要尝试开机。

3、请取出SIM卡和MicroSD卡。

4、请尽快携带手机前往华为客户服务中心检测处理,以免造成不必要的损失。

注意事项:

1、进水后不要频繁移动或摇晃手机,以免水分在手机内部蔓延。

2、进水后不要拿吹风机或者炉子烘烤,以免液体被吹进手机内部以及高温损坏手机。

3、有些手机经过简单处理后,可以正常开机使用,但手机由精密电子元器件构成,进水后存在隐藏的风险,所以建议不要尝试开机。

4、售后维修过程中,数据可能会丢失。


欢迎分享,转载请注明来源:夏雨云

原文地址:https://www.xiayuyun.com/zonghe/334063.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-04
下一篇2023-05-04

发表评论

登录后才能评论

评论列表(0条)

    保存