1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WebRTCのNAT越え問題を解決する|STUN/TURNサーバーの仕組みと実装

1
Posted at

はじめに

前回の記事では、WebRTCの RTCDataChannel を使ってブラウザ間でP2Pファイル転送を実装しました。

でも、「自分のPCで動いたのに、別のネットワークから接続したら繋がらない」という問題に直面したことはありませんか?

これはNAT(Network Address Translation)の壁です。

この記事では、FileExpress を開発する中で実際に遭遇したNAT越えの問題と、STUN/TURNサーバーを使った解決方法を解説します。


なぜP2Pは「繋がらない」ことがあるのか?

NATの仕組み

自宅やオフィスのPCは、プライベートIPアドレス(192.168.x.x10.x.x.x)を持っています。このIPは外部から直接アクセスできません。

ルーターがNATを使って、プライベートIPとグローバルIPの変換を行っています。

[PCのIP]          [ルーター]        [インターネット]
192.168.1.100  →  203.0.113.1   →  相手に届く

P2Pの問題

WebRTCでP2P接続を確立するには、お互いのグローバルIPとポート番号を交換する必要があります。

しかし:

  • 自分のグローバルIPを自分では知らない
  • NATはランダムなポートを割り当てる
  • ファイアウォールが外部からの接続をブロックする

この問題を解決するのが、STUNTURNです。


STUNサーバーの仕組み

STUN(Session Traversal Utilities for NAT)は、自分のグローバルIPとポートを教えてくれるサーバーです。

動作フロー

1. クライアント → STUNサーバー: 「私のIPを教えて」
2. STUNサーバー → クライアント: 「あなたのIPは 203.0.113.1:54321 です」
3. クライアントはこの情報をシグナリングサーバー経由で相手に伝える
4. お互いのIPが分かったら直接P2P接続

コード例

const configuration = {
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'stun:stun1.l.google.com:19302' }
  ]
};

const pc = new RTCPeerConnection(configuration);

GoogleのSTUNサーバーは無料で使えます。ただしSTUNだけでは繋がらない場合があるのが問題です。


STUNで繋がらないケース

企業のファイアウォールや、一部のキャリアグレードNATでは、STUNが機能しません。

接続成功率の現実

実際のWebRTC実装での接続成功率(一般的な傾向):

環境 成功率
同じLAN内 ほぼ100%
家庭用ルーター同士 約80%
企業ネットワーク 約50%
対称型NAT同士 ほぼ0%

残りの20〜50%をカバーするためにTURNサーバーが必要になります。


TURNサーバーの仕組み

TURN(Traversal Using Relays around NAT)は、P2Pが失敗したときの中継サーバーです。

[STUN失敗時]
送信側 → TURNサーバー → 受信側
(サーバー経由になるが、繋がる)

TURNサーバーを経由すると帯域コストがかかるため、必ずP2Pを試してから、失敗時のフォールバックとして使うのがベストプラクティスです。

TURNサーバーの設定

const configuration = {
  iceServers: [
    // まずSTUNを試す
    { urls: 'stun:stun.l.google.com:19302' },
    // STUNで失敗したらTURNにフォールバック
    {
      urls: 'turn:your-turn-server.example.com:3478',
      username: 'user',
      credential: 'password'
    }
  ]
};

TURNサーバーの選択肢

サービス 月額コスト 特徴
Twilio NTS 従量課金($0.40/GB〜) 信頼性高い
Metered.ca 無料枠あり(500MB/月) 個人開発向け
coturn(自前) VPSコストのみ 完全制御可能

ICE(Interactive Connectivity Establishment)の仕組み

WebRTCはICEというフレームワークで、自動的に最良の接続経路を選びます。

ICE Candidateの収集

pc.onicecandidate = (event) => {
  if (event.candidate) {
    // 候補が見つかるたびに相手に送信
    signalingChannel.send(JSON.stringify({
      type: 'candidate',
      candidate: event.candidate
    }));
  }
};

収集される候補の種類:

  1. host候補: ローカルIPアドレス(LAN内接続用)
  2. srflx候補: STUNで取得したグローバルIP(インターネット接続用)
  3. relay候補: TURNサーバー経由(フォールバック)

WebRTCは全候補を試して、最も良い接続を自動選択します。


FileExpressでの実装

FileExpressでは以下の戦略を取っています:

// 1. 無料のSTUNサーバーを複数指定(冗長化)
// 2. LAN内は直接接続(最速)
// 3. WAN越えはSTUN経由のP2P(速度維持)
// 4. 企業環境などはTURNフォールバック

const iceServers = [
  { urls: ['stun:stun.l.google.com:19302', 'stun:stun1.l.google.com:19302'] }
  // 必要に応じてTURN追加
];

接続状態の監視

pc.oniceconnectionstatechange = () => {
  switch(pc.iceConnectionState) {
    case 'connected':
    case 'completed':
      console.log('P2P接続確立!');
      showConnectionType(); // relay か direct かを表示
      break;
    case 'failed':
      console.log('接続失敗 - 別の方法を試してください');
      break;
  }
};

// 接続タイプを確認(P2P直接 or TURN経由)
async function showConnectionType() {
  const stats = await pc.getStats();
  stats.forEach(report => {
    if (report.type === 'candidate-pair' && report.state === 'succeeded') {
      console.log('使用中の接続タイプ:', report.remoteCandidateType);
      // 'host' = LAN直接, 'srflx' = P2P, 'relay' = TURN経由
    }
  });
}

まとめ

WebRTCのNAT越え問題をまとめると:

  1. STUNサーバー: 自分のグローバルIPを知るために使う(無料)
  2. TURNサーバー: STUNで繋がらない場合の中継(コストあり)
  3. ICE: 自動的に最良経路を選択するフレームワーク

実用的なP2Pアプリを作るなら、STUNだけでなくTURNも用意することを強くおすすめします。特に企業向けに使うなら必須です。


実際に動作するP2Pファイル転送を試したい方は、ぜひ FileExpress をお使いください。コードも引き続きシリーズで公開していく予定です。

シリーズ:

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?