GoでQuicTransport

けちゃっぷ @kechako

QuicTransportとは?

QuicTransportとは?

  • WebTransportのプロポーサルの一部分。

  • アプリケーションが、QUICを使用してリモートサーバーに直接接続可能なAPI。

  • 信頼性の無いdatagram APIと信頼性のあるstream APIによるデーター転送をサポート。

  • WebSocketのような目的で使用できる(APIはWebSocketよりモダンな実装)。

  • まだDraftだが実装は始まっており、Google Chrome M84からOrigin Traialとして利用可能。

WebTransportとプロトコル

  • WebTransport

  • QuicTransport

  • Http3Transport

  • Http2Transport

WebTransport登場の背景

WebTransport登場の背景

  • クライアント・サーバー間の双方向ストリームは、WebSocketに依存。

  • WebSocketは単一の順序付けされたストリームのみ提供。

  • そのため、head-on-lineブロッキング (HOLB) の影響を受ける。

  • レイテンシーの影響を受けやすいアプリケーションに適さない。

WebRTC

  • WebSocket以外に、Web開発者が他に利用可能なクライアント・サーバ間通信の手段としては、WebRTCがある。

  • 理論的には、双方向ストリームに使用可能。

  • しかし、ブラウザ以外でのWebRTCの実装が多くないため、ブラウザ・ブラウザ間の通信以外での使用は非現実的。

WebSocket over HTTP3

  • HTTP2と同じようにHTTP3でWebSocketを階層化する方法。

  • OLBは回避できるがいくつか欠点がある。

    • 新しいストリームごとにWebSocketのハンドシェイクが必要。

    • クライアントのみがストリームを開始可能。

    • その他の代替通信モード(QUIC Datagramフレームなど)を使用できない。

WebTransportで解決

WebTransport は、以下のような単一のトランスポートオブジェクトを作成することで、以上の問題を解決。

  • 単一のコンテキスト(SCTP、HTTP/2、QUICなどと同様の)で多重化された複数のストリーム。

  • 信頼性の低いデータグラム(UDPと同様の)の送信にも使用可能。

トランスポートの特性

トランスポートの特性1

各トランスポートには、以下の特性を関連付けることができる。

Stream Independence

ストリームの独立性。異るストリーム間にHOLBが存在しない。

Partial reliability

部分的な信頼性。ストリームがリセットされた場合データは再送信されない。Datagramが再送されない。

トランスポートの特性2

Pooling support

プーリングのサポート。複数のトランスポートが同じトランスポート層接続を共有し、輻輳制御と他のコンテキストを共有する可能性がある。

Connection mobility

接続モビリティ。クライアントとサーバー間のネットワークパスが変更されても、トランスポートが存続し続ける可能性がある。

トランスポートの特性3

QuicTransportHttp3TransportHttp2Transport

Stream independence

×

Partial reliability

×

Pooling support

×

Connection mobility

実装依存

実装依存

×

GoでQuicTransportサーバーの実装

GoのQUIC実装

  • QUICの実装が既にあるなら、少しの変更で実装可能。

  • GoのQUIC実装は github.com/lucas-clemente/quic-go を使用。

  • ALPNの指定やClient Indicationのやりとり以外はまんまQUIC。

  • URIスキームは、「quick-transport://〜」(ドラフト)。サーバーはほぼ関係ない。

ALPNの指定

ALPNに「wq-vvv-01」を指定する。

const alpnQuicTransport = "wq-vvv-01"

func generateTLSConfig(certFile, keyFile string) (*tls.Config, error) {
	cert, err := tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return nil, fmt.Errorf("failed to load x509 key pair: %w", err)
	}

	return &tls.Config{
		Certificates: []tls.Certificate{cert},
		NextProtos:   []string{alpnQuicTransport},
	}, nil
}

Client Indicationのチェック

  • コネクション確立後、クライアントから単方向ストリームでClient Indicationを送信する。

    • Origin: クライアントが接続を開始したOrigin。

    • Path: URIのパス部分 + Query部分。パス部分が存在しない場合は「/」。

  • サーバーは単方向ストリームにて受信したClient Indicationをパースする。

  • Client Indicationが正しいかチェックする(Originは許可されているか?)。

準備完了!

