前回までのあらすじ。
- `nodertc/nodertc`を読んでた
 - クライアントとSessionを確立する際に、内部的にいくつかサーバーを立ててた
- STUN
 - DTLS
 - SCTP
 
 - こいつらの詳細を読み進めていく
 
というわけで、まずはSTUNから。
使われ方
- セッション確率の際に用意していたUDPソケットで初期化される
 - 1000ms間隔で`STUN_BINDING_REQUEST`というメッセージを`send()`
 - `STUN_EVENT_BINDING_REQUEST`を待ち受けて、`STUN_BINDING_RESPONSE`を`send()`
 - あとはエラーもいちおうコンソールに出してる
 
というわけで基本的には`StunServer`の`send()`しか呼んでないのでそこを重点的に読みたい。
nodertc/stun
読んだバージョンは`1.3.0`です。
GitHub - nodertc/stun: Low-level Session Traversal Utilities for NAT (STUN) server
`src/index.js`が全てであり、たった90行!かと思いきや、`node_modules`というディレクトリをわざわざ用意してて、そこに依存がいろいろある。
全部の行数を総計すると、2192行もあった・・。
module.exports = { createMessage, createServer, validateFingerprint, validateMessageIntegrity, StunMessage, StunServer, constants, };
何はともあれ、これらを見ていく。
めぼしいクラスとしては`StunMessage`と`StunServer`くらい。
`createXxx()`系もこれらを使ってるだけ、あとは関数と定数なのでスルー。
class: StunServer
- `constructor()`
- NodeRTCのセッションが持ってたUDPソケットを引数にインスタンス化
 - extends `EventEmitter`
 
 - `onMessage()`
- UDPソケットが受け取った`message`をさばくのが主
 - 受け取ったパケットの先頭のbyteが`0..3` === STUNの時だけ、`process()`する
 
 - `process()`
- この時に内部的に扱いたいように、`StunMessage`に変換
 - `type`によって、異なるイベントを`emit()`
 
 - `send()`
- `StunMessage`を`toBuffer()`して送る
 - NodeRTCのセッションが持ってたUDPソケットの`send()`
 
 
UDPに流れてきたパケットのうち、STUNに関するものだけをリレーするのが仕事っぽい。
他に見落としてないなら、なんか冗長な印象。
class: StunMessage
- サーバーがやり取りするSTUNのパケットをいい感じにラップするもの
 - `attribute`という単位で情報が付加されたり削られたり
- ここでは9種類定義されてる
 
 - 内部的にバイナリを w/ スキーマで扱えるライブラリを使ってる
 
あらためて使われ方
- 1000ms間隔で`STUN_BINDING_REQUEST`というメッセージを`send()`
 - `STUN_EVENT_BINDING_REQUEST`を待ち受けて、`STUN_BINDING_RESPONSE`を`send()`
 
この2つの先を追う。
- `send(STUN_BINDING_REQUEST)`される先は、接続してきたピアのUDPソケット
- なので中身はブラウザのWebRTC実装なはず
 - それは追えないにしても、このサーバー側でも同等の実装をしているはず
 
 - それが`STUN_EVENT_BINDING_REQUEST`のイベントを受けてからやってること
 
コードとしてはこう。
this.stun.on(STUN_EVENT_BINDING_REQUEST, (req, rinfo) => { assert(stun.validateFingerprint(req)); assert(stun.validateMessageIntegrity(req, this[_icePassword])); const userattr = req.getAttribute(STUN_ATTR_USERNAME); const sender = userattr.value.toString('ascii'); const expectedSender = `${this[_iceUsername]}:${this[_peerIceUsername]}`; assert(sender === expectedSender); const response = stun.createMessage( STUN_BINDING_RESPONSE, req.transactionId ); response.addAttribute( STUN_ATTR_XOR_MAPPED_ADDRESS, rinfo.address, rinfo.port ); response.addMessageIntegrity(this[_icePassword]); response.addFingerprint(); this.stun.send(response, rinfo.port, rinfo.address); });
というわけで、送られてきたものを検証して返事するのが仕事っぽい。
わかったこと
途中までそもそもこんなコードが必要な理由がわかってなかった。
そもそもP2Pが確立できてるなら、その後もSTUN(自分・相手の居場所を知るという意味で)の仕事なんてないのでは?と。
ただこれは勘違いで、STUNはSTUNでもICEが継続的に接続を検証するために必要な機構の方であり、RFCもあった。
RFC 7675 - Session Traversal Utilities for NAT (STUN) Usage for Consent Freshness
ちなみにログを仕込んでみたら、Chromeからは、2500ms間隔くらいで飛んできてて、Firefoxも5秒間隔でやってると書いてあった。
そのほか知り得たこと
- RFC 5389 - Session Traversal Utilities for NAT (STUN)
- 元となるRFCがあったが、紆余曲折あって範囲が小さくなったらしい
 - RFC 3489 - STUN - Simple Traversal of User Datagram Protocol (UDP) Through Network Address Translators (NATs)
 - 通称も"Simple Traversal of UDP through NAT"から"Session Traversal Utilities for NAT"に
 
 - STUNの実装はサーバーとクライアントと別れる
- `stun:stun.l.google.com:19302`とか指定するやつで動いてるのがサーバーの実装
 - クライアントの実装はブラウザのWebRTCが持ってる(はず)
 - NodeRTCだとサーバー側の仕組はない(いらないから)
 
 
NodeJSで書かれたSTUNのサーバー、クライアントの実装はいくつかった。
読んでみて
- コードは本体より読みにくい
- モジュールをまたがってやり取りされるオブジェクト、型がないので追いかけるのつらい
 - `options`とか言われてもなんやっけ・・ってなる
 
 - STUNはSTUNでも、そういうSTUNではなかった
 
やはりRFCを読むのが先か?感が強まってきたけど、やり取りの仕様(フォーマット)としてのRFCもあれば、その仕様をなぜ使うかどう使うに触れてるRFCもあって、それぞれが体系的にどういう関係性なのかを最初に知らないと、筋道立てて調べられないことに気付いた・・。
まぁひとまず、当初の予定通りNodeRTCのDTLSサーバーの部分を読んでいきます。