相关文章推荐
温暖的剪刀  ·  hivesql ...·  3 月前    · 
稳重的葫芦  ·  遇到:Client network ...·  5 月前    · 
淡定的茶壶  ·  ubuntu 18.04 ...·  1 年前    · 
爽快的包子  ·  C++ 文件和流 | 菜鸟教程·  1 年前    · 

SNI(单IP多HTTPS证书)场景下,iOS上层网络库 NSURLConnection/NSURLSession 没有提供接口进行 SNI 字段 配置,因此需要 Socket 层级的底层网络库例如 CFNetwork ,来实现 IP 直连网络请求 适配方案。而基于 CFNetwork 的解决方案需要开发者考虑数据的收发、重定向、解码、缓存等问题(CFNetwork是非常底层的网络实现)。

针对 SNI 场景的方案, Socket 层级的底层网络库,大致有两种:

  • 基于 CFNetWork ,hook 证书校验步骤。
  • 基于原生支持设置 SNI 字段的更底层的库,比如 libcurl。

下面将目前面临的一些挑战,以及应对策略介绍一下:

支持 Post 请求

使用 NSURLProtocol 拦截 NSURLSession 请求丢失 body,故有以下几种解决方法:

方案如下:

  1. 换用 NSURLConnection
  2. 将 body 放进 Header 中
  3. 使用 HTTPBodyStream 获取 body,并赋值到 body 中
  4. 换用 Get 请求,不使用 Post 请求。

对方案做以下分析

  • 换用 NSURLConnection ,不多说了,与 NSURLSession 相比终究会被淘汰,不作考虑。
  • body放header的方法,2M以下没问题,超过2M会导致请求延迟,超过 10M 就直接 Request timeout。而且无法解决 Body 为二进制数据的问题,因为Header里都是文本数据。
  • 换用 Get 请求,不使用 Post 请求。这个也是可行的,但是毕竟对请求方式有限制,终究还是要解决 Post 请求所存在的问题。如果是基于旧项目做修改,则侵入性太大。这种方案适合新的项目。
  • 另一种方法是我们下面主要要讲的,使用 HTTPBodyStream 获取 body,并赋值到 body 中,具体的代码如下,可以解决上面提到的问题:
// NSURLRequest+NSURLProtocolExtension.h // Created by ElonChan on 28/07/2017. #import <Foundation/Foundation.h> @interface NSURLRequest (NSURLProtocolExtension) - (NSURLRequest *)httpdns_getPostRequestIncludeBody; // NSURLRequest+NSURLProtocolExtension.h // Created by ElonChan on 28/07/2017. #import "NSURLRequest+NSURLProtocolExtension.h" @implementation NSURLRequest (NSURLProtocolExtension) - (NSURLRequest *)httpdns_getPostRequestIncludeBody { return [[self httpdns_getMutablePostRequestIncludeBody] copy]; - (NSMutableURLRequest *)httpdns_getMutablePostRequestIncludeBody { NSMutableURLRequest * req = [self mutableCopy]; if ([self.HTTPMethod isEqualToString:@"POST"]) { if (!self.HTTPBody) { uint8_t d[1024] = {0}; NSInputStream *stream = self.HTTPBodyStream; NSMutableData *data = [[NSMutableData alloc] init]; [stream open]; while ([stream hasBytesAvailable]) { NSInteger len = [stream read:d maxLength:1024]; if (len > 0 && stream.streamError == nil) { [data appendBytes:(void *)d length:len]; req.HTTPBody = [data copy]; [stream close]; return req;

使用方法:

在用于拦截请求的 NSURLProtocol 的子类中实现方法 +canonicalRequestForRequest: 并处理 request 对象:

+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    return [request httpdns_getPostRequestIncludeBody];
 

