このドキュメントでは、iOS でネットワークリクエストを行う際に DNS 解決時間と LocalDNS 解決結果を取得する方法について説明します。
1. 概要
iOS での標準的なネットワークリクエストの場合、NSURLSessionDelegate のデリゲートメソッドから LocalDNS の名前解決時間を取得できます。HTTPDNS ソフトウェア開発キット (SDK) を統合している場合、SDK は LocalDNS をバイパスしてドメイン名の名前解決を行います。この場合に DNS 名前解決時間を取得するには、SDK の名前解決 API を呼び出す前後のタイムスタンプを取得し、その差を計算します。
iOS では、getaddrinfo メソッドを使用して、ローカル DNS の解決結果を取得できます。これらを想定される解決結果と比較して、ハイジャックが発生しているかどうかを判断できます。
2. DNS 解決時間を取得する方法
ネットワークリクエストインターフェイスが https://domain/path 形式を使用している場合は、次のコードを参照して DNS 解決時間を取得できます。
- 説明
iOS 14 のネイティブ暗号化ソリューションを使用してカスタム DNS を設定している場合、以下のコードを使用して取得される DNS 解決時間は、カスタム DNS の解決時間になります。使用していない場合、取得される DNS 解決時間は LocalDNS の解決時間になります。
#pragma mark URLSession Delegate
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics {
if ([metrics.transactionMetrics count] <= 0) return;
[metrics.transactionMetrics enumerateObjectsUsingBlock:^(NSURLSessionTaskTransactionMetrics *_Nonnull obj, NSUInteger idx, BOOL *_Nonnull stop) {
if (obj.resourceFetchType == NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad) {
NSLog(@"%@",[NSString stringWithFormat:@"リクエスト URL:%@",[obj.request.URL absoluteString]]);
NSLog(@"%@",[NSString stringWithFormat:@"サーバー IP:%@",obj.remoteAddress]);
NSURLSessionTaskMetricsDomainResolutionProtocol dnsProtocol = obj.domainResolutionProtocol;
NSLog(@"%@",[NSString stringWithFormat:@"DNS タイプは %ld です", (long)dnsProtocol]);
NSLog(@"%@",[NSString stringWithFormat:@"0:不明、1:UDP、2:TCP、3:TLS、4:HTTPS"]);
if (obj.domainLookupStartDate && obj.domainLookupEndDate) {
int dnsLookupTime = ceil([obj.domainLookupEndDate timeIntervalSinceDate:obj.domainLookupStartDate] * 1000);
NSLog(@"%@",[NSString stringWithFormat:@"DNS 開始時間: %@、DNS 終了時間: %@", obj.domainLookupStartDate, obj.domainLookupEndDate]);
NSLog(@"%@",[NSString stringWithFormat:@"DNS 解決時間(ミリ秒): %d",dnsLookupTime]);
}
}
}];
}HTTPDNS SDK を統合している場合、次のコードを使用して HTTPDNS SDK からドメイン名の名前解決時間を取得します。
CFTimeInterval startTimer = CACurrentMediaTime();
//使用している SDK の解決メソッドに置き換えてください
[[DNSResolver share] getIpv4DataWithDomain:@"main.m.taobao.com" complete:^(NSArray<NSString *> *dataArray) {
CFTimeInterval endTimer = CACurrentMediaTime();
UInt32 rtt = (endTimer - startTimer) * 1000;
NSLog(@"%@",[NSString stringWithFormat:@"DNS 解決時間(ミリ秒): %d",rtt]);
}];
3. LocalDNS 解決結果の取得とハイジャック検出比較の実行
次のコードを使用して、LocalDNS 解決結果を取得し、想定される解決結果と比較して、ハイジャックが発生しているかどうかを判断できます。
#import <Foundation/Foundation.h>
#import <netdb.h>
#import <arpa/inet.h>
- (void)resolveDomain:(NSString *)domain expectedIPs:(NSArray<NSString *> *)expectedIPs {
struct addrinfo hints, *result, *p;
memset(&hints, 0, sizeof(hints));
// hints 構造体を初期化します
hints.ai_family = AF_UNSPEC; // IPv4 または IPv6
hints.ai_socktype = SOCK_STREAM; // TCP
// DNS クエリを実行します
int s = getaddrinfo([domain UTF8String], NULL, &hints, &result);
if (s != 0) {
NSLog(@"getaddrinfo エラー: %s", gai_strerror(s));
return;
}
BOOL isHijacked = NO;
// 結果をトラバースして IPv4 アドレスと IPv6 アドレスを取得します
for (p = result; p != NULL; p = p->ai_next) {
char ipBuffer[INET6_ADDRSTRLEN]; // IP アドレスをフォーマットするためのバッファ
if (p->ai_family == AF_INET) {
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
inet_ntop(AF_INET, &ipv4->sin_addr, ipBuffer, sizeof(ipBuffer));
NSLog(@"IPv4 アドレス: %s", ipBuffer);
} else if (p->ai_family == AF_INET6) {
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
inet_ntop(AF_INET6, &ipv6->sin6_addr, ipBuffer, sizeof(ipBuffer));
NSLog(@"IPv6 アドレス: %s", ipBuffer);
}
// 解決結果を想定される IP アドレスと比較します
if (![expectedIPs containsObject:[NSString stringWithUTF8String:ipBuffer]]) {
isHijacked = YES;
}
}
// ハイジャックが存在するかどうかを判断します
if (isHijacked) {
NSLog(@"警告: DNS ハイジャックが存在する可能性があります!");
} else {
NSLog(@"DNS ハイジャックは検出されませんでした。");
}
// メモリを解放します
freeaddrinfo(result);
}