Help us understand the problem. What is going on with this article?

【HTTP/3】C#でHTTP/3通信してみる その壱 ~OSSの選定からquicheの.lib/.dllビルドまで~

(2019/9/20 ランタイムライブラリの指定関連の記述に誤りがあったので修正しました)

先日 CURL(libcurl) 7.66.0 にて HTTP/3 が experimental (実験的) に導入されました
CURL 以外にも HTTP/3 に対応している OSS は徐々に増え始めており、大変ありがたいことに日本語での「HTTP/3 使ってみた」という記事も増えてきました。
ただ、クライアント側の実装の話はまだあまり出てきていないので、人柱として実装記録を公開してみます。

ゴールを決める

OSS をそのまま叩くだけでは面白みにかけるので、最終的に C# で HTTP/3 通信を行う所までを目標にしてみます。
C# なのは、その後気が乗ったら Unity で使うところまで記事を書きたいなー、という気持ちがあるからです。
全部を一記事にすると非常に長くなりそうなので以下の三部作でお届けの予定です。

  1. HTTP/3 を使える OSS を Windows から呼び出せるように .lib/.dll を作成する
  2. C#から呼び易いような 1 をラッピングする .dll を更に作成する
  3. C# から叩いて通信してみる(サーバ側は自前実装無しの予定)
  4. Unity から使ってみる(気分次第)

HTTP/3 が使える OSS を選ぼう

HTTP/3 そのものを一から実装するのもそのうちやりたいですが、さすがにちょっと重すぎるので、まずは OSS を使ってみるところから始めてみます。

2019/9/16 現在 HTTP/3 を利用可能な OSS はあまり多くありません。
QUIC を利用可能な OSS ならもっといろいろありますが、今回は HTTP/3 を使ってみるのが目的なのでこれらは対象外としています。
HTTP/3 及び QUIC の実装状況は QUICWG の GitHub ページで確認することができるので、気になる人は確認してみてください。

それでは、HTTP/3 対応している OSS を軽く確認してみましょう。
技術書典7 にて頒布予定の 『くいっく』HTTP/3編 を執筆する際に同様の内容を調べたので、そこから抜粋してみます。

quiche

quiche は QUIC,HTTP/3 層を共に提供する Rust 製の OSS です。
大手 CDN 業者として有名な CloudFlare が主導して実装が進められています。
Rust API の上に被せる C言語 製のラッパも提供しているのも特徴で、C/C++ のアプリケーションやライブラリからも簡単に呼び出すことができます。
ライブラリ、クライアント、サーバ全てのモジュールが提供されているので、実験したい場合には quiche のみで完結するのも良い所です。
Android, iOS のビルドについても標準でサポートしているのも強みです。

Flupke

Flupke は Java 製の HTTP/3 クライアントを提供する OSS です。
QUIC 層には Kwik という同じく Java の OSS が使用されています。

nghttp3

nghttp3 は C言語 製の HTTP/3 ライブラリを提供する OSS です。
QPACK や QUIC の実装も独自に行っていますが、QUIC の API 提供はないようです(OSS なので中を見れば使えますが)。
CURL の実装である libcurl の下層(HTTP/2 部)に用いられている nghttp2 のメインコミッターである Tatsuhiro Tsujikawa さんが主導し実装が進められています。

aioquic

aioquic は Python 製の HTTP/3 サーバーとクライアントを提供する OSS です。
早くも draft-23 に対応している辺り、かなりのやる気を感じます。
Windows から手軽に使ってみるならこの aioquic が一番だと思います。
※ちなみに、こいつは 『くいっく』HTTP/3編 では紹介していません(HTTP/3 対応しているのに最近まで気づかず……)

libcurl

libcurl は HTTP/3 層を共に提供する C言語 製の OSS です。
みんな大好き CURL の内部実装であり、HTTP/0.9 ~ 2.0 まで対応しているかなりの歴史のある OSS です。
ただし、libcurl 自体には QUIC 層の実装は含まれておらず、他の OSS を使用する事により機能を実装しています(quiche もしくは ngtcp2 のいずれかを選択可能)。
※これもタイミング的な問題で 『くいっく』HTTP/3編 では紹介していません

選択肢としては以上です。
今回は C# からの呼び出しを目標としているので、 quiche, nghttp3, libcurl あたりがスマートに事が進みそうです。
軽く検討してみた結果、以下の理由により quiche を使うことにしました。

  • libcurl を使うと 2 のラッピング層の実装が省略できそうだが、おいしい所がすべて隠蔽されてしまってつまらない
  • ビルド周りで quiche の方が簡単に .lib/.dll 作れそうな気配

quiche をビルドするまでの道のりを確認

