console.lealog();

@leader22のWeb系に関する勉強めもブログですのだ

NodeJS製WebRTC DataChannel、NodeRTCのコードを読む Part.3

前回までのあらすじ。

  • `nodertc/nodertc`を読んでた
  • クライアントとSessionを確立する際に、内部的にいくつかサーバーを立ててた
    • STUN: 前回読んだ
    • DTLS: 今回はコレ
    • SCTP
  • こいつらの詳細を読み進めているところ

というわけで、今回はDTLSの部分。
書いてみたら長くなったので、前後編になってます。

使われ方

// STUNの最初の検証が済んでから動く
// this.stun.once(STUN_EVENT_BINDING_RESPONSE, () => this.startDTLS());

startDTLS() {
  console.log('[nodertc][dtls] start');

  const options = {
    socket: this[_usocket],
    certificate: this[_certificate],
    certificatePrivateKey: this[_privateKey],
    checkServerIdentity: certificate =>
      fingerprint(certificate.raw, 'sha256') === this[_peerFingerprint],
  };

  this[_dtls] = dtls.connect(options);

  this.dtls.once('connect', () => {
    console.log('[nodertc][dtls] successful connected!');
  });

  this.dtls.on('error', err => {
    console.error('[nodertc][dtls]', err);
  });

  this.startSCTP();
}
  • `unicast`のUDPソケットで、接続してきたピアとつなぐ
  • D"TLS"なのでもちろん暗号化に関する情報が必要
  • 次につなぐSCTPで`transport`として指定される

というわけで、特別なにかしてるわけではない。土管。

nodertc/dtls

読んだバージョンは`0.5.0`です。

GitHub - nodertc/dtls: Secure UDP communications using DTLS.

`src/index.js`にはじまり、まあ本体はSTUNと同じで`node_modules`というディレクトリの中にある・・。

node_modules
├── cipher
│   ├── abstract.js
│   ├── aead.js
│   ├── defaults.js
│   ├── key-exchange.js
│   ├── null.js
│   └── utils.js
├── filter
│   ├── decoder.js
│   ├── defragmentation.js
│   └── reordering.js
├── fsm
│   ├── protocol.js
│   ├── retransmitter.js
│   └── states.js
├── lib
│   ├── constants.js
│   ├── protocol.js
│   ├── sender.js
│   ├── sliding-window.js
│   └── socket.js
├── session
│   ├── abstract.js
│   ├── client.js
│   └── utils.js
└── utils
    └── debug.js

さすがTLS関連の実装だけあってファイルが多い!

取っ掛かりはモジュールとして、`lib/socket.js`からエクスポートしてる`connect()`関数。

ただ`connect()`では`new Socket(options).connect()`しかしてなかったのでそっちがメイン。
上のレイヤーから渡されてるのは、`unicast`のソケットなので、それも忘れずに追う。

class: Socket

`constructor()`でやってること。

  • extends `Duplex`
    • つまり extends `EventEmitter`でもある
  • `ClientSession`を`session`として初期化
    • `session.on('data', packet => this.push(packet)` = 自身の`Duplex.push()`
    • 'session.on('handshake:finish', () => this.emit('connect'))`
  • 各種`Stream`を`pipeline()`でつないでる
    • `pipeline(writer, socket, onerror);`
    • `pipeline(socket, isdtls, decoder, reorder, defrag, protocol, onerror);`

というわけでこのクラスでは、

  • `ClientSession`がやってること
  • `Stream`の`pipeline()`群
  • `connect()`の中身

この3つを追えばよさそう。

class: ClientSession

その名の通り、DTLSのセッションの本体であり一番実装が重そうなところ。

`session/client.js`が実態、`session/abstract.js`を継承してて、`session/utils.js`が関数群。

`AbstractSession`と`ClientSession`からわかるのは、

  • DTLSのバージョンは`1.2`
  • DTLSなので暗号化まわりのコード
    • `NullCipher`
  • `retransmitter`は再送制御
    • `SlidingWindow`とかも自前実装で持ってる
  • etc...

このあたりはざっくりでもDTLSの実装フローを知ってないと読み解けなさそう。

今のところ、副作用のあるコードにはたどりつけてないので、`session`を引数にもらってる登場人物各種がどこかできっかけを作るはず。

`socket.connect()`

  • `constructor()`で初期化した`ProtocolReader(session)`の`start()`を呼んでる
    • `fsm/protocol.js`
  • この`start()`で、`session.startHandshake()`を呼んでた

`ProtocolReader`をみていく。

class: ProtocolReader

  • DTLSのHandshakeを、内部的なStateを持ちながら順に処理していくクラス
    • `CLIENT_HELLO`やら`SERVER_HELLO`から`CLIENT_FINISHD`まで
    • おそらくこれがRFCに書いてある
    • `FINISHED`に到達したら、さっきの`handshake:finish`が発火してつながる
  • `next(state)`が再帰で回ってStateを更新してくイメージ
  • 受け取った`session`のメソッドも逐次使ってる
    • 上述のとおり最初に`startHandshake()`してる

続 ClientSession

`Socket`で初期化された`session`は、登場人物各種にも引数として渡されてる。

- Socket
  - ClientSession
    - ProtocolReader
    - Sender
    - Decoder
    - Reordering
  - Defragmentation

`Socket`の`constructor()`で渡される依存関係はこんな感じ。
`Defragmentation`だけ`session`を受け取らない。

`ClientSession`内の目ぼしいクラスは以下。

  • `RetransmitMachine`
    • `fsm/retransmitter.js`
  • `SlidingWindow`
    • `lib/sliding-window.js`

class: RetransmitMachine

class: SlidingWindow

  • Anti-Replayの仕組み
  • 逐次シーケンスNoを入れてあって、なんかのタイミングでチェックしてる
    • 並び替えとか再送とか

後編へつづく

長くなってきたのでこのへんで。

今回はDTLSソケットがつながる過程で用意される`ClientSession`でやってることの途中まで読んだ。

長くなりそうな`pipeline()`をつなげてるところは後編で。