How It Works
Server Name Indication (SNI) is an extension to SSL/TLS that allows a server to use multiple domains and certificates. It works as follows:
Send the domain (hostname) of the site to be accessed before connecting to the server to establish an SSL connection.
The server returns an appropriate certificate according to the domain.
In the above process, when the client uses HTTPDNS to resolve a domain, the host in the request URL will be replaced with the IP resolved by HTTPDNS, so that the domain obtained by the server will be the resolved IP, and the client cannot find a matching certificate and can return only the default certificate or no certificate. As a result, an SSL/TLS handshake failure will occur.
As iOS' upper level network libraries of NSURLConnection and NSURLSession don't provide an API to configure the SNI field, you can consider using NSURLProtocol
to intercept network requests, using CFHTTPMessageRef
to create an NSInputStream
instance for socket communication, and setting the value of its kCFStreamSSLPeerName
.
Note that when you use NSURLProtocol
to intercept a POST request made by NSURLSession
, HTTPBody
will be empty. There are two solutions to this:
Send a POST request by using NSURLConnection
.
Place HTTPBody
in the HTTP header field first and then get it from NSURLProtocol
.
Demo
Register the NSURLProtocol
subclass in SNIViewController.m
of the demo before sending network requests.
[NSURLProtocol registerClass:[MSDKDnsHttpMessageTools class]];
NSString *originalUrl = @"your url";
NSURL *url = [NSURL URLWithString:originalUrl];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
NSArray *result = [[MSDKDns sharedInstance] WGGetHostByName:url.host];
NSString *ip = nil;
if (result && result.count > 1) {
if (![result[1] isEqualToString:@"0"]) {
ip = result[1];
} else {
ip = result[0];
}
}
if (ip) {
NSRange hostFirstRange = [originalUrl rangeOfString:url.host];
if (NSNotFound != hostFirstRange.location) {
NSString *newUrl = [originalUrl stringByReplacingCharactersInRange:hostFirstRange withString:ip];
request.URL = [NSURL URLWithString:newUrl];
[request setValue:url.host forHTTPHeaderField:@"host"];
}
}
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[self.connection start];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSArray *protocolArray = @[ [MSDKDnsHttpMessageTools class] ];
configuration.protocolClasses = protocolArray;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:[NSOperationQueue mainQueue]];
self.task = [session dataTaskWithRequest:request];
[self.task resume];
Notes
You need to call the following API to set the domains to be or not to be intercepted:
#pragma mark - In SNI scenarios, call it only once
/**
Set the list of domains to be intercepted in SNI scenarios
We recommend you call the API to intercept domains only in SNI scenarios but not other scenarios
@param hijackDomainArray List of domains to be intercepted
*/
- (void) WGSetHijackDomainArray:(NSArray *)hijackDomainArray;
/**
Set the list of domains not to be intercepted in SNI scenarios
@param noHijackDomainArray List of domains not to be intercepted
*/
- (void) WGSetNoHijackDomainArray:(NSArray *)noHijackDomainArray;
If you set the list of domains to be intercepted, only HTTPS requests in the list will be intercepted and processed, while other domains will not.
If you set the list of domains not to be intercepted, HTTPS requests in the list will not be intercepted and processed.
Note
We recommend you use WGSetHijackDomainArray
to intercept domains only in SNI scenarios but not other scenarios.
Was this page helpful?