下面介绍下相关方法的作用:

 //NSURLProtocol.h
    @method canInitWithRequest:
    @abstract This method determines whether this protocol can handle
    the given request.
    @discussion A concrete subclass should inspect the given request and
    determine whether or not the implementation can perform a load with
    that request. This is an abstract method. Sublasses must provide an
    implementation.
    @param request A request to inspect.
    @result YES if the protocol can handle the given request, NO if not.
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
    @method canonicalRequestForRequest:
    @abstract This method returns a canonical version of the given
    request.
    @discussion It is up to each concrete protocol implementation to
    define what "canonical" means. However, a protocol should
    guarantee that the same input request always yields the same
    canonical form. Special consideration should be given when
    implementing this method since the canonical form of a request is
    used to look up objects in the URL cache, a process which performs
    equality checks between NSURLRequest objects.
    This is an abstract method; sublasses must provide an
    implementation.
    @param request A request to make canonical.
    @result The canonical form of the given request. 
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
//NSURLProtocol.h
 *  @method:创建NSURLProtocol实例,NSURLProtocol注册之后,所有的NSURLConnection都会通过这个方法检查是否持有该Http请求。
 @parma :
 @return: YES:持有该Http请求NO:不持有该Http请求
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
 *  @method: NSURLProtocol抽象类必须要实现。通常情况下这里有一个最低的标准:即输入输出请求满足最基本的协议规范一致。因此这里简单的做法可以直接返回。一般情况下我们是不会去更改这个请求的。如果你想更改,比如给这个request添加一个title,组合成一个新的http请求。
 @parma: 本地HttpRequest请求:request
 @return:直接转发
+ (NSURLRequest*)canonicalRequestForRequest:(NSURLRequest *)request
  • +[NSURLProtocol canInitWithRequest:] 负责筛选哪些网络请求需要被拦截
  • +[NSURLProtocol canonicalRequestForRequest:] 负责对需要拦截的网络请求NSURLRequest 进行重新构造。

这里有一个注意点:+[NSURLProtocol canonicalRequestForRequest:] 的执行条件是 +[NSURLProtocol canInitWithRequest:] 返回值为 YES

注意在拦截 NSURLSession 请求时,需要将用于拦截请求的 NSURLProtocol 的子类添加到 NSURLSessionConfiguration 中,用法如下:

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    NSArray *protocolArray = @[ [CUSTOMEURLProtocol class] ];
    configuration.protocolClasses = protocolArray;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];

换用其他提供了SNI字段配置接口的更底层网络库

如果使用第三方网络库:curl, 中有一个 -resolve 方法可以实现使用指定 ip 访问 https 网站,iOS 中集成 curl 库,参考 curl文档

另外有一点也可以注意下,它也是支持 IPv6 环境的,只需要你在 build 时添加上 --enable-ipv6 即可。

curl 支持指定 SNI 字段,设置 SNI 时我们需要构造的参数形如: {HTTPS域名}:443:{IP地址}

假设你要访问. www.example.org ,若IP为 127.0.0.1 ,那么通过这个方式来调用来设置 SNI 即可:

curl * --resolve 'www.example.org:443:127.0.0.1'

iOS CURL 库

使用libcurl 来解决,libcurl / cURL 至少 7.18.1 (2008年3月30日) 在 SNI 支持下编译一个 SSL/TLS 工具包,curl 中有一个 --resolve 方法可以实现使用指定ip访问https网站。

在iOS实现中,代码如下

   //{HTTPS域名}:443:{IP地址}
   NSString *curlHost = ...;
   _hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);
   curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);

其中 curlHost 形如:

{HTTPS域名}:443:{IP地址}

_hosts_list 是结构体类型hosts_list,可以设置多个IP与Host之间的映射关系。curl_easy_setopt方法中传入CURLOPT_RESOLVE 将该映射设置到 HTTPS 请求中。

这样就可以达到设置SNI的目的。

走过的弯路

误以为 iOS11 新 API 可以直接拦截 DNS 解析过程

参考:NEDNSProxyProvider:DNS based on HTTP supported in iOS11

参考链接:

