BLOG

エンジニア

by ryusuke_nakano

モンストのIPv6対応

こんにちは。XFLAG™ スタジオの中野です。普段はモンストのクライアントサイドの開発をしています。今回はモンストをIPv6(NAT64+DNS64)ネットワークに対応させた話をしようと思います。

iOS9のアプリからIPv6(NAT64+DNS64)ネットワークで動作することを求められるようになりました。
モンスターストライクもVer5.5からNAT64+DNS64環境に対応しています。
このエントリではモンストのIPv6対応で苦労した点などを書いてみようと思います。

モンストIPv6対応の課題

モンストをNAT64+DNS64環境で動作させるために解決するべき課題はいくつかありましたが、最大の課題がマルチプレイでした。

モンストはインターネット上に配置したTURNサーバーとTURNプロトコル(RFC 5766)で通信することで、リアルタイムのマルチプレイを実現しています。
しかし RFC 5766はIPv4を前提とするプロトコルなので、IPv6に対応させるためには工夫する必要があります。

TURNをIPv6に対応させる方法

TURNをIPv6に対応させる方法は大きく分けて2つ考えられます。TURNサーバーに直接グローバルIPv6アドレスを付与するか、IPv4アドレスのみ付与してNAT64+DNS64を使うか、です。

TURNサーバーにIPv6アドレスを付与する方法(RFC 6156を使う

ipv64.png

TURNサーバーにIPv4とv6双方のアドレスを付与して、クライアントから TURN Extension for IPv6(RFC 6156)を使って通信する方法です。
この方法には、RFCに沿った実装で実現でき、NAT64+DNS64のないIPv6の環境でもマルチプレイが行えるというメリットがあります。

しかし IPv4/v6 の双方を付与したサーバーをどうやって大量に確保するか?が問題になりました。
現在モンストではTURNサーバーをクラウド上に配置していますが、サーバーにグローバルIPv6アドレスを付与できるクラウドプロバイダは多くありません。
サーバーにグローバルIPアドレスを付与するために、

  • TURNサーバーをデータセンターに配置する
  • グローバルIPv6アドレスをサポートしているクラウドプロバイダを使う

などの選択肢もありますが、モンストは海外展開も行っており、これらの方法が海外で使えない可能性もあるため、今回は別の方法を使うことにしました。

TURNサーバーにIPv4アドレスとドメイン名を付与する方法(NAT64+DNS64を使う)

ipv66.png

TURNサーバーにIPv4アドレスとドメイン名を付与して、クライアントからドメイン名を使って通信する方法です。
DNS64がある環境で名前解決を行うと、IPv4のAレコードしか存在しないドメイン名でもNAT64 IPv6アドレスを返してくれます。
クライアントはNAT64 IPv6アドレスにconnect(2) して通信すれば、NAT64がパケットを変換してIPv4のサーバーに運んでくれます。

しかし TURNはIPアドレスでリレー先ポートを管理するプロトコルです。この方法に対応するには TURNサーバーかクライアントを拡張し、ドメイン名でリレー先ポートを管理するように変更する必要があります。
一方で、グローバルIPv4アドレスが付与されたサーバーとDNSサーバーがあれば環境を構築できるため、データセンターや特定のクラウドプロバイダにロックインされることがなくなります。

今回はインフラ構成の自由度を重視して、こちらの方法を採用しました。

実装

リレー先ポートはIPv4アドレスのまま管理して、クライアントがTURNサーバーに接続する直前(socket(2)でソケットを開く直前)に以下の変換を行い、IPv6アドレスを得る方針で実装しました。

  1. 1) IPv4アドレスを特定のルールでドメイン名に変換(たとえば、 192.0.2.1 を 192-0-2-1.example に変換)
  2. 2) ドメイン名をgetaddrinfo(3)で名前解決してIPv4/v6アドレスを得る
  3. 3) IPv6アドレスが得られた場合はv6に、得られなかった場合はv4に接続

iOS 9.2 以降であれば、ドメイン名を経由せずに getaddrinfo(3) だけで IPv4 から NAT64 IPv6 アドレスに変換できるのですが、iOS 9.1 以下とAndroidに対応するために、必ずドメイン名を経由するようにしています。

実際のコードは以下のようになります(エラー処理は省略しています)。


struct sockaddr_in sin4;  // 変換するIPv4アドレス

// 1) IPv4アドレス => ドメイン名に変換
unsigned char ip[4] = {0};
memcpy(ip, &sin4.sin_addr, sizeof(ip));

char domain[256] = {0};
snprintf(domain, sizeof(domain), "%d-%d-%d-%d.example", ip[0], ip[1], ip[2], ip[3]);

// 2) ドメイン名を名前解決してIPv4/v6アドレスを得る
struct addrinfo hints;
struct addrinfo *addrlist;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;

getaddrinfo(domain, NULL, &hints, &addrlist);

for (struct addrinfo *addr = addrlist; addr != NULL; addr = addr->ai_next) {
    if (addr->ai_family == AF_INET6) {
        // IPv6を取れた場合はNAT64環境下にいる
        // 3) IPv6接続処理
    } else if (addr->ai_family == AF_INET) {
        // 3) IPv4接続処理
    }
}

// Android 5.1より前のlibcでは freeaddrinfo(NULL) がクラッシュするので
// NULL チェックが必要
// ref: https://code.google.com/p/android/issues/detail?id=13228
if (addrlist != NULL) {
    freeaddrinfo(addrlist);
}

検証には社内のNAT64/DNS64環境のネットワークを使いました。このネットワークのおかげで開発時、QA時の環境セットアップの手間が省けて大助かりでした!

まとめ

モンストのIPv6対応話でした。NAT64+DNS64のおかげで、モンストは IPv4端末とIPv6端末間でのマルチプレイも可能になっています。よろしければお試しください!

この記事をシェアする