不知道你们有没有发现这样一个现象,在打开一些网页的时候会弹出一些与所浏览网页不相关的内容好比这样奇(se)怪(qing)的东西
javascript
如今假如咱们访问一个网站www.baidu.com从按下回车到百度页面显示到咱们的电脑上会经历以下几个步骤html
其中第二步就是咱们所说的DNS解析过程,域名和IP地址的关系其实就是咱们的身份证号和姓名的关系,都是来标记一我的或者是一个网站的,只是IP地址\身份证号只是一串没有意义的数字,辨识度低,又很差记,因此就会在IP上加上一个域名以便区分,或是作的更加个性化,可是若是真的要来准确的区分仍是要靠身份证号码或者是IP的,因此DNS解析就应运而生了。java
根本缘由就是如下两点:ios
了解了DNS劫持的相关资料后咱们就知道了,防止NDS劫持就要从第二步入手,由于DNS解析过程是运营商来操做的,咱们不能去干涉他们,否则咱们也就成了劫持者了,因此咱们要作的就是在咱们请求以前对咱们的请求连接作一些修改,将咱们本来的请求连接www.baidu.com 修改成180.149.132.47,而后请求出去,这样的话就运营商在拿到咱们的请求后发现咱们直接用的就是IP地址就会直接给咱们放行,而不会去走他本身DNS解析了,也就是说咱们把运营商要作的事情本身先作好了。不走他的DNS解析也就不会存在DNS被劫持的问题,从根本是解决了。git
咱们知道要要把项目中请求的接口替换成成IP其实很简单,URL是字符串,域名替换IP,无非就是一个字符串替换而已,的确这块其实没有什么技术含量,并且如今像阿里云(没开源),七牛云(开源),等一些比较大的平台在这方面也都有了比较成熟的解决方案,一个SDK,传个普通的URL进去就会返回一个域名被替换成IP的URL出来,也比较好用,这里要说一下IP地址的来源,如何拿到一个域名所对应的IP呢?这里就是须要用到另外一个服务——HTTPDNS,国内比较有名的就是DNSPOD,包括阿里,七牛等也是使用他们的DNS服务来解析,就是这个
github
///这个请求URL的结构是固定的119.29.29.29是DNSPOD固定的服务器地址,ttl参数的意思是返回结果是否带ttl是个BOOL,dn就是咱们须要解析的域名,id就是咱们在dnspod上注册时候他给咱们的一个KEY
NSString *url = [NSString stringWithFormat:@"http://119.29.29.29/d?ttl=1&dn=www.baidu.com&id=KEY"];
NSMutableURLRequest * request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSData * data = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&networkError];复制代码
这里使用同步仍是异步都是能够的,具体根据大家业务需求。web
其实dnspod最难的部分是接入的部分,由于不一样的APP不一样的网络环境会致使各类各样的问题,若是你是一个新的项目那么接入难度会大大下降,由于你彻底能够本身封装一套网络请求,把DNS解析相关的逻辑都封装到本身的网络请求中,这样你就能够获得APP全部的网络层的控制权,想干什么就干什么,可是若是是在一个已经比较完善的APP中加入DNS防劫持的话那就是比较困难,由于你不能拿到全部网络请求的控制权这篇文章中我主要使用是NSURLProtocol + Runtime hook方式来处理这些东西的,NSURLProtocol属于iOS黑魔法的一种能够拦截任何从APP的 URL Loading System系统中发出的请求,其中包括以下api
若是你的请求不在以上列表中就不能进行拦截了,好比WKWebview,AVPlayer(比较特殊,虽然请求也是http/https可是就是不走这套系统,苹果爸爸就是这样~)等,其实对于正常来讲光用已经NSURLProtocol足够了。
NSURLProtocol这个类咱们不能直接使用,咱们须要本身建立一个他的子类而后在咱们的子类中操做他们像这样数组
// 注册自定义protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[CustomURLProtocol class]];复制代码
在这个类中咱们能够拦截到请求,而后进行处理。这个类中有四个很是重要的方法浏览器
+ (BOOL)canInitWithRequest:(NSURLRequest *)request;
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request;
- (void)startLoading;
//对于拦截的请求,NSURLProtocol对象在中止加载时调用该方法
- (void)stopLoading;复制代码
经过返回值来告诉NSUrlProtocol对进来的请求是否拦截,好比我只拦截HTTP的,或者是某个域名的请求之类
若是上面的方法返回YES那么request会传到这里,这个地方一般不作处理 直接返回request
这个地方就是对咱们拦截的请求作一些处理,咱们文中所作的IP对域名的替换就在这里进行,处理完以后将请求转发出去,好比这样
- (void)startLoading {
///其中customRequest是处理过的请求(域名替换后的)
NSURLSession *session = [NSURLSession sessionWithConfiguration:[[NSURLSessionConfiguration alloc] init] delegate:self delegateQueue:nil];
NSURLSessionDataTask *task = [session dataTaskWithRequest:customRequest];
[task resume];
}复制代码
你能够在 - startLoading 中使用任何方法来对协议对象持有的 request 进行转发,包括 NSURLSession、 NSURLConnection 甚至使用 AFNetworking 等网络库,只要你能在回调方法中把数据传回 client,帮助其正确渲染就能够,好比这样:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}复制代码
client在后面会有讲解。
请求完毕后调用
大概的执行流程是这样
/*! @method client @abstract Returns the NSURLProtocolClient of the receiver. @result The NSURLProtocolClient of the receiver. */
@property (nullable, readonly, retain) id <NSURLProtocolClient> client;复制代码
你能够认为是这个是请求的发送者,打个比方,A想给B发送一个消息,因为距离遥远因而A去了邮局,A把消息内容告诉了邮局,而且A在邮局登记了本身名字方便B有反馈的时候邮局来通知A查收。这个例子中邮局就是NSURLProtocol,A在邮局登记的名字就是client。全部的 client 都实现了 NSURLProtocolClient 协议,协议的做用就是在 HTTP 请求发出以及接受响应时向其它对象传输数据:
@protocol NSURLProtocolClient <NSObject>
...
- (void)URLProtocol:(NSURLProtocol *)protocol didReceiveResponse:(NSURLResponse *)response cacheStoragePolicy:(NSURLCacheStoragePolicy)policy;
- (void)URLProtocol:(NSURLProtocol *)protocol didLoadData:(NSData *)data;
- (void)URLProtocolDidFinishLoading:(NSURLProtocol *)protocol;
...
@end复制代码
固然这个协议中还有不少其余的方法,好比 HTTPS 验证、重定向以及响应缓存相关的方法,你须要在合适的时候调用这些代理方法,对信息进行传递。
到此正常状况下的DNS的解析过程已经结束,若是你发现按照如上操做以后并无达到预期效果那么请往下看,(一般状况下完成以上操做 原有的URL的就会变成http://123.456.789.123/XXX/XXX/XXX的格式。若是发现请求不成功就往下看吧)
[mutableRequest setValue:self.request.URL.host forHTTPHeaderField:@"HOST"];复制代码
[mutableRequest setValue:YOUR Cookie forHTTPHeaderField:@"Cookie"];复制代码
// 注册自定义protocol
[NSURLProtocol registerClass:[CustomURLProtocol class]];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.protocolClasses = @[[CustomURLProtocol class]];复制代码
AFHTTPSessionManager * sessionManager = [AFHTTPSessionManager manager];复制代码
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
self.operationQueue.maxConcurrentOperationCount = 1;
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
.
.
.
}复制代码
+ (NSURLSession *)swizzle_sessionWithConfiguration:(NSURLSessionConfiguration *)configuration {
NSURLSessionConfiguration *newConfiguration = configuration;
// 在现有的Configuration中插入咱们自定义的protocol
if (configuration) {
NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolArray insertObject:[CustomProtocol class] atIndex:0];
newConfiguration.protocolClasses = protocolArray;
}
else {
newConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSMutableArray *protocolArray = [NSMutableArray arrayWithArray:configuration.protocolClasses];
[protocolArray insertObject:[CustomProtocol class] atIndex:0];
newConfiguration.protocolClasses = protocolArray;
}
return [self swizzle_sessionWithConfiguration:newConfiguration];
}复制代码
/* * Customization of NSURLSession occurs during creation of a new session. * If you only need to use the convenience routines with custom * configuration options it is not necessary to specify a delegate. * If you do specify a delegate, the delegate will be retained until after * the delegate has been sent the URLSession:didBecomeInvalidWithError: message. */
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration;
+ (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(nullable id <NSURLSessionDelegate>)delegate delegateQueue:(nullable NSOperationQueue *)queue;复制代码
/*! @method initWithURL:options: @abstract Initializes an instance of AVURLAsset for inspection of a media resource. @param URL An instance of NSURL that references a media resource. @param options An instance of NSDictionary that contains keys for specifying options for the initialization of the AVURLAsset. See AVURLAssetPreferPreciseDurationAndTimingKey and AVURLAssetReferenceRestrictionsKey above. @result An instance of AVURLAsset. */
- (instancetype)initWithURL:(NSURL *)URL options:(nullable NSDictionary<NSString *, id> *)options NS_DESIGNATED_INITIALIZER;复制代码
AVF_EXPORT NSString *const AVURLAssetPreferPreciseDurationAndTimingKey NS_AVAILABLE(10_7, 4_0);
AVF_EXPORT NSString *const AVURLAssetReferenceRestrictionsKey NS_AVAILABLE(10_7, 5_0);
AVF_EXPORT NSString *const AVURLAssetHTTPCookiesKey NS_AVAILABLE_IOS(8_0);
AVF_EXPORT NSString *const AVURLAssetAllowsCellularAccessKey NS_AVAILABLE_IOS(10_0);复制代码
[self swizzle_initWithURL:videoURL options:@{AVURLAssetHTTPHeaderFieldsKey : @{@"Host":host}}]复制代码
//加密后的KEY
const NSString * headerKey = @"35905FF45AFA4C579B7DE2403C7CA0CCB59AA83D660E60C9D444AFE13323618F";
.
.
.
//getRequestHeaderKey方法为解密方法
return [self swizzle_initWithURL:videoURL options:@{[self getRequestHeaderKey] : @{@"Host":host}}];复制代码
######三行文字加三个连接就完事了。其实在遇到这个坑的时候我也查过不少相关资料,无非就是这三行话加这三个连接复制来复制去,没有实质性的进展,大部分公司或者是项目没有这么重的Httpdns需求,因此也就不会有这个环境,即便遇到了也就直接关闭httpdns了,后来只能本身去用CFNetwork一点点实现。具体代码就不跟你们粘贴了由于涉及到一些公司内部的代码,不过我会把我主要的参考资料发给你们。这里有个小技巧,由于都在说CFNetwork是比较底层的网络实现,好多东西须要开发者自行处理好比一些变量的释放之类的,因此咱们能少用尽可能少用,由于Cfnetwork是为SNI(https)环境服务,因此咱们在拦截判断的时候能够区分是用上层的网络请求转发仍是用底层的cfnetwork来转发,
if ([self.request.URL.scheme isEqualToString:@"https"] ) {
//使用CFnetwork
curRequest = req;
self.task = [[CustomCFNetworkRequestTask alloc] initWithURLRequest:originalRequest swizzleRequest:curRequest delegate:self];
if (self.task) {
[self.task startLoading];
}
} else {
//使用普通网络请求
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionTask *task = [self.session dataTaskWithRequest:req];
[task resume];
}复制代码
完成了以上的步骤以后你回发如今DNS坏掉的状况下手机里面除了微信QQ(他们也作了DNS解析)以外其余应用都不能上网了可是你的App依然能够正常浏览网络数据。这就是我最近在作的时候遇到的一些问题,有什么问题及时与我交流吧。
juejin.im/post/58d8e9…