クラウドMacでiOS IPv6-onlyネットワーク互換性ゲートを構築する

CI/CD ·約 11 分

クラウドMacでiOS IPv6-onlyネットワーク互換性ゲートを構築する

モバイルネットワーク、企業 Wi-Fi、通信事業者のテスト環境を IPv6-only に切り替えたとき、最もよく起きる問題は「まったく通信できない」ことではありません。ログインには成功するのに画像をアップロードできない、最初のリクエストは成功するのに再接続がタイムアウトする、あるいは古い API の設定に IPv4 アドレスがハードコードされたままになっている、といった障害です。クラウド Mac を使えば、こうした断続的な問題を再現可能なゲートに変えられます。マージのたびにアドレスリテラルをスキャンし、DNS64 の名前解決条件を確認し、ネットワークテストを実行して、失敗時の情報をアーカイブできます。

ゲートの適用範囲を先に定義する

IPv6-only 互換性を確保するために、サーバーが AAAA レコードのみを提供する必要はありません。テストネットワークに DNS64/NAT64 が用意されていれば、ドメイン名を使用し、システムリゾルバーに名前解決を任せるクライアントは、IPv4 のみを提供するサービスにもアクセスできます。本当に危険なのは、名前解決後の IPv4 アドレスをキャッシュする、AF_INET の使用を強制する、192.0.2.10 のようなアドレスをアプリケーション設定に直接記述するなど、クライアントがシステムの名前解決フローを回避する実装です。

ゲートは次の 3 層に分けることを推奨します。

レイヤー 検査内容 失敗時に保存する情報
静的検査 IPv4 リテラル、アドレスファミリー定数、独自実装のソケット ファイル名と行番号
環境の事前検査 DNS、デフォルトルート、対象ドメインの名前解決 scutil と名前解決結果
動作テスト ログイン、アップロード、再試行、長時間接続の復旧 テストログと結果バンドル

ping の成功をアプリケーションの受け入れ基準にしてはいけません。実際のアプリケーションは HTTPS、WebSocket、ファイルアップロードを使用します。リクエスト経路、タイムアウト方針、接続の再利用によって、異なる結果になる可能性があります。

リポジトリ内の IPv4 前提をスキャンする

まず、CI ワークスペースで保守的なスキャンを実行します。次の正規表現は候補を抽出するためのものであり、すべての一致をそのまま不具合と判断してはいけません。テストフィクスチャ、ドキュメント、ループバックアドレスには正当な用途があるためです。

set -euo pipefail

mkdir -p artifacts/ipv6
rg -n \
  --glob '*.{swift,m,mm,h,plist,json,yaml,yml,xcconfig}' \
  '([0-9]{1,3}\.){3}[0-9]{1,3}|AF_INET([^6]|$)|sockaddr_in([^6]|$)' \
  . | tee artifacts/ipv6/ipv4-candidates.txt

if rg -n \
  --glob 'Sources/**' \
  --glob 'Config/**' \
  'https?://([0-9]{1,3}\.){3}[0-9]{1,3}' .; then
  echo "Production configuration contains an IPv4 URL" >&2
  exit 1
fi

候補一覧は一度人手で分類し、許可する項目を理由付きのホワイトリストに追加します。Tests ディレクトリ全体を除外してはいけません。テスト用の補助コードが、そのまま本番のネットワーク層へコピーされる可能性があるためです。C または Objective-C のラッパーでは、特に getaddrinfo の ai_family に AF_UNSPEC が指定されているかを確認してください。単一のアドレスファミリーを明示的に要求する場合に限り、値を固定します。

誤った修正を避ける

IPv4 アドレスを機械的に IPv4-mapped IPv6 アドレスへ書き換えたり、NAT64 プレフィックスを独自に付加したりしてはいけません。プレフィックスは現在のネットワークによって決まるため、ハードコードすると別のネットワークでは機能しなくなります。正しい方法はドメイン名を保持し、接続時にシステムリゾルバーから現在利用可能なアドレスを取得することです。

DNS64 の条件を事前検査に組み込む

テストを開始する前にネットワーク状態を記録し、環境自体が必要条件を満たしていない場合に大量の誤検知が発生するのを防ぎます。クラウド Mac の実行ノードでは、チームがあらかじめ用意した管理下の DNS64/NAT64 ネットワークを使用してください。CI スクリプトは条件の検証だけを行い、ジョブ内でシステムのネットワーク設定を一時的に変更してはいけません。

set -euo pipefail

OUT="artifacts/ipv6"
mkdir -p "$OUT"

scutil --dns > "$OUT/scutil-dns.txt"
route -n get default > "$OUT/default-route.txt" 2>&1 || true
dscacheutil -q host -a name api.test.example \
  > "$OUT/target-resolution.txt"

