Afnetworking: Is there any way to pre-populate the DNS cache like libcurl?

Created on 8 Sep 2015  ·  9Comments  ·  Source: AFNetworking/AFNetworking

I have a requirement that pre-populate the DNS cache. I find libcurl has an API to specify domain-port-IP pair to DNS resolve for a request. See http://curl.haxx.se/libcurl/c/CURLOPT_RESOLVE.html .

I'm using AFNetworking now. However, I have a strong requirement on this libcurl API. Can AFNetworking have any way to do this?

Most helpful comment

I found it!!!
最近我们公司也要用httpDns把原始host换成IP发请求, 然后遇到了ssl校验不通过的问题,
第三方HTTPDns的文档或demo上都说在sessiontask 代理方法里去改host, 但是afn 根本就不会走task的代理方法, 它走的是session的代理方法;两个方法我都分别列在下面:

afn不走的task的代理方法:
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

afn走的session的代理方法:
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

这两个方法唯一的区别就是一个带task一个不带task, 但是致命问题就产生了, 不带task的session的代理方法无法拿到原始的没有换成IP的host, 也就无法再进行domain校验时再偷梁换柱将IP换成原始host地址去校验!
那么, 怎么解决这个问题, 让我们能使用afn用IP地址去请求呢?

首先明确解决思路:
在进行ssl校验时, 把ip换回原始host, 用原始host与后台服务器进行校验.

具体实现:
我们先去看看afn走的session的代理方法里做了什么:

