このトピックでは、HTTPDNS を iOS アプリに統合する際に、ダイレクト IP 接続を使用するためのソリューションについて説明します。SDK の統合方法の詳細については、「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 アドレスに置き換えると、しばしば以下の問題が発生します。これらの問題は、TLS/SSL レイヤーや HTTP レイヤーなど、HTTPS プロトコルのさまざまなレイヤーで Host フィールドがどのように使用されるかと密接に関連しています。
TLS/SSL レイヤー HTTPS シナリオでは、クライアントはまず TLS/SSL ハンドシェイクを実行します。ハンドシェイクでは、URL の Host を使用して次のタスクを完了します。
証明書の検証: サーバー証明書のドメイン名 (Common Name または Subject Alternative Name (SAN)) が、リクエストされた Host と一致することを確認します。
SNI: TLS 接続を確立する際、クライアントはリクエストされたドメイン名情報 (URL の Host) をサーバーに送信します。これにより、サーバーは対応する証明書を返すことができます。
HTTP レイヤー TLS ハンドシェイクが完了した後、クライアントは HTTP リクエストヘッダーに
Hostフィールドを含めます。これにより、リクエストの対象となる特定のサイトまたはリソースをサーバーに伝えます。URL のドメイン名を IP アドレスに置き換えても、HTTP ヘッダーでHostを手動で設定しない場合、サーバーはアクセスされている実際のドメイン名を識別できない可能性があります。これにより、リクエストが失敗したり、異常なコンテンツが返されたりすることがあります。
HTTPS プロトコルスタックのさまざまなレイヤーでの Host の役割に基づくと、URL のドメイン名を HTTPDNS によって名前解決された IP アドレスに単純に置き換えるだけで、次の技術的な問題が発生します。
ドメイン名と証明書の不一致 HTTPS リクエストの場合、HTTPDNS によって名前解決された IP アドレスを URL の Host として直接使用すると、TLS レイヤーは正しい証明書のドメイン名 (Common Name または SAN) と一致させることができません。これにより、SSL ハンドシェイクが失敗します。
SNI の問題 SNI シナリオでは、単一のサーバー IP アドレスが複数のドメイン名の証明書に対応できます。クライアントが SSL ハンドシェイク中に正しいドメイン名情報を送信せず、IP アドレスのみを送信した場合、サーバーはドメイン名に一致する証明書を返すことができません。これにより、SSL ハンドシェイクが失敗します。
NSURLSessionなどの高レベルの iOS ネットワーク API は、直接的な SNI 構成のためのインターフェイスを公開していないため、簡単な方法で SNI 関連の問題を解決することはしばしば困難です。Host ヘッダーとサービスアドレッシング URL のドメイン名を IP アドレスに置き換えるだけで、HTTP リクエストヘッダーの
Hostを元のドメイン名に明示的に設定し忘れた場合、HTTP レイヤーのサーバーは特定のサイトやリソースを識別できない可能性があります。たとえば、CDN シナリオでは、サーバーは Host フィールドに依存して正しいコンテンツを配信します。Host が IP アドレスの場合、サービスの問題が発生する可能性があります。低レベルネットワークライブラリの選択
NSURLSessionなどの iOS に組み込まれている高レベル API は、カスタム SNI や手動での証明書検証のための拡張性が限られています。SNI を処理したり、TLS ハンドシェイクのロジックを変更したりする場合は、CFNetworkやlibcurlなどの低レベルのインターフェイスを使用する必要があります。しかし、これにより開発およびメンテナンスのコストが増加します。
まとめると、URL のドメイン名を HTTPDNS によって名前解決された IP アドレスに直接置き換えると、TLS レイヤーでの証明書検証と SNI に影響を与え、HTTP レイヤーでの Host ヘッダーの問題を引き起こす可能性があります。iOS に HTTPDNS を統合するには、ネットワークリクエストの信頼性とセキュリティを確保するために、これらの問題に対処する必要があります。
3. プレーン HTTP および HTTPS 非 SNI シナリオ向けの統合ソリューション
プレーン HTTP または HTTPS 非 SNI シナリオでは、システムの組み込み NSURLSession と標準のネットワークリクエストロジックを引き続き使用できます。いくつかの簡単な変更を行うだけで済みます。プレーン HTTP シナリオには TLS ハンドシェイクや証明書の検証が含まれないことに注意してください。HTTPS 非 SNI シナリオでは証明書の検証が必要ですが、NSURLSession の検証プロセスをフックすることでこれを処理できます。
3.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に変更します。
HTTP ヘッダーの
Hostを元のドメイン名に明示的に設定します。NSMutableURLRequestを使用する場合は、request.allHTTPHeaderFields[@"Host"] = @"example.com";をリクエスト ヘッダーに追加します。これにより、サーバーはアプリケーション層で正しいドメイン名を識別できます。
長所: 実装が簡単です。既存の HTTP リクエストで Host を置き換えてヘッダーを設定するだけでよく、開発工数は最小限で済みます。
短所: プレーンテキストの HTTP プロトコルにのみ適用されます。HTTPS シナリオでの証明書検証や SNI 関連の問題は解決しません。
3.2 HTTPS 非 SNI シナリオ
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に変更します。
HTTP ヘッダーの
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]) { // 証明書が検証されました。 NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust]; completionHandler(NSURLSessionAuthChallengeUseCredential, credential); } else { // 証明書の検証に失敗しました。デフォルトのハンドラを使用します。 completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } else { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain { // 証明書検証ポリシーを作成します。 NSMutableArray *policies = [NSMutableArray array]; if (domain) { [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)]; } else { [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()]; } // 検証ポリシーをサーバー証明書にバインドします。 SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies); // 現在の serverTrust が信頼できるかどうかを評価します。Apple は、結果が kSecTrustResultUnspecified または kSecTrustResultProceed の場合に serverTrust が検証可能であることを推奨しています。詳細については、https://developer.apple.com/library/ios/technotes/tn2232/_index.html をご参照ください。 // SecTrustResultType の詳細については、SecTrust.h をご参照ください。 SecTrustResultType result; SecTrustEvaluate(serverTrust, &result); return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed); }
長所:
追加のサードパーティライブラリをインポートする必要がありません。ネイティブシステムの
NSURLSessionと証明書検証ロジックを直接使用します。実装コストは比較的手頃です。SNI を含まないシナリオや、少数のドメイン名証明書のみを必要とするシナリオに適しています。
短所:
SNI シナリオを解決できません。CDN シナリオのように、複数のドメイン名が同じ IP アドレスにデプロイされている場合、サーバーが間違った証明書を返すため、ハンドシェイクは依然として失敗します。
4. HTTPS SNI シナリオ向けの統合ソリューション
複数の HTTPS ドメイン名を持つ単一の IP を含む SNI シナリオでは、単純な NSURLSession ソリューションでは SSL ハンドシェイク中に正しいドメイン名情報を送信できません。これにより、ハンドシェイクが失敗します。この問題を解決するには、より低いソケットレベルで SNI フィールドを変更または指定する必要があります。以下の 3 つのアプローチが一般的です。
4.1 カスタム NSURLProtocol の実装
iOS では、開発者は NSURLProtocol から継承することで、システムによって開始されたネットワークリクエストをインターセプトできます。その後、より低いレベルで HTTP/HTTPS リクエストロジックを実装できます。CFNetwork や NSInputStream/NSOutputStream などのインターフェイスを使用して、すべてのネットワーク操作を手動で処理できます。
リクエストをインターセプトする
canInitWithRequest:メソッドで、現在のリクエストをインターセプトするかどうかを決定します。startLoadingメソッドでは、元のリクエスト URL 内のドメイン名を IP アドレスに置き換えます。後続の証明書の検証と SNI 設定のために元のドメイン名を保持します。
SNI を設定する
CFStream関連の API またはSecureTransportインターフェイスを使用して、kCFStreamSSLPeerNameを元のドメイン名として指定します。これにより、基盤となるレイヤーが SSL ハンドシェイク中に正しいドメイン名情報を含めることが保証されます。
証明書の検証
証明書内のドメイン名が元のドメイン名と一致することを確認するために、証明書の検証プロセスを手動で実行します。
長所: サードパーティのライブラリに依存しません。完全に低レベルのシステム API に基づいており、高い柔軟性を提供します。
短所: 実装とメンテナンスのコストが高いです。リダイレクト、Cookie、キャッシング、エンコーディング、トラフィック統計などを手動で処理する必要があります。接続の再利用をサポートしていないため、パフォーマンスは平均的です。また、メンテナンスのリスクが高く、システムやネットワーク環境がアップグレードされた際には追加の適応が必要です。
リファレンス例として、Alibaba Cloud は httpdns_ios_demo で HttpDnsNSURLProtocolImpl.m のサンプル実装を提供しています。必要に応じて変更または再利用できます。
4.2 libcurl を使用してネットワーク リクエストを実装する
libcurl は C で実装されたクロスプラットフォームのネットワークライブラリです。SNI フィールドを手動で設定して、SSL ハンドシェイク中に正しいドメイン名情報を渡すことをサポートしています。これにより、複数のドメイン名が同じ IP アドレスを共有するシナリオで、証明書の検証が成功します。一般的なプロセスは次のとおりです。
ドメイン名を名前解決して、対応する IP アドレスを取得します。
たとえば、HTTPDNS が提供する API (例:
resolveHostSyncNonBlocking:) を使用して、ターゲットドメイン名の IP アドレスを取得します。
SNI と IP マッピングを設定します。
CURLOPT_RESOLVEまたは別の API を使用して、「ドメイン:ポート:名前解決された IP」のマッピングを curl の内部 DNS キャッシュに書き込みます。引き続き元のドメイン名を
CURLOPT_URLのアクセス先として使用します。これにより、TLS ハンドシェイク中に正しいドメイン名情報が含まれることが保証されます。
証明書の検証
libcurlはデフォルトで証明書検証を有効にします。必要に応じて、対応するコールバックを使用して、証明書に対してより詳細なチェックを行うこともできます。
以下のコアコードスニペット (擬似コード) は、HTTPDNS の名前解決の結果を使用して、iOS で libcurl を介してリクエストを完了する方法を示しています。
CURL *curl_handle = curl_easy_init();
if (curl_handle) {
// たとえば、HTTPDNS から IP = 1.2.3.4 を取得し、ターゲット ドメイン = example.com、ポート = 443 とします。
struct curl_slist *dnsResolve = NULL;
dnsResolve = curl_slist_append(dnsResolve, "example.com:443:1.2.3.4");
// ドメインと IP のマッピングを設定します。
curl_easy_setopt(curl_handle, CURLOPT_RESOLVE, dnsResolve);
// URL として元のドメイン名を引き続き使用します。
curl_easy_setopt(curl_handle, CURLOPT_URL, "https://example.com");
// SSL 検証を有効にします。
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYPEER, 1L);
curl_easy_setopt(curl_handle, CURLOPT_SSL_VERIFYHOST, 2L);
// リクエストを開始します。
CURLcode res = curl_easy_perform(curl_handle);
// 結果を確認します。
if (res != CURLE_OK) {
fprintf(stderr, "curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
}
// クリーンアップします。
curl_easy_cleanup(curl_handle);
curl_slist_free_all(dnsResolve);
}長所:
成熟しており安定しています。豊富なプロトコルをサポートし、複雑なネットワーク環境に適応できます。SNI シナリオの組み込みサポートがあります。
短所:
iOS プロジェクトに
libcurlをコンパイルする必要があります。純粋な C インターフェイスは、Objective-C/Swift 開発者にとって学習曲線があります。Cookie、リダイレクト、キャッシングなどのカスタムリクエストフローの処理や HTTP ロジックのラップが必要です。
4.3 EMASCurl を使用する
iOS で直接 libcurl を使用するための参入障壁を下げるために、Alibaba Cloud EMAS チームは EMASCurl ライブラリを提供しています。これは libcurl をラップし、HTTPDNS との直接統合をサポートします。
インストールとインターセプト
使用するには主に 2 つの方法があります: 1) 指定された
NSURLSessionConfigurationから作成されたNSURLSessionをインターセプトする。 2) システムのグローバルな[NSURLSession sharedSession]をインターセプトする。API 情報については、GitHub の README ファイルをご参照ください。
HTTPDNS との統合
EMASCurlProtocolDNSResolverプロトコルを実装して、HTTPDNS 解決結果を EMASCurl に渡します。resolveDomain:メソッドで、[HttpDnsService resolveHostSyncNonBlocking:]を呼び出して IP アドレスを取得します。次に、それを EMASCurl に返して、後続の SNI 設定とリクエスト送信を完了します。
証明書の検証
EMASCurl は
libcurlの証明書検証メカニズムに依存しています。また、対応するインターフェイスを介して証明書検証を拡張またはカスタマイズして、サービスのニーズを満たすこともできます。
長所:
libcurlの低レベル機能をラップします。API は iOS 開発者の慣習により適合しています。DNS フックメカニズムを介して HTTPDNS と簡単に統合できます。
SNI シナリオでのドメイン名渡しと証明書検証の問題を解決し、統合コストを削減します。
短所:
サードパーティのライブラリ (EMASCurl と libcurl) に依存します。互換性、バージョンアップグレード、その他の要因に注意を払う必要があります。
非常に複雑な HTTP 機能やカスタム要件の場合、サービスの可用性を確保するために EMASCurl の内部実装を読んで理解する必要がある場合があります。
EMASCurl を統合する際は、リダイレクト、Cookie、同時リクエストなどの一般的な HTTP/HTTPS 機能をテストして、サービスのニーズを満たしていることを確認してください。
サービスにセキュリティやネットワークパフォーマンスに関する厳しい要件がある場合は、現在の iOS システムバージョンでの EMASCurl のパフォーマンスを評価してください。
Wi-Fi、セルラーネットワーク、プロキシなど、さまざまなネットワーク環境でリクエストとハンドシェイクが正常に完了することを確認してください。
5. まとめ
サービスの SNI、複数のドメイン名、証明書検証をサポートする必要があるかどうかに基づいてソリューションを選択してください。次の表は、各ソリューションを比較したものです。
ソリューション | シナリオ | 長所 | 短所 |
Host とヘッダーのみを設定 | プレーン HTTP シナリオ | - 統合コストが最も低い | - プレーンテキストの HTTP プロトコルにのみ適用 |
NSURLSession + 証明書検証のフック (Host とヘッダーの設定は依然として必要) | HTTPS 非 SNI シナリオ | - 統合コストが低い - 追加のライブラリなしでシステム API を使用 | - SNI をサポートしていない |
カスタム NSURLProtocol | すべてのシナリオ より柔軟な低レベルの制御が必要 | - 完全に低レベルのシステム API に基づく - 自由度が高い | - 開発コストとメンテナンス コストが高い - 接続の再利用がなく、パフォーマンスは平均的 - リダイレクト、Cookie、キャッシング、エンコーディングなどの特殊なシナリオを手動で処理する必要がある |
libcurl | すべてのシナリオ クロスプラットフォームまたはカスタム HTTP フロー | - 成熟して安定している - SNI フィールドの設定が可能で、豊富なプロトコルをサポート - 証明書の検証は柔軟で拡張可能 | - C API は Objective-C/Swift 開発者にとって学習曲線がある - Cookie、リダイレクト、キャッシングなどのロジックをラップする必要がある |
EMASCurl | すべてのシナリオ iOS での簡単な統合の場合 | - libcurl の優れたラッパーを提供 - HTTPDNS との簡単な統合 - SNI と証明書検証を実装 | - サードパーティのライブラリに依存し、互換性やアップグレードに注意が必要 - 特別な要件がある場合、カスタム開発のためにソースコードを読む必要がある場合がある |
複数のドメイン名のサポート、ネットワークセキュリティ要件、互換性、メンテナンスコスト、サードパーティライブラリの使用に関するポリシーなど、ビジネスニーズを評価してください。その後、適切な統合ソリューションを選択します。オンラインにする前に、ネットワークリクエストの可用性とセキュリティを十分にテストしてください。