if ! grep -Eq 'ipv6_address|ip_address' "$OUT/target-resolution.txt"; then
  echo "Target hostname did not resolve in the test environment" >&2
  exit 2
fi

テスト用ドメインはチームが管理する環境に属し、安定していて機密情報を含まないヘルスチェックレスポンスを返すものにします。公開 Web サイトをゲートの依存先にしてはいけません。外部サービスのレート制限、証明書の変更、地域別ポリシーによって、ビルド結果の再現性が失われるためです。

URLSession で実際の動作を検証する

ネットワークテストでは、「初回の成功」と「失敗後の復旧」の両方の経路を対象にします。リクエストには有限のタイムアウトを設定し、ベース URL は依存性注入で渡してください。テストコードに再びアドレスをハードコードしてはいけません。

import XCTest

final class IPv6ReadinessTests: XCTestCase {
    func testHealthEndpointReturnsExpectedStatus() async throws {
        let value = try XCTUnwrap(
            ProcessInfo.processInfo.environment["TEST_BASE_URL"]
        )
        let baseURL = try XCTUnwrap(URL(string: value))
        let url = baseURL.appending(path: "health")

        let configuration = URLSessionConfiguration.ephemeral
        configuration.timeoutIntervalForRequest = 15
        configuration.timeoutIntervalForResource = 30

        let session = URLSession(configuration: configuration)
        let (_, response) = try await session.data(from: url)
        let http = try XCTUnwrap(response as? HTTPURLResponse)

        XCTAssertEqual(http.statusCode, 200)
    }
}

実行時には、テストプラン、シミュレータのモデル、結果の出力先を固定します。

set -o pipefail

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme AppNetworkTests \
  -testPlan IPv6Readiness \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath artifacts/ipv6/IPv6Readiness.xcresult \
  TEST_BASE_URL='https://api.test.example/' \
  | tee artifacts/ipv6/xcodebuild.log

アプリケーションにアップロードや長時間接続が含まれる場合は、小さなファイルのアップロード、接続切断後の指数バックオフ、フォアグラウンドとバックグラウンドの切り替え後の復旧についてもテストを追加します。アサーションは業務上の結果を対象とし、ローカライズされたエラーメッセージに依存させてはいけません。

失敗時に診断可能な証拠を保存する

ゲートが失敗した場合は、少なくとも静的スキャン結果、DNS の状態、対象ホストの名前解決結果、xcodebuild ログ、xcresult を保存します。ログにはトークン、リクエスト本文、完全な Authorization ヘッダーを記録してはいけません。リクエスト情報を出力する場合は、HTTP メソッド、マスキング済みのパス、ステータスコード、再試行回数、エラードメインだけを記録します。

調査手順は次のように固定できます。

  1. 環境の事前検査に成功しているか、対象ドメインがシステムによって名前解決されているかを確認します。
  2. 失敗したリクエストが、名前解決後にキャッシュされたアドレスではなく、ドメイン名を使用しているか確認します。
  3. 下位レベルのライブラリが AF_INET に制限されていないか、独自に IPv4 ソケットを作成していないか確認します。
  4. 初回接続と再接続の経路を比較し、両方が同じ名前解決方針を使用しているか確認します。
  5. 管理下の DNS64/NAT64 ネットワークに接続した実機で、重要なフローを再検証します。

シミュレータのゲートは高頻度の回帰テストを担当し、実機はリリース前の確認を担当します。両者の役割を分けることで、日常的なビルドを手動操作に依存させずに済み、ネットワークチームもアーカイブされた証拠から、問題が名前解決、接続、またはアプリケーションの再試行レイヤーのどこにあるかを判断できます。VPSGit 上の実行ノードでは、ツールチェーン、テストプラン、環境変数を統一しておくだけで、この一連の検査を継続的かつ再現可能に実行できます。ユーザーから問題が報告されてから、急いで検証環境を構築する必要はありません。

よくある質問

ドメイン名を使えばIPv6-onlyに必ず対応できますか?

いいえ。サービスがDNS64/NAT64経由で到達可能であり、アプリがIPv4アドレスを保存したり、AF_INETを強制したり、システムリゾルバーを迂回したりしないことが必要です。

iOSシミュレータだけでIPv6-onlyテストを完了できますか?

継続的な回帰には有効ですが、リリース前には管理されたDNS64/NAT64ネットワーク上の実機で、ログイン、アップロード、コールバック、長時間接続の復旧も確認します。

専有物理ノード

次のビルドや推論タスクに向けてクラウドMacを構成

3種類のApple Silicon構成、6つのノード、固定利用期間から選択できます。実際の利用可能状況はコンソールのリアルタイム表示をご確認ください。

クラウドMacを構成