Open8

OpenSSL QUIC メモ

voluntasvoluntas

概要

OpenSSL 3.2 で QUIC v1 のクライアント、3.3 で QUIC v1 サーバーが実装される予定です。それに伴い、OpenSSL の QUIC 実装についてまとめていきます。

ついでに Erlang/OTP の QUIC 関連のもまとめておきます。

モチベーション

サーバーで利用する QUIC を 1 から実装するのも楽しそうで良いのですが、ビジネスで利用する場合はまずは既存の実装に乗っかるのがよさそうと考えています。

自分たちが利用している Erlang/OTP に QUIC を実装する場合は、1 から Erlang で実装するのと、NIF (Native Implemented Functions) で C/C++/Rust などで書かれた QUIC ライブラリを利用するの二択になります。

であれば、最初から Erlang で利用している OpenSSL に入る QUIC を利用するのがとてもキレイだと判断しました。自分たちの用途だと QUIC の上に HTTP/3 、WebTransport 、そして Media over QUIC Transport を実装する必要があります。

この部分は Erlang でがっつり書いて、性能が必要な QUIC 部分を OpenSSL に依存するという流れで行けるのではないかと考えています。

ゴール

OpenSSL QUIC を利用して Media over QUIC Transport を Erlang/OTP で提供すること。

OpenSSL QUIC

資料

Hidden comment
voluntasvoluntas

Erlang/OTP + OpenSSL QUIC

  • Erlang/OTP では暗号処理に OpenSSL が利用されています
  • 自社では最新の OpenSSL を利用するため、自前ビルドして静的リンクしています
  • NIF を利用する際に C を利用するか Zig を利用するか悩んでいます
voluntasvoluntas

WebTransport over HTTP/2

QUIC だけならいいのですが、WebTransport を利用する際のフォールバック先としての HTTP/2 での利用をどうするか考える必要があります。

OpenSSL では QUIC までしか面倒見てくれないため、WebTransport over HTTP/2 は自前実装する必要があります。この部分は Cowboy の fork を想定しています。もちろん、メインラインへのマージして貰えるのが一番ですが、QUIC 部分が OpenSSL に依存してしまうことを考えると難しいかも知れません。

voluntasvoluntas

$ openssl s_client -quic を試す

  • OpenSSL 3.2.0-beta1
  • mkcert localhost で証明書を作成
  • mkcert -CAROOT
  • quic-go でシンプルな quic サーバーを立てる
$ openssl s_client --connect localhost:4242 -verifyCAfile rootCA.pem \
    -quic -alpn test -keylogfile key.txt

雑に Go で書いた QUIC サーバー

  • 証明書を指定
  • ポートは 4242
package main

import (
	"context"
	"crypto/tls"
	"log"

	"github.com/quic-go/quic-go"
)

func main() {
	tlsCert, err := tls.LoadX509KeyPair("localhost.pem", "localhost-key.pem")
	if err != nil {
		log.Fatal("tls.LoadX509KeyPair: ", err)
	}
	tlsConfig := &tls.Config{
		Certificates: []tls.Certificate{tlsCert},
		NextProtos:   []string{"test"},
	}

	listener, err := quic.ListenAddr(":4242", tlsConfig, nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}

	conn, err := listener.Accept(context.Background())
	if err != nil {
		log.Fatal("Accept: ", err)
	}

	_, err = conn.AcceptStream(context.Background())
	if err != nil {
		log.Fatal("AcceptStream: ", err)
	}
}