それでは quiche をビルドしてみましょう。
前述した通り、 quiche は RUST 製の OSS ですが、C言語 製のラッパ(quiche.h)も提供してくれています。
筆者は Rust はほとんど触ったことがないレベルなので、今回はこの C言語 ラッパ層を呼び出せるような Windows の動的/静的ライブラリを作成します。
C# から直接 Rust 製の .dll の呼び出しも可能なので、慣れている人はこうしたラッパを作らずにそのまま Rust でビルドしても良いと思います。
(が、C# 層から使い易いようにどちらにせよ一段噛ませた方が良い印象はあります)

ちなみに、手順 2 の C# から呼び出すライブラリ作成段階では .dll を作成する必要がありますが、このレイヤーはその .dll から呼び出す層なので .lib/.dll どちらでも問題ありません。
好みに合わせて作成できるように、当記事では両方の手順を記載しておきます。

quiche のビルド方法の確認

quiche のビルドには cargo build を用います。
cargo build は Rust のビルドシステム&パッケージマネージャである Cargo を使ったビルドコマンドです。
デフォルトの設定では libquiche.a しかビルドしてくれないようなので、 .lib や .dll をビルド可能なように自前で設定してあげる必要があります。
また、quiche は BoringSSL に依存しているので、こちらも同様に .lib/.dll を用意してあげる必要があります。

Calling quiche from C/C++
quiche exposes a thin C API on top of the Rust API that can be used to more easily integrate quiche into C/C++ applications (as well as in other languages that allow calling C APIs via some form of FFI). The C API follows the same design of the Rust one, modulo the constraints imposed by the C language itself.
When running cargo build, a static library called libquiche.a will be built automatically alongside the Rust one. This is fully stand-alone and can be linked directly into C/C++ applications.

BoringSSL をビルドする

と言う訳で、まずは BoringSSL を準備しましょう。
BoringSSL の Windows 版(.lib/.dll)ビルドには以下のモジュールが必要です。

  • CMake (必須)
  • Perl (必須)
  • NASM (必須)
  • C/C++ コンパイラ (必須)
  • Go (必須)
  • Ninja (推奨)

1つずつセットアップしていきましょう。

CMake

3.0 以上が必要です。
https://cmake.org/download/
から Windows 用のインストーラーをダウンロードして展開しましょう。
今回は CMake 3.15.3 を使用しています。

Perl

Perl の最新バージョンが必要です。
Windows では Active State Perl と MSYS Perl が利用できます。
StrawberryPerl も使えるようですが、 CMake と PATH の取り扱いで競合が起きるようで面倒とのことです。
StrawberryPerl は以下のサイトから入手可能です。
https://www.activestate.com/products/activeperl/
アカウント登録をするとリポジトリができるので、「Buildタブ」 ⇒ 「Windows 10 タブ」から好きな形式でダウンロードしましょう。
perl.png
インストーラーから入れると勝手にパスが設定されますが、環境変数 PERL_EXECUTABLE で指定しても OK です。
今回は Active State Perl 5.8 を使用しています。

NASM

BoringSSL は一部にアセンブリを使用しているようで、Windows でビルドするには NASM が必要です。
NASM は Netwide Assembler の略で x86 系を対象としたアセンブラです。
https://www.nasm.us/ からダウンロードしてパスを通しましょう。
今回は NASM 2.14.02 を使用しています。

C/C++ コンパイラ

Windows ビルドでは Platform SDK 8.1 以降を含む MSVC 14(Visual Studio 2015) 以降がサポートされているようです。
GCC 4.8 移行でもいけるようですが、ドキュメントの表記が maybe なので MSVC 側を使う方が無難そうです。
CMake 時には MSVC の cl.exe へのパスを通してあげる必要がありますが、単体でパスを通すのではなく関連の環境変数をまとめせて設定してくれる vcvars64.bat 等を使いましょう。
参照 コマンドラインからMicrosoft C ++ツールセットを使用する(MSDN)
今回は Visual Studio 2019 を使用しています。

Go

最新の安定バージョンが必要です。
https://golang.org/dl/ 等からインストールしてパスを通しましょう。
パスとを通す代わりに、環境変数 GO_EXECUTABLE でもパス指定が可能です。
今回は Go 1.13 を使用しています。

Ninja

Ninja は CMake の置き換えを目指して作られた高速なビルドシステムです。
Windows 版 BoringSSL をビルドするには、 この Ninja を使うか CMake のみで実施するかの二つの選択肢があるようです。
しかし、 Windows 環境での CMake のみでのビルドはメンテされていないようなので、現状では Ninja を使うしかなさそうです(どっちにせよ早いので Ninja の方が良いとは思いますが)。
と言う訳で https://github.com/ninja-build/ninja/releases から Ninja を落としてパスを通してください。
今回は Ninja 1.9.0 を使用しています。

BoringSSL のビルド

上記の準備が完了したら Ninja を使って BoringSSL のビルドを実行します。
手順はとても簡単です。

  1. BoringSSL のリポジトリをダウンロードする
    https://github.com/google/boringssl.git

  2. ダウンロードした BoringSSL リポジトリのカレントディレクトリに移動して以下のコマンドを叩く

(必要に応じて MSVC 等へのパスを通す)
$ mkdir build
$ cd build
$ cmake -GNinja ..
$ ninja

何かしらにパスが通ってないと
cmake -GNinja ..
でエラーが出ますが、エラーメッセージが分かり易いので苦労しないと思います。

成功すると以下の .lib ファイルができます。

build\crypto\crypto.lib
build\decrepit\decrepit.lib
build\ssl\ssl.lib

このうち crypto.libssl.lib を使用します。

ビルドオプション

リリースビルドしたい時には -DCMAKE_BUILD_TYPE=Release を指定します。

$ cmake -DCMAKE_BUILD_TYPE=Release -GNinja ..

.dll をビルドしたい時には -DBUILD_SHARED_LIBS=1 を cmake 時のオプションとして指定します。
※ quiche のビルドには .lib が要求されるので今回は不要です

$ cmake -DBUILD_SHARED_LIBS=1 -GNinja ..

また、OPENSSL_SMALL を定義するとコードサイズ等削れるようです(詳細は追っていないです)。
今回はお試し実装なので、この設定や使用する暗号スイートの制限等は無しでそのまま使います。

C ランタイムライブラリの指定

quiche のビルドは Rust の Cargo で行いますが、Cargo では MDd, MTd を指定することはできません。
また、MTのビルドも若干手間が掛かります。
BoringSSL の設定はデフォルトでは MD でのビルドなので、基本的にはそのまま MD で行くのが良さそうです。
何らかの事情で MT でビルドしたい場合には、CMakeLists.txt に以下の変更を加えることで実現可能です。

set(CompilerFlags
    CMAKE_CXX_FLAGS
    CMAKE_CXX_FLAGS_DEBUG
    CMAKE_CXX_FLAGS_RELEASE
    CMAKE_C_FLAGS
    CMAKE_C_FLAGS_DEBUG
    CMAKE_C_FLAGS_RELEASE)
foreach(CompilerFlag ${CompilerFlags})
    string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}")
