このトピックでは、HTTPDNS を iOS アプリに統合する際の IP 直連の実装方法について説明します。iOS での HTTPDNS の統合方法の詳細については、「iOS SDK の統合」をご参照ください。
1. はじめに
モバイルネットワーク環境では、DNS ハイジャックやローカル DNS のキャッシュ汚染といった問題により、ドメイン名の解決が正しく行われず、ネットワークリクエストが失敗することがよくあります。このようなシナリオに対し、Alibaba Cloud HTTPDNS は信頼性の高い再帰的なドメイン名解決サービスを提供します。このサービスにより、モバイルアプリはローカル DNS の潜在的なリスクをバイパスし、ネットワークリクエストの成功率と安定性を向上させることができます。
しかし、iOS プラットフォームで HTTPDNS を使用する場合、リクエストを開始する前に、リクエスト内の元のドメイン名を解決済みの IP アドレスに置き換える必要があります。これは、特に HTTPS やサーバ名表示 (SNI) のような複雑なシナリオで、さらなる問題を引き起こす可能性があります。したがって、HTTPDNS を統合する前に、潜在的な問題と実行可能なソリューションを包括的に理解しておく必要があります。これにより、ビジネスで HTTPDNS を安全かつ正しく使用できるようになります。

このトピックでは、iOS で HTTPDNS を使用する際に遭遇する可能性のある主な問題について説明します。また、開発者が迅速に HTTPDNS の統合を完了できるよう、さまざまなシナリオ向けの統合ソリューションとその長所と短所を提示します。
2. iOS で HTTPDNS を使用する際の問題
モバイルアプリケーションでは、元の URL 内のドメイン名 (例: example.com) を HTTPDNS によって解決された IP アドレスに置き換えると、しばしば以下の問題が発生します。これらの問題は、HTTPS プロトコルが異なるレイヤー (TLS/SSL レイヤーと HTTP レイヤー) で Host フィールドをどのように使用するかと密接に関連しています。
TLS/SSL レイヤー: HTTPS シナリオでは、クライアントはまず TLS/SSL ハンドシェイクを実行します。クライアントは URL の Host を使用して、以下のタスクを完了します。
証明書検証:サーバー証明書のドメイン名 (コモンネームまたはサブジェクト代替名) がリクエストされた Host と一致することを確認します。
SNI:TLS 接続を確立する際、クライアントはリクエストされたドメイン名情報 (URL 内の Host) をサーバーに送信します。これにより、サーバーは対応する証明書を返すことができます。
HTTP レイヤー: TLS ハンドシェイクが完了した後、クライアントは HTTP リクエストヘッダーに
Hostフィールドを含めます。これにより、リクエストの対象となる特定のサイトまたはリソースをサーバーに伝えます。URL 内のドメイン名を IP アドレスに置き換え、HTTP ヘッダーでHostを手動で設定しない場合、サーバーは実際にアクセスされているドメイン名を識別できない可能性があります。これにより、リクエストが失敗したり、異常なコンテンツが返されたりすることがあります。
HTTPS プロトコルスタックの各レイヤーにおける Host の役割に基づき、URL 内のドメイン名を HTTPDNS で解決された IP アドレスに置き換えると、以下の技術的な問題が発生します。
ドメイン名と証明書の不一致 HTTPS リクエストの場合、HTTPDNS で解決された IP アドレスを URL の Host として直接使用すると、TLS レイヤーで正しい証明書ドメイン名 (コモンネームまたは SAN 追加ドメイン名) を照合できず、SSL ハンドシェイクが失敗します。
SNI の問題 SNI シナリオでは、単一のサーバー IP アドレスが複数のドメイン名の証明書に対応する場合があります。クライアントが SSL ハンドシェイクの段階で正しいドメイン名情報を渡さず、IP アドレスのみを送信した場合、サーバーはドメイン名に一致する証明書を返すことができません。これにより、SSL ハンドシェイクが失敗します。iOS の高レベルなネットワーク API (例:
NSURLSession) は SNI を直接設定するインターフェイスを公開していないため、SNI の問題は簡単な方法では解決が困難なことが多いです。Host ヘッダーとサービスアドレッシング URL 内のドメイン名を IP アドレスに置き換えたものの、HTTP リクエストヘッダーの
Hostを元のドメイン名に明示的に設定し忘れた場合、サーバー側は HTTP レイヤーで特定のサイトやリソースを識別できない可能性があります。例えば、CDN シナリオでは、サーバーは Host フィールドに依存して正しいコンテンツを配信します。Host が IP アドレスの場合、サービスは異常になります。下位レベルのネットワークライブラリの選択 iOS の組み込み高レベル API (例:
NSURLSession) は、SNI のカスタマイズや手動での証明書検証に対する拡張性が限られています。開発者が SNI を処理したり、TLS ハンドシェイクのロジックを変更したりしたい場合は、CFNetworkやlibcurlのような、より低レベルのインターフェイスを使用する必要があります。しかし、これにより開発およびメンテナンスコストが増加します。
要約すると、URL 内のドメイン名を HTTPDNS で解決された IP アドレスに直接置き換えることは、HTTPS シナリオにおいて TLS レイヤーでの証明書検証と SNI 転送に影響を与えます。また、HTTP レイヤーで Host ヘッダー情報が異常になる可能性もあります。したがって、iOS で HTTPDNS を統合する際には、ネットワークリクエストの信頼性とセキュリティを確保するために、これらの問題に的を絞って対処する必要があります。
3. 推奨ソリューション:EMASCurl との統合
実際の iOS アプリケーションでは、HTTPS が標準であり、CDN、単一 IP での複数ドメイン、SNI を含むシナリオが一般的です。これらの課題に対処するため、Alibaba Cloud EMAS チームは libcurl 上に構築された iOS ネットワークライブラリである EMASCurl を提供しています。これは HTTPDNS と直接連携し、iOS での IP 直連を実装するための推奨ソリューションです。コードはオープンソースで、GitHub で入手できます。
このセクションでは、EMASCurl ソリューションのユースケース、統合アプローチ、および使用上の注意について説明します。具体的なインストール手順、インターセプト設定、API の使用方法については、GitHub の README ファイルをご参照ください。
インストールとインターセプト
主に 2 つの方法で使用できます。
1) 特定の
NSURLSessionConfigurationで作成されたNSURLSessionからのリクエストをインターセプトする。2) システムのグローバルな
[NSURLSession sharedSession]からのリクエストをインターセプトする。
HTTPDNS との統合
EMASCurlProtocolDNSResolverプロトコルを実装して、HTTPDNS の解決結果を EMASCurl に渡します。resolveDomain:メソッドで、[HttpDnsService resolveHostSyncNonBlocking:]を呼び出して IP アドレスを取得します。その後、それを EMASCurl に返して SNI の設定とリクエストの送信を処理させます。
証明書検証
EMASCurl は
libcurlの証明書検証メカニズムに依存しています。提供されているインターフェイスを通じて証明書検証を拡張またはカスタマイズし、サービスの要件を満たすこともできます。
HTTP/3 のサポート
EMASCurl は、QUIC 対応の
libcurlをベースにしたカプセル化バージョンである EMASCurl/HTTP3 を提供しています。必要に応じてこれを統合することで、追加の適応なしに HTTP/3 の機能を使用できます。
以下の例は、EMASCurl を HTTPDNS と統合するための基本的な方法を示しています。
@interface MyDNSResolver : NSObject <EMASCurlProtocolDNSResolver>
@end
@implementation MyDNSResolver
+ (nullable NSString *)resolveDomain:(nonnull NSString *)domain {
HttpDnsService *httpdns = [HttpDnsService sharedInstance];
HttpdnsResult *result = [httpdns resolveHostSyncNonBlocking:domain
byIpType:HttpdnsQueryIPTypeBoth];
if (!result || (!result.hasIpv4Address && !result.hasIpv6Address)) {
return nil;
}
NSMutableArray<NSString *> *allIPs = [NSMutableArray array];
if (result.hasIpv4Address) {
[allIPs addObjectsFromArray:result.ips];
}
if (result.hasIpv6Address) {
[allIPs addObjectsFromArray:result.ipv6s];
}
return [allIPs componentsJoinedByString:@","];
}
@end
- (void)setupEMASCurl {
EMASCurlConfiguration *config = [EMASCurlConfiguration defaultConfiguration];
// Configure the HTTPDNS resolver
config.dnsResolver = [MyDNSResolver class];
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
// Install EMASCurl
[EMASCurlProtocol installIntoSessionConfiguration:sessionConfig
withConfiguration:config];
// Create a specific session
self.session = [NSURLSession sessionWithConfiguration:sessionConfig];
// Use this session for subsequent requests
}利点:
iOS 開発者にとって馴染みのある API で
libcurlの下位レベルの機能をカプセル化しています。DNS フックメカニズムを通じて HTTPDNS と簡単に統合できます。
SNI シナリオにおけるドメイン転送と証明書検証の両方の問題を解決し、統合コストを削減します。
HTTP/3 をすぐにサポートし、カスタムコンパイルや適応は不要です。
欠点:
サードパーティライブラリ (EMASCurl および libcurl) に依存します。互換性とバージョンのアップグレードに注意する必要があります。
複雑な HTTP 機能やカスタム要件の場合、サービスの可用性を確保するために EMASCurl の内部実装を理解する必要がある場合があります。
EMASCurl を統合する際は、リダイレクト、Cookie、同時リクエストなど、一般的な HTTP/HTTPS 機能をテストして、サービスの要件を満たしていることを確認してください。
ビジネスに厳格なセキュリティまたはネットワークパフォーマンス要件がある場合は、現在の iOS バージョンでの EMASCurl のパフォーマンスを評価してください。
Wi-Fi、セルラーネットワーク、プロキシなど、さまざまなネットワーク環境でリクエストとハンドシェイクが正常に完了することを確認してください。
4. 簡略化されたシナリオ向けの統合ソリューション
平文 HTTP または SNI を使用しない HTTPS のシナリオでは、通常、システムの組み込み NSURLSession と通常のネットワークリクエストロジックを引き続き使用し、簡単な調整で HTTPDNS を統合できます。これらのソリューションは実装がより簡単で、特定のシナリオに適していますが、適用範囲は限定的です。
平文 HTTP シナリオでは TLS ハンドシェイクや証明書検証は行われませんが、SNI を使用しない HTTPS シナリオでは、依然として証明書検証を処理する必要があります。これは NSURLSession の検証プロセスをフックすることで実行できます。
4.1 平文 HTTP シナリオ
平文 HTTP リクエストの場合、TLS/SSL ハンドシェイクや証明書検証は存在しません。したがって、HTTPDNS を統合するためのコア操作は HTTP レイヤーでのみ行われます。
リクエスト URL の Host を HTTPDNS からの IP アドレスに置き換える
例えば、元のリクエスト URL が
http://example.com/apiで、HTTPDNS がそれを IP アドレス1.2.3.4に解決した場合、URL をhttp://1.2.3.4/apiに変更します。
Hostヘッダーを元のドメインに明示的に設定するNSMutableURLRequestを使用する場合、リクエストヘッダーにrequest.allHTTPHeaderFields[@"Host"] = @"example.com";を追加できます。これにより、サーバーはアプリケーション層で正しいドメインを識別できます。
利点:実装が簡単です。既存の HTTP リクエストで Host を置き換え、ヘッダーを設定するだけで、開発工数は最小限です。
欠点:平文 HTTP にのみ適用可能です。HTTPS シナリオでの証明書検証や SNI 関連の問題は解決できません。
4.2 SNI を使用しない HTTPS シナリオ
SNI を使用しない、または証明書でカバーされるドメインが数個の固定ドメインのみである HTTPS サイトの場合、以下のように NSURLSession レイヤーで HTTPDNS を統合し、証明書検証を実行できます。
リクエスト URL の Host を HTTPDNS からの IP アドレスに置き換える
例えば、元のリクエスト URL が
https://example.com/apiで、HTTPDNS がそれを IP アドレス1.2.3.4に解決した場合、URL をhttps://1.2.3.4/apiに変更します。
Hostヘッダーを元のドメインに明示的に設定する同様に、
NSMutableURLRequestでrequest.allHTTPHeaderFields[@"Host"] = @"example.com";を設定できます。
証明書検証プロセスをフックする
これは HTTPS リクエストであるため、TLS ハンドシェイク中に証明書検証が必要です。この時点で、IP アドレスを Host として検証に使用すると、ドメインと証明書の不一致が発生します。
NSURLSessionDelegateのコールバックメソッドURLSession:didReceiveChallenge:completionHandler:で、システムから取得したserverTrustを検証する際に、IP アドレスを元のドメイン (example.com) に置き換えることができます。これにより、証明書検証が成功します。コード例:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { NSString *originalHost = [self getOriginalHostFromRequest:task.originalRequest]; SecTrustRef serverTrust = challenge.protectionSpace.serverTrust; if ([self evaluateServerTrust:serverTrust forDomain:originalHost]) { // The certificate is verified. NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { // Certificate verification failed. Use the default handler. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { // Create a certificate verification policy. NSMutableArray *policies = [NSMutableArray array]; if (domain) { [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)]; } else { [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()]; } // Bind the verification policy to the server certificate. SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies); // Evaluate whether the current serverTrust is trusted. Apple recommends that the serverTrust can be verified if the result is kSecTrustResultUnspecified or kSecTrustResultProceed. For more information, see https://developer.apple.com/library/ios/technotes/tn2232/_index.html. // For more information about SecTrustResultType, see SecTrust.h. SecTrustResultType result; SecTrustEvaluate(serverTrust, &result); return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); }
利点:
サードパーティライブラリを追加することなく、ネイティブの
NSURLSessionとその証明書検証ロジックを使用できます。実装コストは管理可能であり、SNI を伴わない、または少数のドメイン証明書のみを必要とするシナリオに適しています。
欠点:
このアプローチは SNI シナリオを処理しません。CDN シナリオのように、同じ IP アドレスに複数のドメインがデプロイされている場合、サーバーが誤った証明書を返すため、ハンドシェイクは依然として失敗します。
5. HTTPS + SNI シナリオ向けのその他のソリューション
HTTPS + SNI シナリオでは、EMASCurl の他に、より低レベルの機能を使用して HTTPDNS の統合を実装することもできます。これらのソリューションは通常、より高い柔軟性を提供しますが、開発およびメンテナンスコストも高くなります。これらは、既存のカスタムネットワークスタックがある、より強力な低レベルの制御が必要、またはクロスプラットフォームの統一要件があるシナリオに適しています。特別な要件がない場合は、EMASCurl ソリューションの使用を推奨します。
5.1 カスタム NSURLProtocol
iOS では、NSURLProtocol をサブクラス化することで、システムによって開始されるネットワークリクエストをインターセプトし、より低いレベルで HTTP/HTTPS リクエストロジックを自分で実装することができます。CFNetwork や NSInputStream/NSOutputStream などのインターフェイスを使用して、すべてのネットワーク操作を手動で完了できます。
リクエストのインターセプト
canInitWithRequest:メソッドで、現在のリクエストをインターセプトするかどうかを決定します。startLoadingメソッドで、元のリクエスト URL のドメインを IP アドレスに置き換えます。後続の証明書検証と SNI 設定のために、元のドメインを保持する必要があります。
SNI の設定
CFStream関連の API またはSecureTransportインターフェイスを使用して、kCFStreamSSLPeerNameを元のドメインとして指定します。これにより、下位レイヤーが SSL ハンドシェイク中に正しいドメイン情報を含めることが保証されます。
証明書の検証
証明書検証プロセスを手動で実行し、証明書に含まれるドメインが元のドメインと一致することを確認します。
利点:サードパーティライブラリに依存しません。完全に低レベルのシステム API に基づいており、高い柔軟性を提供します。
欠点:実装コストが高いです。リダイレクト、Cookie、キャッシング、エンコーディング、トラフィック統計などを手動で処理する必要があります。接続の再利用をサポートしていないため、パフォーマンスは平均的です。また、メンテナンスリスクが高く、システムやネットワーク環境がアップグレードされる際には追加の適応が必要です。
参考例として、Alibaba Cloud EMAS は httpdns_ios_demo 内に HttpDnsNSURLProtocolImpl.m という名前のサンプル実装を提供しています。サービスの要件に基づいて変更または再利用できます。
5.2 libcurl の使用
libcurl は C で実装されたクロスプラットフォームのネットワークライブラリです。SSL ハンドシェイク中に正しいドメイン情報を渡すために SNI フィールドを手動で設定することをサポートしています。これは、複数のドメインが同じ IP アドレスを共有するシナリオでの証明書検証に必要です。一般的なプロセスは次のとおりです。
ドメインを解決して対応する IP アドレスを取得する
例えば、
resolveHostSyncNonBlocking:のような HTTPDNS API メソッドを使用して、ターゲットドメインの IP アドレスを取得します。
SNI と IP マッピングを設定する
CURLOPT_RESOLVEまたは別の API を使用して、「domain:port:resolved_IP」マッピングを curl の内部 DNS キャッシュに書き込みます。CURLOPT_URLには元のドメインを引き続き使用します。これにより、TLS ハンドシェイクに正しいドメイン情報が含まれることが保証されます。
証明書を検証する
libcurlはデフォルトで証明書検証を有効にします。必要に応じて、対応するコールバックを使用して証明書をより詳細にチェックすることもできます。
以下のコードスニペット (擬似コード) は、iOS で libcurl と HTTPDNS の解決結果を使用してリクエストを完了する方法を示しています。
CURL *curl_handle = curl_easy_init();
if (curl_handle) {
// For example, get IP = 1.2.3.4 from HTTPDNS, target domain = example.com, and port = 443
struct curl_slist *dnsResolve = NULL;
dnsResolve = curl_slist_append(dnsResolve, "example.com:443:1.2.3.4");
// Set the domain-to-IP mapping
curl_easy_setopt(curl_handle, CURLOPT_RESOLVE, dnsResolve);
// Continue to use the original domain as the URL
curl_easy_setopt(curl_handle, CURLOPT_URL, "https://example.com");
// Enable SSL verification
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L);
// Initiate the request
CURLcode res = curl_easy_perform(curl_handle);
// Check the result
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
// Clean up
curl_easy_cleanup(curl_handle);
curl_slist_free_all(dnsResolve);
}利点:
成熟しており安定しています。豊富なプロトコルをサポートし、複雑なネットワーク環境に適応できます。SNI シナリオを組み込みでサポートしています。
欠点:
iOS プロジェクトに
libcurlをコンパイルする必要があり、純粋な C インターフェイスを使用するため、Objective-C/Swift 開発者には学習曲線があります。Cookie、リダイレクト、キャッシングなど、カスタムリクエストフローや HTTP ロジックのカプセル化も処理する必要があります。
6. まとめ
SNI、複数ドメイン名、証明書検証をサポートする必要があるかどうかなど、ビジネスニーズに基づいてソリューションを選択できます。以下の表は、さまざまなソリューションを比較したものです。
ソリューション | シナリオ | 利点 | 欠点 |
EMASCurl (推奨) | すべてのシナリオ iOS での簡単な統合向け | - libcurl の優れたラッパーを提供 - HTTPDNS との簡単な統合 - SNI と証明書検証を実装 - HTTP/3 をサポート | - サードパーティライブラリに依存し、互換性とアップグレードに注意が必要 - 特別な要件がある場合、カスタマイズのためにソースコードを読む必要がある場合がある |
Host とヘッダーのみを設定 | 平文 HTTP シナリオ | - 統合コストが最も低い | - 平文 HTTP プロトコルのみ |
NSURLSession + 証明書検証のフック (Host とヘッダーの設定は依然として必要) | HTTPS (非 SNI) シナリオ | - 統合コストが低い - システム API を使用し、追加のライブラリは不要 | - SNI をサポートしない |
カスタム NSURLProtocol | すべてのシナリオ より柔軟な低レベルの制御が必要な場合 | - 完全に低レベルのシステム API に基づく - 高い柔軟性 | - 開発およびメンテナンスコストが高い - 接続の再利用がなく、パフォーマンスは平均的 - リダイレクト、Cookie、キャッシング、エンコーディングなどの特殊なケースを手動で処理する必要がある |
libcurl | すべてのシナリオ クロスプラットフォームまたはカスタム HTTP フロー | - 成熟しており安定している - SNI フィールド設定と豊富なプロトコルをサポート - 柔軟な証明書検証拡張 | - C インターフェイスは Objective-C/Swift 開発者にとって学習曲線がある - Cookie、リダイレクト、キャッシングなどの手動でのカプセル化が必要 |
ビジネスの複数ドメインのニーズ、ネットワークセキュリティ要件、互換性、メンテナンスコスト、およびサードパーティライブラリの受け入れに基づいてこれらのソリューションを評価できます。ソリューションを公開する前に、最も適切な統合ソリューションを選択し、ネットワークリクエストの可用性とセキュリティを徹底的にテストする必要があります。