このトピックでは、HTTPDNS を iOS アプリに統合する際の IP 直結の実装方法について説明します。iOS での HTTPDNS の統合方法の詳細については、「iOS SDK の統合」をご参照ください。
1. はじめに
モバイルネットワーク環境では、DNS ハイジャックやローカル DNS キャッシュ汚染などの問題により、ドメイン名が正しく解決できず、ネットワークリクエストの失敗につながることがよくあります。このようなシナリオに対し、Alibaba Cloud の HTTPDNS は、信頼性の高い再帰的なドメイン名解決サービスを提供します。このサービスは、モバイルアプリがローカル DNS の潜在的なリスクを回避し、ネットワークリクエストの成功率と安定性を向上させるのに役立ちます。
しかし、iOS プラットフォームで HTTPDNS を利用する場合、リクエストを開始する前に、リクエスト内の元のドメイン名を解決済みの IP アドレスに置き換える必要があります。これは、特に HTTPS や Server Name Indication (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 アdress のみを送信した場合、サーバーはドメイン名に一致する証明書を返すことができません。これにより、SSL ハンドシェイクが失敗します。iOS の
NSURLSessionなどの高レベルのネットワーク API は SNI を直接設定するインターフェイスを公開していないため、SNI の問題は簡単な方法では解決が難しいことが多いです。Host ヘッダーとサービスアドレッシング URL のドメイン名を IP アドレスに置き換えたものの、HTTP リクエストヘッダーの
Hostを元のドメイン名に明示的に設定し忘れた場合、サーバー側は HTTP レイヤーで特定のサイトやリソースを識別できない可能性があります。例えば、CDN シナリオでは、サーバーは Host フィールドに依存して正しいコンテンツを配信します。Host が IP アドレスの場合、サービスは異常になります。基盤となるネットワークライブラリの選択 iOS に組み込まれている
NSURLSessionなどの高レベル API は、SNI のカスタマイズや手動での証明書検証に対する拡張性が限られています。開発者が SNI を処理したり、TLS ハンドシェイクのロジックを変更したりしたい場合は、CFNetworkやlibcurlなどの低レベルのインターフェイスを使用する必要があります。しかし、これにより開発およびメンテナンスコストが増加します。
まとめると、URL のドメイン名を HTTPDNS で解決した IP アドレスに直接置き換えることは、HTTPS シナリオにおいて 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) シナリオ向けの統合ソリューション
SNI (単一 IP に複数の HTTPS ドメイン名) シナリオの場合、単純な 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 アドレスを取得する。
例えば、
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) {
// 例: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 アドレスを取得できます。その後、IP アドレスを EMASCurl に返して、後続の SNI 設定とリクエスト送信を完了できます。
証明書の検証
EMASCurl は
libcurlの証明書検証メカニズムに依存しています。開発者は、ビジネス要件を満たすために、対応するインターフェイスを通じて証明書検証を拡張またはカスタマイズすることもできます。
HTTP/3 のサポート
EMASCurl は、QUIC 対応の
libcurlに基づくカプセル化バージョンである EMASCurl/HTTP3 を提供します。開発者は必要に応じて 統合し、追加の適応なしで HTTP/3 CAPABILITIES を使用できます。
長所:
libcurlの基盤となる機能をカプセル化しており、iOS 開発者にとってより馴染みのある API を提供します。DNS フックメカニズムを通じて HTTPDNS と簡単に統合できます。
SNI シナリオにおけるドメイン名転送と証明書検証の両方の問題を解決し、統合コストを削減します。
HTTP/3 をすぐに利用でき、自己コンパイルや適応は不要です。
短所:
サードパーティライブラリ (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 インターフェイスは Objective-C/Swift 開発者にとって学習曲線がある - Cookie、リダイレクト、キャッシュなどの手動カプセル化が必要 |
EMASCurl | すべてのシナリオ iOS での簡単な統合の場合 | - libcurl の優れたカプセル化 - HTTPDNS との簡単な統合 - SNI と証明書検証を実装 - HTTP/3 をサポート | - サードパーティライブラリに依存し、互換性やアップグレードに注意が必要 - 特別な要件がある場合、ソースコードを読んでカスタム開発が必要になることがある |
ビジネスの複数ドメインのニーズ、ネットワークセキュリティ要件、互換性、メンテナンスコスト、およびサードパーティライブラリの受け入れに基づいてこれらのソリューションを評価できます。ソリューションを公開する前に、最適な統合ソリューションを選択し、ネットワークリクエストの可用性とセキュリティを徹底的にテストする必要があります。