endforeach()

project(BoringSSL NONE) の後ろにでも雑に付け加えましょう。
※Windoiws 以外でもビルドする場合は if(WIN32) 等で囲むこと

Ninja により生成された細かいビルドオプションを確認したい場合は cmake -GNinja .. 後に生成される build\build.ninja 内にあるので、そちらを参照しましょう。

quiche をビルドする

いよいよ quiche のビルドに入ります。

Rust 環境のセットアップ

先ほども書いたように、 quiche のビルドには Rust のビルドシステム&パッケージマネージャである Cargo が必要です。
Cargo は Rust に付属されているので、まずは Rust をインストールします。
@euledge さんが Windows10でRustの開発環境を構築 をまとめてくださっているので、これを参考に環境を構築してください(詳細は割愛)。
今回は Rust 1.37.0 をインストールしました。

Cargo のコンフィグファイルを修正する

まず quiche のリポジトリを clone します。
https://github.com/cloudflare/quiche

このリポジトリのカレントディレクトリに Cargo.toml という Cargo のコンフィグファイルがあるので、 .lib/.dll がビルドされるようにこのファイルの crate-type を変更します。

.lib を作りたい場合
crate-type = ["staticlib"]

.dll を作りたい場合
crate-type = ["cdylib"]

まとめて
crate-type = ["staticlib", "cdylib"]

BoringSSL のライブラリにパスを通す

環境変数 QUICHE_BSSL_PATH に先ほどビルドした BoringSSL のカレントディレクトリを指定してください。
更に、 BoringSSL でビルドした際に生成されるデフォルトのパスを quiche は見に行かないので、ディレクトリ構成を変更してあげる必要があります。

  • Debug 時
    • crypto.lib 関連ファイルを build\crypto\Debug に入れる
    • ssl.lib 関連ファイルを build\ssl\Debug に入れる
  • Release 時
    • crypto.lib 関連ファイルを build\crypto\RelWithDebInfo に入れる
    • ssl.lib 関連ファイルを build\ssl\RelWithDebInfo に入れる

上記の構成にするのが面倒な場合は BoringSSL 側のコンフィグを直すか、もしくは quiche 内に同梱されている src\build.rs の以下の内容を修正すると良いです。

        if cfg!(debug_assertions) {
            return format!("{}/Debug", lib);
        } else {
            return format!("{}/RelWithDebInfo", lib);
        }

quiche のビルド

お疲れさまでした。ここまでくればあとは Cargo を叩くだけです!

Debug ビルド

$ cargo build

Release ビルド

$ cargo build --release

ビルドが成功するとカレントディレクトリ直下にある target フォルダ内に .lib/.dll が生成されます。
(適当に Visual Studio からリンクして呼び出せることまでは確認してますが割愛します)
また、この手順で生成されるのはランタイムライブラリは MD、プラットフォームは x64 であることに注意してください(x86 ビルドの方法は未調査です)。
ログの詳細が欲しい場合は --verbose オプションを付けると若干詳しい内容が出てきます。

長くなりましたが、以上で「HTTP/3 を使える OSS を Windows から呼び出せるように .lib/.dll を作成する」手順は完了です。
次回は「C#から呼び易いような 1 をラッピングする .dll を更に作成する」を解説したいと思います。

おまけ: quiche を MT でビルドする

環境変数 RUSTFLAGS に MT であることを明示する以下のオプションを設定してから cargo build してください。

RUSTFLAGS=-C target-feature=+crt-static

参考 : https://doc.rust-lang.org/reference/linkage.html

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away