これで、QuicTransportの接続が確立。 以降、必要に応じて、ストリームやDatagramで転送を行う。

QuicTransportをJavaScriptから使用する

接続の開始

// URLを指定して接続を開始する
var transport = new QuicTransport(
  ’quic-transport://localhost:4433/counter’);
// 接続完了を待機
await transport.ready;

Datagram の送信

// 送信するデータはUint8Array
let encoder = new TextEncoder('utf-8');
let rawData = 'Sending text';
let data = encoder.encode(rawData);
// Datagram の送信
var writer = transport.sendDatagrams().getWriter();
await writer.write(data);

Datagram の受信

// Datagramの受信
let reader = transport.receiveDatagrams().getReader();
let decoder = new TextDecoder('utf-8');
while (true) {
  let result = await reader.read();
  if (result.done) {
    break;
  }
  let data = decoder.decode(result.value);
  console.log(data);
}

単方向ストリームによる送信

// 単方向ストリームによる送信
let stream = await transport.createSendStream();
let writer = stream.writable.getWriter();
await writer.write(data);
writer.close();

単方向ストリームによる受信待機

// 単方向ストリーム受信待機
let reader = transport.receiveStreams().getReader();
while (true) {
  let result = await reader.read();
  if (result.done) {
    return;
  }
  let stream = result.value;
  // 単方向ストリーム受信
}

単方向ストリームによる受信

let decoder = new TextDecoderStream('utf-8');
// 単方向ストリームによる受信
let reader = stream.readable.pipeThrough(decoder).getReader();
while (true) {
  let result = await reader.read();
  if (result.done) {
    return;
  }
  let data = result.value;
  console.log(data);
}

双方向ストリームによる送信

// 双方向ストリームの生成
let stream = await transport.createBidirectionalStream();
// 双方向ストリームによる送信
let stream = await transport.createBidirectionalStream();
let writer = stream.writable.getWriter();
await writer.write(data);
writer.close();

双方向ストリームによる受信待機

// 単方向ストリーム受信待機
let reader = transport.receiveBidirectionalStreams().getReader();
while (true) {
  let result = await reader.read();
  if (result.done) {
    return;
  }
  let stream = result.value;
  // 双方向ストリーム送受信
}

双方向ストリームによる受信

let decoder = new TextDecoderStream('utf-8');
// 双方向ストリームによる受信
let reader = stream.readable.pipeThrough(decoder).getReader();
while (true) {
  let result = await reader.read();
  if (result.done) {
    return;
  }
  let data = result.value;
  console.log(data);
}

デモ

本日のデモ

QuicTransportのサンプル

https://github.com/GoogleChrome/samples/tree/gh-pages/quictransport

GoによるQuicTransportのサーバーサンプルプログラム

https://github.com/kechako/quictransport-sample

QUICの各種Draft

Version-Independent Properties of QUIC

https://tools.ietf.org/html/draft-ietf-quic-invariants-10

QUIC: A UDP-Based Multiplexed and Secure Transport

https://tools.ietf.org/html/draft-ietf-quic-transport-29

QUIC Loss Detection and Congestion Control

https://tools.ietf.org/html/draft-ietf-quic-recovery-30

QUICの各種Draft

Using TLS to Secure QUIC

https://tools.ietf.org/html/draft-ietf-quic-tls-30

Hypertext Transfer Protocol Version 3 (HTTP/3)

https://tools.ietf.org/html/draft-ietf-quic-http-30

QPACK: Header Compression for HTTP/3

https://tools.ietf.org/html/draft-ietf-quic-qpack-17

QUICの各種Draft

QUIC-LB: Generating Routable QUIC Connection IDs

https://tools.ietf.org/html/draft-ietf-quic-load-balancers-04

An Unreliable Datagram Extension to QUIC

https://tools.ietf.org/html/draft-ietf-quic-datagram-01

Compatible Version Negotiation for QUIC

https://tools.ietf.org/html/draft-ietf-quic-version-negotiation-01

WebTransportの各種Draft

WebTransportの各種Draft

その他リンク

QUIC Working Group

https://quicwg.org/

HTTP/3 explained

https://http3-explained.haxx.se/ja

Experimenting with QuicTransport

https://web.dev/quictransport/