-(void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {//注意看这里
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}

注意看我标注"注意看这里"的那一行, 只要self.sessionDidReceiveAuthenticationChallenge有值他就不会走底下的else方法(也就是系统默认的校验方法), 就会走我们定义的校验方法;

那么好了我们只需要给它sessionDidReceiveAuthenticationChallenge即可:

[mgr setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential *__autoreleasing _Nullable * _Nullable credential) {

//证书校验替换IP
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

NSString *originHost = weakMgr.requestSerializer.HTTPRequestHeaders[@"host"];
if ([weakMgr.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:originHost]) {//这个方法是校验ssl的关键校验时需要把IP替换成原始host
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
这里有个关键的地方就是, evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain 这个方法会进行domain校验, 也就是ssl校验, 它需要传校验的host 和 服务器的serverTrust, 两者匹配通过则ssl校验通过, 我们就是要在这里将host换成原始host, 之后就顺利通过啦;

我将原始host存在请求头里, 这样就能通过请求头取到,上面有写到:
NSString *originHost = weakMgr.requestSerializer.HTTPRequestHeaders[@"host"];

Cheers~
备注: mgr是业务代码 它是指AFHTTPSessionManager的实例;

All 9 comments

AFNetworking uses the standard NSURL Loading system, and NSURLSession. If there is a way to support it there, you should be able to, regardless of if you are using AFNetworking. I am personally not familiar with that feature.

If you find anything out, I would love to hear about it!

I do It,and it works well in AFN1.3.x.Good Luck.The Comment write in Chinese.Somebody see this ,just Read the code In attach file!

AFN中,如何使用ip直接访问https网站?

通过IP直接访问网站,可以解决DNS劫持问题,如果是HTTP请求,使用ip地址直接访问接口,配合header中Host字段带上原来的域名信息即可;如果是 https请求,会很麻烦,需要 Overriding TLS Chain Validation Correctly;curl 中有一个 -resolve 方法可以实现使用指定ip访问https网站,iOS中集成curl库应该也可以,不过改动太大,未验证;对于服务器IP经常变的情况,可能需要使用httpDNS服务,参见:https://www.dnspod.cn/httpdns.

1. 最直接的方式是允许无效的SSL证书,生产环境不建议使用;

2.一个需要部分重写AFN的方法.

  • 在Info.plist中添加NSAppTransportSecurity类型Dictionary,在NSAppTransportSecurity下添加NSAllowsArbitraryLoads类型Boolean,值设为YES.这些本来是用来解决iOS9下,允许HTTP请求访问网络的,当然作用不止这些.具体原因感兴趣的自行google.
  • 给 AFURLConnectionOperation 类添加新属性:
/** 可信任的域名,用于支持通过ip访问此域名下的https链接.
 Trusted domain, this domain for support via IP access HTTPS links.
 */
@property(nonatomic, strong) NSMutableArray * trustHostnames;
  • 给 AFURLConnectionOperation 实现的代理方法: - (void)connection:(NSURLConnection *)connection
    willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge 添加添加可信任的域名的相关逻辑代码:
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if (self.authenticationChallenge) {
        self.authenticationChallenge(connection, challenge);
        return;
    }

    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;

        /* 添加可信任的域名,以支持:直接使用ip访问特定https服务器.
         Add trusted domain name to support: direct use of IP access specific HTTPS server.*/
        for (NSString * trustHostname  in [self trustHostnames]) {
            serverTrust = AFChangeHostForTrust(serverTrust, trustHostname);
        }

    ....
  • 参考Apple官方文档,实现自定义的添加可信域名的函数: AFChangeHostForTrust
static inline SecTrustRef AFChangeHostForTrust(SecTrustRef trust, NSString * trustHostname)
{
    if ( ! trustHostname || [trustHostname isEqualToString:@""]) {
        return trust;
    }

    CFMutableArrayRef newTrustPolicies = CFArrayCreateMutable(
                                                              kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

    SecPolicyRef sslPolicy = SecPolicyCreateSSL(true, (CFStringRef)trustHostname);

    CFArrayAppendValue(newTrustPolicies, sslPolicy);


#ifdef MAC_BACKWARDS_COMPATIBILITY
    /* This technique works in OS X (v10.5 and later) */

    SecTrustSetPolicies(trust, newTrustPolicies);
    CFRelease(oldTrustPolicies);

    return trust;
#else
    /* This technique works in iOS 2 and later, or
     OS X v10.7 and later */

    CFMutableArrayRef certificates = CFArrayCreateMutable(
                                                          kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);

    /* Copy the certificates from the original trust object */
    CFIndex count = SecTrustGetCertificateCount(trust);
    CFIndex i=0;
    for (i = 0; i < count; i++) {
        SecCertificateRef item = SecTrustGetCertificateAtIndex(trust, i);
        CFArrayAppendValue(certificates, item);
    }

    /* Create a new trust object */
    SecTrustRef newtrust = NULL;
    if (SecTrustCreateWithCertificates(certificates, newTrustPolicies, &newtrust) != errSecSuccess) {
        /* Probably a good spot to log something. */

        return NULL;
    }

    return newtrust;
#endif
}
  • 使用AOP方法,重写 AFURLConnectionOperation 的trustHostnames属性:
    /* 使用AOP方式,指定可信任的域名, 以支持:直接使用ip访问特定https服务器.*/
    [AFURLConnectionOperation aspect_hookSelector:@selector(trustHostnames) withOptions:AspectPositionInstead usingBlock: ^(id<AspectInfo> info){
        __autoreleasing NSArray * trustHostnames = @[@"www.example.com"];

        NSInvocation *invocation = info.originalInvocation;
        [invocation setReturnValue:&trustHostnames];
    }error:NULL];

此处用到的是一个 iOS AOP库,不熟悉的点这里: http://www.ios122.com/2015/08/aspects/.

AFURLConnectionOperation.zip

Uploading AFURLConnectionOperation.zip…

Hey guys, Thank you very much.. I will try it in my project….Thanks.!

But , I have a another question , Does it work in AFNetwork 3.x version and Https Request?

Thanks !

Thank you

IT seems AFNetworking3.x does not sopport connect HTTPS by IP address。

I found it!!!
最近我们公司也要用httpDns把原始host换成IP发请求, 然后遇到了ssl校验不通过的问题,
第三方HTTPDns的文档或demo上都说在sessiontask 代理方法里去改host, 但是afn 根本就不会走task的代理方法, 它走的是session的代理方法;两个方法我都分别列在下面:

afn不走的task的代理方法:
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

afn走的session的代理方法:
-(void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

这两个方法唯一的区别就是一个带task一个不带task, 但是致命问题就产生了, 不带task的session的代理方法无法拿到原始的没有换成IP的host, 也就无法再进行domain校验时再偷梁换柱将IP换成原始host地址去校验!
那么, 怎么解决这个问题, 让我们能使用afn用IP地址去请求呢?

首先明确解决思路:
在进行ssl校验时, 把ip换回原始host, 用原始host与后台服务器进行校验.

具体实现:
我们先去看看afn走的session的代理方法里做了什么:

-(void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__block NSURLCredential *credential = nil;
if (self.sessionDidReceiveAuthenticationChallenge) {//注意看这里
disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
} else {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
}
if (completionHandler) {
completionHandler(disposition, credential);
}
}

注意看我标注"注意看这里"的那一行, 只要self.sessionDidReceiveAuthenticationChallenge有值他就不会走底下的else方法(也就是系统默认的校验方法), 就会走我们定义的校验方法;

那么好了我们只需要给它sessionDidReceiveAuthenticationChallenge即可:

[mgr setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession * _Nonnull session, NSURLAuthenticationChallenge * _Nonnull challenge, NSURLCredential *__autoreleasing _Nullable * _Nullable credential) {

//证书校验替换IP
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

NSString *originHost = weakMgr.requestSerializer.HTTPRequestHeaders[@"host"];
if ([weakMgr.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:originHost]) {//这个方法是校验ssl的关键校验时需要把IP替换成原始host
*credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
if (credential) {
disposition = NSURLSessionAuthChallengeUseCredential;
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
} else {
disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
}
} else {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
这里有个关键的地方就是, evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain 这个方法会进行domain校验, 也就是ssl校验, 它需要传校验的host 和 服务器的serverTrust, 两者匹配通过则ssl校验通过, 我们就是要在这里将host换成原始host, 之后就顺利通过啦;

我将原始host存在请求头里, 这样就能通过请求头取到,上面有写到:
NSString *originHost = weakMgr.requestSerializer.HTTPRequestHeaders[@"host"];

Cheers~
备注: mgr是业务代码 它是指AFHTTPSessionManager的实例;

@lafayea 这个非常有用

Was this page helpful?
0 / 5 - 0 ratings