精选文章 iOS 防 DNS 污染方案调研 --- SNI 业务场景

iOS 防 DNS 污染方案调研 --- SNI 业务场景

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

概述

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.
//  Copyright © 2017 ChenYilong. All rights reserved.
//

#import 

@interface NSURLRequest (NSURLProtocolExtension)

- (NSURLRequest *)httpdns_getPostRequestIncludeBody;

@end //
//  NSURLRequest+NSURLProtocolExtension.h
//
//
//  Created by ElonChan on 28/07/2017.
//  Copyright © 2017 ChenYilong. All rights reserved.
//

#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;
}

@end

使用方法:

在用于拦截请求的 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

参考链接:

勿删,copyright占位
您找到想要的结果了吗?
iOS 防 DNS 污染方案调研 --- SNI 业务场景
提交成功!非常感谢您的反馈,我们会继续努力做到更好
分享文章到微博
分享文章到朋友圈

上一篇:红帽发布新Ansible产品,推动企业与网路自动化

下一篇:iOS 防 DNS 污染方案调研 --- 302等 URL 重定向业务场景

您可能感兴趣

  • 创新业务 银联海量数据分析平台案例

    本文讲的是创新业务 银联海量数据分析平台案例,为了创新业务的需求,更好地服务银行业,中国银联积极探索新的技术,进军大数据领域。   ? 作为首期应用示范,需要用hadoop构建字段合规性分析系统,商户推荐系统,数据规模是,数据导入性能每小时80GB,全年数据30TB左右,每条记录模式是刷卡交易信息,每条大小200~500字节不等。   ...

  • Redis基础教程第2节 Redis和NoSql 介绍与应用场景

    NoSQL(NoSQL = Not Only SQL ),意即“不仅仅是SQL”,是一项全新的数据库革命性运动,早期就有人提出,发展至2009年趋势越发高涨。NoSQL的拥护者们提倡运用非关系型的数据存储,相对于铺天盖地的关系型数据库运用,这一概念无疑是一种全新的思维的注入。 NoSQL的特点: 处理差大量的数据运行在廉价的...

  • iOS开发-keyboardType合集

    1、UIKeyboardTypeDefault,常用于文本输入 2、UIKeyboardTypeASCIICapable,常用于密码输入 3、UIKeyboardTypeNumbersAndPunctuation,和上一个键盘互相切换 4、UIKeyboardTypeURL,适用于网址输入 5、UIKeyboardTypeNumb...

  • 判断当前网络状态 - iOS

    在目前项目中,为了更好的用户体验,有些场景下首先需要进行当前网络状态的判断后,再执行后续流程。 例如:下载、音频、视频的播放等相关的一些操作前,首先需要对当前用户网络环境进行判断,是 WiFi 还是 4G 等。 WiFi则可以继续执行后续操作流程,如果当前网络处于 4G 的情况下,需要使用流量才可执行后续操作,则应提醒用户是否执行后续操作的提示,使得...

  • H5 iOS浏览器Safari/IE浏览器不认识日期格式"-"与“T“,“2019-11-22T:10:00:00”这个格式需要转换处理

    最近做一个h5时间段展示界面,需要将日期格式转为毫秒进行处理,本人用的Android手机与google浏览器调试,一切显示正常,但是当同事用苹果手机测试的时候发现显示不出来,于是调试了一下发现所有日期转出来的毫秒值都是0.问了万能的度娘后发现已经有同僚遇到过此类问题了 就是Safari不认识“2019-11-22”这个格式,认识“2019/11/2...

  • iOS组件化 打包SDK

    笔者性懒,腹中无墨. 项目组件化可能使用到的framework打包流程梳理(本文均已ISHTool_SDK项目为例). framework打包 1.新建工程, 选择 iOS -> Framework & Libra...

  • open***负载均衡高可用多种方案实战讲解02(老男孩主讲)

    ×××(全称Virtual Private Network)虚拟专用网络,是依靠ISP和其他NSP,在公共网络中建立专用的数据通信网络的技术,可以为企业之间或者个人与企业之间提供安全的数据传输隧道服务。在×××中任意两点之间的连接并没有传统专网所需的端到端的物理链路,而是利用公共网络资源动态组成的,可以理解为通过私有的隧道技术在公共数据网...

  • DNS:域名系统

    域名系统(DNS)是一种用于TCP/IP应用程序的分布式数据库,它提供主机名字和IP地址之间的转换及有关电子邮件的选路信息。分布式是指在Internet上的单个站点不能拥有所有的信息。每个站点保留它自己信息数据库,并运行给一个服务器程序供Internet上的其他系统查询。DNS提供了允许服务器和客户程序相互通信的协议。 1. 数据结构 D...

CSDN

CSDN

中国开发者社区CSDN (Chinese Software Developer Network) 创立于1999年,致力为中国开发者提供知识传播、在线学习、职业发展等全生命周期服务。
iOS 防 DNS 污染方案调研 --- SNI 业务场景介绍:华为云为您免费提供iOS 防 DNS 污染方案调研 --- SNI 业务场景在博客、论坛、帮助中心等栏目的相关文章,同时还可以通过 站内搜索 查询更多iOS 防 DNS 污染方案调研 --- SNI 业务场景的相关内容。| 移动地址: iOS 防 DNS 污染方案调研 --- SNI 业务场景 | 写博客