構築や調査の過程を記録しています。
このブログについて
Civilization IVのマルチプレイは、GameSpyのサービス終了後、有志によるロビーサーバーの実装によって続けられています。このブログでは、その一つとなるCiv4 Internet Lobbyプロジェクトに取り組む中で分かったことや直したこと等を記録していきます。 扱う予定のテーマ: GameSpyプロトコルの構造 NAT越え(NATNEG)の仕組み 運用上気づいた点
構築や調査の過程を記録しています。
Civilization IVのマルチプレイは、GameSpyのサービス終了後、有志によるロビーサーバーの実装によって続けられています。このブログでは、その一つとなるCiv4 Internet Lobbyプロジェクトに取り組む中で分かったことや直したこと等を記録していきます。 扱う予定のテーマ: GameSpyプロトコルの構造 NAT越え(NATNEG)の仕組み 運用上気づいた点
GameSpy終了後の仕組み オープンソースプロジェクトであるpylobbyは、Civilization IVのマルチプレイ・ロビー機能が依存していたGameSpy SDKのマスターサーバー群を、ゼロから再実装したものである。GameSpyは2010年代にオンラインサービス事業を終了しており、公式のロビーサーバー(gpcm.gamespy.com等)はすでに存在しない。Civ4クライアント側のプロトコル実装は変更できないため、クライアントが「GameSpyのサーバーだ」と信じて話しかけてくる相手を、自前サーバー上に用意するというアプローチを取っている。具体的には、Civ4クライアントのhostsファイルでgpcm.gamespy.com等のホスト名を自前サーバーのIPに向け、そこでpylobbyをベースにしたサーバー実装(本プロジェクト「Civ4 Internet Lobby」)を稼働させている。 GameSpy終了後のなりすまし構成かつてCiv4クライアントは実在するGameSpyのマスターサーバー群と通信していたが、GameSpyサービス終了後はhostsファイルでホスト名を自前サーバーのIPに向けることで、クライアントを改造せずにCiv4 Internet Lobbyへ通信させている。かつて現在(Civ4 Internet Lobby)Civ4クライアントGameSpyマスターサーバー群(gpcm.gamespy.com等)2010年代にサービス終了・現存せずCiv4クライアント(無改造)hostsファイルで書き換えgpcm.gamespy.com → 自前サーバーCiv4 Internet LobbyGameSpyになりすまして応答 クライアント側の実装は変更せず、通信先だけをhostsファイルで自前サーバーへ差し替えている。 Civ4 Internet Lobbyの構成 pylobbyはGameSpy Presence(ログイン)とサーバーブラウザ(部屋一覧)を実装したものだが、NAT越え(NATNEG)機能は含まれていなかった。Civ4 Internet Lobbyではpylobbyのコードをベースに、NATNEGサーバーを新規実装として追加し、実機検証を通じてプロトコルの確定仕様に合わせ込んだ。 ...
システムは独立した2つのプロセス(civgs-login・civgs-gamebrowser)と、別途起動が必要なIRCサーバー(civgs-ircd)から成ります。2つのプロセスは互いに独立して動作しており、共有しているのはコードだけで、プロセス間通信は一切行いません。civgs-ircdが起動していないと、Civ4クライアントはログイン処理を完了できずフリーズしてしまいます。 サーバー構成 Civ4クライアント civgs-login TCP 29900・29901 civgs-gamebrowser TCP 28910 UDP 27900・27901 civgs-ircd TCP 6667 ログイン・検索 部屋一覧・NAT越え 在席確認 プロセス間通信なし (コードのみ共有) Civ4クライアントはcivgs-login・civgs-gamebrowser・civgs-ircdという3つの独立したプロセスにそれぞれ接続する。civgs-loginとcivgs-gamebrowserはコードを共有するだけで、プロセス間通信は行わない。 プロセス ポート 役割 対応するGameSpyドメイン civgs-login TCP 29900 ログイン(GP presence) gpcm.gamespy.com civgs-login TCP 29901 ユーザー検索(GP search) gpsp.gamespy.com civgs-gamebrowser TCP 28910 部屋一覧(ServerBrowser) <エディション>.ms*.gamespy.com等 civgs-gamebrowser UDP 27900 ホストのハートビート(QR) 同上 civgs-gamebrowser UDP 27901 NAT越え(NATNEG) natneg1〜3.gamespy.com civgs-ircd TCP 6667 在席確認・チャット(peerchat) peerchat.gamespy.com 通信シーケンス(部屋参加〜NAT越え) ホストが部屋を作成してから、参加者が直接通信を始めるまでの流れは次の通りです。 ...
この通信は何のためにあるのか Civ4のマルチプレイでロビーにログインする際、クライアントは裏側で複数のGameSpy系サーバーと順番に通信している。その中の1つがpeerchat.gamespy.com(TCP 6667)で、これは目に見えるチャット機能ではなく、ログイン処理の一部として必須のハンドシェイクである。クライアントは接続直後にCRYPTという拡張コマンドを送り、サーバーからの暗号チャレンジ応答(705)を待つ。この一往復が成立しないと、クライアントはロビー画面にたどり着けない。 ログイン処理内でのIRCハンドシェイクの位置づけCiv4クライアントはGameSpy Presence認証に続けてpeerchat/IRC(TCP 6667)へ接続しCRYPTコマンドを送る。705応答が返ればロビー画面へ進み、応答がなければ無応答のままフリーズする。Civ4クライアントGameSpy Presence認証(TCP 29900・ログイン)IRCハンドシェイク(peerchat, TCP 6667)CRYPTコマンド → 705応答を待つ705応答あり→ ロビー画面へ(civgs-ircdが正常稼働)705応答なし→ 無応答のままフリーズ(標準IRCサーバー等) peerchat(TCP 6667)でのCRYPTハンドシェイクが成立しないと、クライアントはエラーも出さずに無応答のままフリーズする。 なぜ普通のIRCサーバーでは動かないのか 一般的なIRCサーバー実装(標準のminiircd本家など)は、GameSpy独自のCRYPTコマンドを理解しない。この場合に起きることは2通りある。 ...
この通信は何のためにあるのか Civ4のマルチプレイでロビーに入るとき、クライアントが最初に話しかける相手がcivgs-loginである。GameSpyは、かつて多くのPCゲームのマルチプレイを支えていたオンラインサービスである。その中で、アカウントの作成、ログイン認証、ユーザーの検索、フレンドの管理といった「誰がオンラインにいるか」を扱う部分が、GameSpy Presence(以下、GP)で、ロビーへの入り口にあたる。本物のGameSpyではgpcm.gamespy.comとgpsp.gamespy.comがこれを受け持っていた。hostsファイルでこの2つの名前をロビーサーバーに向けることで、civgs-loginが代わりに応答している。 ポート 役割 TCP 29900 アカウント作成・ログイン認証 TCP 29901 ユーザー検索 ログインは、パスワードそのものをネットワークに流さないチャレンジ・レスポンス方式で行われる。クライアントが接続すると、サーバーが確認用のデータ(チャレンジ)を送ってくる。クライアントはこれを元にパスワードから計算した値を返し、サーバーも同じ計算をして、一致すれば認証成功となる。 Civ4クライアント civgs-login TCP 29900(ログイン) civgs-login TCP 29901(検索) TCP接続 確認用データ(チャレンジ) サーバーが接続直後に送る ニックネームと応答 チャレンジを元に計算した値 認証結果の通知 成功時はセッション情報を返す TCP接続を張るだけ (検索・バディ追加用) この後、peerchat(TCP 6667)のハンドシェイクへ進む サーバーからのチャレンジに対して、パスワードから計算した値で応答する。29901には接続を張るだけである。 ログイン後、クライアントは続けて29901にTCP接続を張り、さらに、ログイン処理の一部として必須のチャット系の通信であるpeerchat/IRC(TCP 6667)のハンドシェイクへ進む。ここまで通ってはじめて、ロビー画面に到達する。 ...
この通信は何のためにあるのか ロビーに入ったあと、部屋を作る・部屋を探すという操作を受け持つのがcivgs-gamebrowserである。タイトルにあるQRとSBは、ここで使われる、向きの違う2つの通信の略称である。どちらもGameSpyが提供していた仕組みの名前で、ロビー画面の部屋一覧はこの2つで成り立っている。 QRは、Query & Reporting(クエリ・アンド・レポーティング)の略である。部屋を作った側(ホスト)のクライアントが、「こんな部屋を作りました」「この部屋はまだ開いています」と、自分の部屋の状態をサーバーに報告するための仕組みである。 SBは、Server Browser(サーバーブラウザ)の略である。部屋を探す側(参加者)のクライアントが、サーバーから部屋の一覧を取得するための仕組みである。「サーバー」はゲームの部屋のことを指しており、その一覧を眺める機能、という意味の名前である。 略称 ポート 役割 QR UDP 27900 部屋の登録と生存報告 SB TCP 28910 部屋一覧の取得と増減の通知 ホストが部屋を作ると、クライアントはcivgs-gamebrowserのQRに、部屋の情報を報告する。サーバーはこれを部屋の台帳として持ち、参加者がSBで一覧を要求したときに返す。ホストは部屋が開いている間、定期的に報告を続ける。部屋が増えたり減ったりしたときは、一覧を開いている参加者にも通知される。ホストからの連絡がしばらく途絶えた部屋は、台帳から自動的に削除される。 ホスト 部屋を作る人 civgs-gamebrowser 部屋の台帳 1部屋につき1件 連絡が途絶えると 自動的に削除 参加者 部屋を探す人 QR / UDP 27900 登録・生存報告 (定期的に) SB / TCP 28910 一覧の取得 増減の通知 対戦データはホストと参加者の間を直接流れる ゲームブラウザは「相手の住所」を教えるだけで、対戦データは通らない ホストはQRで部屋を登録し、参加者はSBで一覧を取得する。対戦中のデータはゲームブラウザを経由しない。 なぜ普通のサーバーでは動かないのか QRもSBも、GameSpy独自の形式で作られた通信である。特にSBの応答は扱いが厳しく、それらしいデータを返せばよいというものではない。 ...
想定外の使い方で見つかった症状 このロビーは、1つのネットワークにつき1台のPCから遊ぶ使い方を想定している。動作検証で、人数合わせのために同じルーターの内側から2台のPCを同時に使ったところ、片方だけが部屋に参加できない現象が起きた。想定外の使い方ではあるが、原因を調べる過程で、P2P型のゲームが家庭用ルーターの中でどう動くかがよく分かる事例だったので、記録として残す。 症状は次のとおりである。 ロビーには入れる。部屋の一覧も見える。 部屋に参加しようとすると、「プレイヤー○○に接続できませんでした。ファイアウォールの設定が正しくない可能性があります」という趣旨のメッセージが出て、参加できない。 失敗するのは、同じルーターの内側で、後から接続した側のPCだけである。先に接続していたPCは、同じ相手と問題なく通信できている。 同じPCが、別の時間帯には同じ相手の部屋へ問題なく参加できている。つまり、PCやOSの不具合ではなく、そのときの状況で成否が変わる。 用語の整理 以降の説明で使う用語を、先に定義しておく。 ホストと参加者: ホストは、部屋を作った側である。参加者は、その部屋に入ろうとする側である。 P2P(Peer to Peer): サーバーを経由せず、ホストと参加者のPCどうしが、直接通信する方式である。このゲームの対戦中のデータは、この方式で流れる。これはゲームブラウザの記事でも説明している。 IPアドレス: ネットワーク上の機器を識別する、住所にあたる番号である。公開IP(グローバルIP)は、インターネット上から見える住所で、家庭では通常、ルーターが1つだけ持つ。プライベートIPは、家庭内のネットワーク(LAN)の中でだけ通用する住所で、LAN内の各PCに1つずつ割り当てられる。 ポート: 1台の機器の中で、通信を受け取るソフトを区別する番号である。IPアドレスが建物の住所なら、ポートは部屋番号にあたる。 UDP: 通信方式の1つである。接続の確立の手順を踏まず、パケット(通信の1単位となるデータのかたまり)を送りっぱなしにする。反応の速さが求められるゲームの対戦通信で、よく使われる。 ルーターとNAT: 家庭のルーターは、複数のPCが1つの公開IPを共有してインターネットに出られるように、NAT(Network Address Translation、アドレス変換)を行う。NATは、LAN内のアドレスと、公開側のアドレスを、通信ごとに付け替える仕組みである。 通信表(NAT変換表): ルーターが、NATの付け替えの対応を記録している表である。「LAN内のどのPCの、どのポートが、外側のどの相手の、どのポートと通信しているか」と、その通信に割り当てた公開側のポート(公開ポート)が対応づけて記録される。外から届いた通信は、この表に記録のある返事だけが、LAN内に通される。 送信元ポート: 通信を送り出す側が使う、ポート番号である。 固定ポート: ゲームが、毎回同じ番号で使うポートである。 NAT越え(穴あけ): 通信表に記録がないと外から通信を通さないルーターの間で、直接通信を成立させる方法である。サーバーが仲介して、両者が互いの相手に向けて同時に通信を送り出すことで、両方のルーターの通信表に記録を作る。 ポート転送: ルーターに設定する、特定のポートあての通信を、LAN内の決まった1台のPCへ通す設定である。UPnPは、PC上のソフトが、この設定をルーターへ自動で依頼する仕組みである。 ファイアウォール: 許可していない通信を遮断する仕組みである。PCのOSにも、ルーターにも備わっている。 ログ: サーバーが残す動作の記録である。 参加時に何が起きるか ホストと参加者は、そのままでは互いの通信を届けられない。ルーターが、通信表に記録のない外からの通信を通さないためである。そこで、サーバーが仲介して、NAT越えを行う。 次の図は、参加者の視点で、参加を要求してから、直接通信できるようになるまでの流れを示している。矢印1本が、通信または通知の1回にあたる。 参加の要求: 参加者が、サーバーに「この部屋に参加したい」と要求する。 連絡: サーバーが、ホストに「参加者が来ている」と連絡する。 応答: ホストが、サーバーに応答して、仲介の準備ができたことを知らせる。 通知(参加者へ): サーバーが、参加者に「ホストの公開IPと公開ポート」を通知する。 通知(ホストへ): サーバーが、ホストに「参加者の公開IPと公開ポート」を通知する。 送り出し(参加者から): 参加者が、通知されたホストの住所へ、通信を送り出す。 送り出し(ホストから): ホストが、通知された参加者の住所へ、通信を送り出す。手順6と、ほぼ同時に行う。 手順6と7で、双方のルーターに、互いの相手との通信の記録(通信表の記録)ができる。以降、相手からの通信は、その記録に対する返事として通されるので、直接通信できるようになる。 参加者の視点 参加者 PC + ルーター(自分) サーバー 仲介役 ホスト PC + ルーター ① 参加の要求 ② 連絡(参加者が来ている) ③ 応答(準備ができた) ④ 通知:ホストの公開IPとポート ⑤ 通知:参加者の公開IPとポート ここから先は、サーバーを通らない ⑥ 参加者からホストへ、通信を送り出す ⑦ ホストから参加者へ、通信を送り出す(⑥とほぼ同時) 相手に伝えられる住所は「公開IP + 固定のポート」である 参加者の視点での、参加の流れ。矢印1本が、通信または通知の1回にあたる。⑥と⑦は別々の矢印で、互いの相手へ同時に送り出す。 ここで重要なのは、④と⑤で通知される住所のポートが、ゲームの固定ポートである点である。つまり、参加者もホストも、公開IPと固定ポートの組み合わせを、相手に名乗っている。この前提が、次に説明する衝突の原因になる。 ...