概述SNI(单IP多HTTPS证书)场景下,iOS上层网络库 NSURLConnection/NSURLSession 没有提供接口进行 SNI 字段 配置,因此需要 Socket 层级的底层网络库例如 CFNetwork,来实现 IP 直连网络请求适配方案。而基于 CFNetwork 的解决方案需要开发者考虑数据的收发、重定向、解码、缓存等问题(CF... ios14 无法访问本地dns的自定义dns解析 type65 HTTPS记录 SVCB HTTPSSVC 简单说,就是升级ios14和mac OS11后,内网DNS定义的域名不能被正确解析,导致无法在内网访问公司域名。 一度以为是由于新的系统读取HTTPS记录,忽略了本地DNS导的问题,尝试修改DNS无果。 关于IOS14和mac OS11 忽略本地DNS上自定义域名的查询的问题
mdns-代理 一个非常简单的多播 DNS 到Node.js 的DNS 代理。 使用DNS子系统解析A和AAAA mDNS查询。 感谢的包使这成为可能。 使mydomain.local名称再次适用于 OS X Yosemite (10.10) 和 iOS 8.2(可能还有 8.1 或 8.0 - 让我们知道)(请参阅)。 要安装软件包,请执行: "npm install mdns-proxy -g" 然后,执行“mdns-proxy”命令。 注意:在 Windows 上,您可能需要添加机器的 IP 地址作为附加参数。 虽然这个应用程序确实有效,但它仍然远非完美。 可能的改进: 注意DNS记录的TTL(目前硬编码为1800秒) 从缓存中删除旧条目(目前,如果太大,我们只需清除整个缓存) 添加对“不存在”回复的支持(参见)
文章目录简介开始1. 打开设置 > 无线局域网2. 选择想要变更设置的Wi-Fi > 点击Wi-Fi右侧的“蓝色感叹号ℹ”3. 在信息页面找到“低数据模式”并关闭 > 找到“私有无线局域网地址”并关闭4.还是在信息页面找到“配置DNS”项目5. 在配置DNS页,选择手动,并在下方DNS服务器中填充如下地址:6. 存储 即可 关闭私有地址、低数据模式、解决Wi-Fi自动的DNS失效。 1. 打开设置 > 无线局域网 2. 选择想要变更设置的Wi-Fi > 点击Wi-
首先说明一下,要解决的问题:DNS劫持。 对,就是要解决DNS劫持这个问题。不太懂网络的同学们可能不太懂什么是DNS,什么又是DNS劫持,这里简单介绍一下。 DNS就是域名解析系统,就是把我们平时用的网址域名(如www.baidu.com   www.sina.com.cn)解析成相对应的服务器IP,只有解析成IP之后,网络请求才能找到服务器。 DNS劫持是啥呢?更简单,就是有人把你的域名解
本文主要介绍 HTTPS(含SNI) 业务场景下在 iOS 端实现 “IP直连” 的通用解决方案。 1.1 HTTPS 发送 HTTPS 请求首先要进行 SSL/TLS 握手,握手过程大致如下: 客户端发起握手请求,携带随机数、支持算法列表等参数。 服务端收到请求,选择合适的算法,下发公钥证书和随机数。 客户端对服务端证书进行校验,并发... 302等 URL 重定向业务场景需要解决的问题: 302 等重定向状态码,如何正确执行跳转逻辑,要求跳转后,依然需要执行 IP 直连逻辑,多次 302,也能覆盖到。 302等 URL 重定向业务场景问题主要集中在 POST 请求上,解决方案的方向大致有几种: 将请求方式统一替换为 GET 解决 POST 请求时的重定向问题 将 URL 统一替换... 第一部分、概念的理解 1、什么是Socket? Socket又称之为“套接字”,是系统提供的用于网络通信的方法。它的实质并不是一种协议,没有规定计算机应当怎么样传递消息,只是给程序员提供了一个发送消息的接口,程序员使用这个接口提供的方法,发送与接收消息。 Socket描述了一个IP、端口对。它简化了程序员的操作,知道对方的IP以及PORT就可以给对方发送消息,再由服务
DNS 是domain name server 的简称。当然如果你搜索到这篇文章,那么我猜测你可能对DNS也有一定的了解了。所以对DNS的基本知识我就不在介绍了。。DNS 劫持举个例子,说到劫持,我们可能联想到一个坏蛋劫持了DNS服务器,拿着刀架在脖子上。这时候你问DNS服务器,博主帅吗?(发出请求)。 DNS服务器听到了,他心里的答案是,好帅哦(返回正确ip地址)。 由于坏蛋控制了他,并且对他
川流不息的方盒: 你好,请问index唯一是什么意思呀,我的程序运行现在就出现了In zoo(x$INE, order.by = as.Date(as.character(x$daily), format = "%m/%d/%Y")) : some methods for “zoo” objects do not work if the index entries in ‘order.by’ are not unique问题,是因为这吗 硬盘分区删除后的恢复! dintc: 我有一次也是遇到这种问题,用 skyRecy 恢复了 手把手教你如何在Ubuntu系统中安装Pycharm qq_45535191: Resources.Load加载文件返回null的原因 星光守卫SG: 加载时photoName不需要扩展名真的是。。。