MsQuic Setup - とあるペンギンの実験室

C/C++ Notes

MsQuicのセットアップ (Linux用)

CMakeのアップグレード(>= 3.16)

バージョン確認

ターミナル
cmake --version

リポジトリの追加

ターミナル
echo "deb http://deb.debian.org/debian bookworm-backports main contrib non-free" | sudo tee /etc/apt/sources.list.d/bookworm-backports.list

必要なパッケージをインストール

ターミナル
sudo apt update
sudo apt install -t bookworm-backports cmake

MsQuicのインストール

必要なパッケージをインストール

ターミナル
sudo apt install -y git software-properties-common apt-transport-https curl libssl-dev libunwind-dev clang ninja-build libtool m4 autoconf

GitHubからダウンロード

ターミナル
git clone https://github.com/microsoft/msquic.git
cd msquic/
git submodule update --init --recursive

ビルド & インストール

ターミナル
mkdir build
cd build
QUIC_ENABLE_PREVIEW_FEATURES=1 cmake -B build -S .. -GNinja
cmake --build build
sudo cmake --install build
sudo ldconfig

インストール先

-- Install configuration: "Release"
-- Installing: /usr/local/lib/libmsquic.so.2.6.0
-- Installing: /usr/local/lib/libmsquic.so.2
-- Installing: /usr/local/lib/libmsquic.so
-- Installing: /usr/local/lib/libmsquic_platform.a
-- Installing: /usr/local/include/msquic.h
-- Installing: /usr/local/include/msquic.hpp
-- Installing: /usr/local/include/msquic_fuzz.h
-- Installing: /usr/local/include/msquic_posix.h
-- Installing: /usr/local/include/msquic_winkernel.h
-- Installing: /usr/local/include/msquic_winuser.h
-- Installing: /usr/local/include/msquichelper.h
-- Installing: /usr/local/include/msquicp.h
-- Installing: /usr/local/include/quic_cert.h
-- Installing: /usr/local/include/quic_crypt.h
-- Installing: /usr/local/include/quic_datapath.h
-- Installing: /usr/local/include/quic_driver_helpers.h
-- Installing: /usr/local/include/quic_hashtable.h
-- Installing: /usr/local/include/quic_pcp.h
-- Installing: /usr/local/include/quic_platform.h
-- Installing: /usr/local/include/quic_platform_posix.h
-- Installing: /usr/local/include/quic_platform_winkernel.h
-- Installing: /usr/local/include/quic_platform_winuser.h
-- Installing: /usr/local/include/quic_sal_stub.h
-- Installing: /usr/local/include/quic_storage.h
-- Installing: /usr/local/include/quic_tls.h
-- Installing: /usr/local/include/quic_toeplitz.h
-- Installing: /usr/local/include/quic_trace.h
-- Installing: /usr/local/include/quic_trace_manifested_etw.h
-- Installing: /usr/local/include/quic_var_int.h
-- Installing: /usr/local/include/quic_versions.h
-- Installing: /usr/local/share/msquic/msquic-config.cmake
-- Installing: /usr/local/share/msquic/msquic.cmake
-- Installing: /usr/local/share/msquic/msquic-release.cmake

cat ファイルパス | grep [オプション -A : 後ろ -B : 前 -C : 前後] [行数] [検索文字列]
でファイルの中身を検索出来る

MsQuicのセットアップ (Windows用)

Visual Studioをインストールする


Download

CMakeをインストールする

 最新版をインストール


Download

 Windows x64 Installerを選択 (インストール後パスが通っていることを確認)

MsQuicをビルド

コマンドプロンプト
cd C:/
mkdir Programs
cd Programs
git clone https://github.com/microsoft/msquic.git
cd msquic
git submodule update --init --recursive
mkdir build
cd build
cmake -B build -S .. -G "Visual Studio 17 2022" -A x64 -DQUIC_TLS_LIB=schannel
cmake --build build

ライブラリ

ライブラリのパス
C:/Programs/msquic/build/build/bin/Debug/msquic.dll
ヘッダのパス
C:/Programs/msquic/src/inc/msquic.h

Visual Studio の設定

ヘッダーファイルのインクルードパスを設定

[プロジェクト] → [プロパティ] → [構成プロパティ] → [VC++ ディレクトリ] → [全般] → [外部インクルードディレクトリ]

C:/Programs/msquic/src/inc
ライブラリディレクトリの追加

[プロジェクト] → [プロパティ] → [構成プロパティ] → [リンカー] → [全般] → [追加のライブラリディレクトリ]

C:/Programs/msquic/build/build/obj/Debug
リンクするライブラリの指定

[プロジェクト] → [プロパティ] → [構成プロパティ] → [リンカー] → [入力] → [追加の依存ファイル]

msquic.lib
環境変数にDLLのPATHを追加
C:\Programs\msquic\build\build\bin\Debug

サンプルコード

ヘッダファイル (QUICStart.cpp)
#define _CRT_SECURE_NO_WARNINGS //SDLチェック無効

#include <cstddef>
#include <limits>
#include <climits>
#include <cfloat>
#include <cstdint>
#include <cstdlib>
#include <new>
#include <typeinfo>
#include <exception>
#include <initializer_list>
#include <cstdalign>
#include <stdexcept>
#include <cassert>
#include <cerrno>
#include <system_error>
#include <string>

#if __has_include(<string_view>)
#   include <string_view>
#endif

#include <array>
#include <deque>
#include <forward_list>
#include <list>
#include <vector>
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <queue>
#include <stack>
#include <iterator>
#include <algorithm>
#include <cfenv>
#include <random>
#include <numeric>
#include <iosfwd>
#include <iostream>
#include <ios>
#include <streambuf>
#include <istream>
#include <ostream>
#include <iomanip>
#include <sstream>
#include <fstream>

#if __has_include(<filesystem>)
    # include <filesystem>
#endif

#include <cstdio>
#include <cinttypes>

#include <regex>
#include <atomic>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <condition_variable>
#include <future>
#include <cstring>       
#include <tuple>   
#include <ctime>
#include <chrono>

#define _USE_MATH_DEFINES //数値演算定数を定義
#include <cmath>

#define OPEN_PROCESS_TOKEN (TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY) // アクセス権の定数

#ifdef __linux__ 
    //linux code
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <unistd.h>

#elif _WIN32
    // windows code
    #define WIN32_LEAN_AND_MEAN
    #include <Windows.h>
    #include <direct.h>
    #include <tchar.h>
    #include <WinSock2.h>
    #include <ws2tcpip.h>
    #include <windowsx.h>
    #pragma comment(lib, "ws2_32.lib")
    #pragma comment(lib, "msquic.lib")
    #pragma warning(disable:4996)

#else

#endif


#if __has_include(<msquic.h>)
    #include <msquic.h>
    class QUICStart{
        protected:
            QUIC_API_TABLE* MsQuic;
            QUIC_STATUS status;
            HQUIC Listener                                  = NULL;
            HQUIC Configuration                             = NULL;
            HQUIC Registration                              = NULL;
            HQUIC QUICConnection                            = NULL;
            QUIC_SETTINGS Settings                          = {0};
            QUIC_ADDR Address                               = {0};
            QUIC_BUFFER AlpnBuffer;
            QUIC_REGISTRATION_CONFIG RegistrationConfig;            // 登録設定
            QUIC_CERTIFICATE_FILE CertificateConfig;                // 証明書と秘密鍵の設定
            QUIC_CREDENTIAL_CONFIG CredConfig;
            QUIC_BUFFER* Buffer;
            bool isServer                                   = true;
            std::string ipv4;
            int port;
            std::vector<uint8_t> recvData;
            std::string sendData                            = "";
            bool debug                                      = false;
            bool retry                                      = false;
            std::string alpn;

            _IRQL_requires_max_(DISPATCH_LEVEL) 
            _Function_class_(QUIC_LISTENER_CALLBACK) 
            static QUIC_STATUS QUIC_API ListenerCallback( // ListenerCallback関数
                _In_ HQUIC Listener,
                _In_opt_ void* Context,
                _Inout_ QUIC_LISTENER_EVENT* Event
            ) {
                QUICStart* self = reinterpret_cast<QUICStart*>(Context);
                switch (Event->Type) { // イベントを重複定義しないよう switch case文を使う
                    case QUIC_LISTENER_EVENT_NEW_CONNECTION:
                        if (self->debug) printf("[ListenerCallback] NEW_CONNECTION\n");
                        self->QUICConnection = Event->NEW_CONNECTION.Connection;
                        self->MsQuic->SetCallbackHandler(self->QUICConnection, (void*)QUICStart::ConnectionCallback, self);
                        self->status = self->MsQuic->ConnectionSetConfiguration(self->QUICConnection, self->Configuration);
                        if (QUIC_FAILED(self->status)) {
                            printf("[FAILED] ConnectionSetConfiguration: 0x%x\n", self->status);
                            self->MsQuic->ConnectionClose(self->QUICConnection);
                        }
                        break;

                    default:
                        if (self->debug) printf("[ListenerCallback] Listener event type: 0x%X\n", Event->Type);
                        break;
                }
                return QUIC_STATUS_SUCCESS;
            }

            
            _IRQL_requires_max_(DISPATCH_LEVEL)
            _Function_class_(QUIC_CONNECTION_CALLBACK)
            static QUIC_STATUS QUIC_API ConnectionCallback( // ConnectionCallback関数
                _In_ HQUIC Connection,
                _In_opt_ void* Context,
                _Inout_ QUIC_CONNECTION_EVENT* Event
            ) {
                HQUIC Stream;
                QUICStart* self = reinterpret_cast<QUICStart*>(Context);
                switch (Event->Type) {
                    case QUIC_CONNECTION_EVENT_CONNECTED:
                        if (self->debug) printf("[ConnectionCallback] QUIC_CONNECTION_EVENT_CONNECTED\n");
                        if(!self->isServer){
                            self->status = self->MsQuic->StreamOpen(// ストリームを作成
                                Connection,
                                QUIC_STREAM_OPEN_FLAG_NONE,
                                (QUIC_STREAM_CALLBACK_HANDLER)QUICStart::StreamCallback,
                                Context,
                                &Stream
                            );
                            if (QUIC_SUCCEEDED(self->status)) {
                                self->MsQuic->StreamStart(Stream, QUIC_STREAM_START_FLAG_IMMEDIATE);
                            
                            } else {
                                printf("[FAILED] StreamOpen : 0x%x\n", self->status);
                                // ストリーム作成失敗時に Connection を閉じる処理を追加しても良い
                                self->MsQuic->ConnectionShutdown(Connection, QUIC_CONNECTION_SHUTDOWN_FLAG_NONE, 0);
                            }
                        }
                        break;
                    case QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT:
                        if (self->debug) printf("[ConnectionCallback] QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT\n");
                        self->MsQuic->ConnectionClose(Connection);
                        break;
                    case QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE:
                        if (self->debug) printf("[ConnectionCallback] QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE\n");
                        self->MsQuic->ConnectionClose(Connection);
                        break;
                    case QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED:
                        if (self->debug) printf("[ConnectionCallback] QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED\n");
                        if(self->isServer){
                            Stream = Event->PEER_STREAM_STARTED.Stream;
                            self->MsQuic->SetCallbackHandler(Stream, (void*)QUICStart::StreamCallback, self);
                            self->MsQuic->StreamReceiveSetEnabled(Stream, TRUE);
                        }
                        break;
                    
                    case QUIC_CONNECTION_EVENT_PEER_NEEDS_STREAMS:
                        if (self->debug) printf("[ConnectionCallback] QUIC_CONNECTION_EVENT_PEER_NEEDS_STREAMS\n");
                        break;
                    
                    default:
                        if (self->debug) printf("[ConnectionCallback] Connection event type: 0x%X\n", Event->Type);
                        break;
                }
                return QUIC_STATUS_SUCCESS;
            }

            _IRQL_requires_max_(DISPATCH_LEVEL)
            _Function_class_(QUIC_STREAM_CALLBACK)
            static QUIC_STATUS QUIC_API StreamCallback( // StreamCallback関数
                _In_ HQUIC Stream,
                _In_opt_ void* Context,
                _Inout_ QUIC_STREAM_EVENT* Event
            ) {
                size_t msgLen;
                uint8_t* message; 
                QUICStart* self = reinterpret_cast<QUICStart*>(Context);
                switch (Event->Type) {
                    case QUIC_STREAM_EVENT_START_COMPLETE:
                        if (self->debug) printf("[StreamCallback] QUIC_STREAM_EVENT_START_COMPLETE\n");
                        if (self->sendData != ""){
                            msgLen                      = self->sendData.length();
                            message                     = (uint8_t*)malloc(msgLen + 1);
                            if (!message) {
                                printf("[FAILED] malloc\n");
                                return QUIC_STATUS_OUT_OF_MEMORY;
                            }
                            memcpy(message, self->sendData.c_str(), msgLen + 1);
                            self->Buffer                = (QUIC_BUFFER*)malloc(sizeof(QUIC_BUFFER));
                            if (!self->Buffer) {
                                printf("[FAILED] malloc\n");
                                free(message);
                                return QUIC_STATUS_OUT_OF_MEMORY;
                            }
                            self->Buffer->Buffer        = (uint8_t*)message;
                            self->Buffer->Length        = (uint32_t)msgLen;
                            self->MsQuic->StreamSend(
                                Stream, 
                                self->Buffer, 
                                1, 
                                QUIC_SEND_FLAG_START | QUIC_SEND_FLAG_FIN, 
                                self->Buffer
                            );
                            //MsQuic->StreamSend(Stream, NULL, 0, QUIC_SEND_FLAG_FIN, NULL);
                            if (self->debug) printf("[StreamCallback] StreamSend : %s\n", self->sendData.c_str());
                        }
                        break;

                    case QUIC_STREAM_EVENT_SEND_COMPLETE:
                        if (self->debug) printf("[StreamCallback] QUIC_STREAM_EVENT_SEND_COMPLETE\n");
                        self->Buffer = (QUIC_BUFFER*)Event->SEND_COMPLETE.ClientContext;
                        if (self->Buffer) {
                            free(self->Buffer->Buffer); // message の解放
                            free(self->Buffer);         // Buffer の解放
                        }
                        break;

                    case QUIC_STREAM_EVENT_RECEIVE:
                        if (self->debug) printf("[StreamCallback] QUIC_STREAM_EVENT_RECEIVE\n");
                        for (uint32_t i = 0; i < Event->RECEIVE.BufferCount; ++i) {
                            message             = static_cast<uint8_t*>(Event->RECEIVE.Buffers[i].Buffer);
                            msgLen              = static_cast<size_t>(Event->RECEIVE.Buffers[i].Length);

                            self->recvData.insert(self->recvData.end(), message, message + msgLen);
                        }
                        self->MsQuic->StreamReceiveComplete(Stream, Event->RECEIVE.TotalBufferLength);
                        break;

                    case QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN:
                        if (self->debug) printf("[StreamCallback] QUIC_STREAM_EVENT_PEER_SEND_SHUTDOWN\n");
                        self->MsQuic->StreamShutdown(Stream, QUIC_STREAM_SHUTDOWN_FLAG_NONE, 0);
                        break;
                    case QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE:
                        if (self->debug) printf("[StreamCallback] QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE\n");
                        self->MsQuic->StreamClose(Stream);
                        break;
                    
                    
                    default:
                        if (self->debug) printf("[StreamCallback] Stream event type: 0x%X\n", Event->Type);
                        break;
                }
                return QUIC_STATUS_SUCCESS;
            }
        
        public:
            QUICStart(
                std::string serverIPv4                          = "127.0.0.1",
                int serverPort                                  = 9999,
                std::string appName                             = "QUICApplication",
                std::string alpn                                = "echo", 
                bool isServer                                   = true,
                std::string certFile                            = "certificate.crt",
                std::string keyFile                             = "private.key", 
                int streamCount                                 = 5,
                bool debug                                      = false
            ) { 
                this->ipv4                                      = serverIPv4;
                this->port                                      = serverPort;
                this->isServer                                  = isServer;
                this->debug                                     = debug;
                this->RegistrationConfig.ExecutionProfile       = QUIC_EXECUTION_PROFILE_LOW_LATENCY;
                this->RegistrationConfig.AppName                = appName.c_str();
                this->alpn                                      = alpn;

                this->AlpnBuffer.Buffer                         = (uint8_t*)this->alpn.c_str();
                this->AlpnBuffer.Length                         = (uint32_t)strlen(this->alpn.c_str());

                this->Settings.IsSet.PeerBidiStreamCount        = TRUE;
                this->Settings.PeerBidiStreamCount              = streamCount; // 双方向ストリーム数
                this->Settings.IsSet.PeerUnidiStreamCount       = TRUE;
                this->Settings.PeerUnidiStreamCount             = 0;

                // 接続のセキュリティ設定
                memset(&this->CredConfig, 0, sizeof(this->CredConfig));
                if(this->isServer){
                    this->CertificateConfig.CertificateFile     = certFile.c_str();
                    this->CertificateConfig.PrivateKeyFile      = keyFile.c_str();
                    this->CredConfig.Flags                      = QUIC_CREDENTIAL_FLAG_NONE;
                    this->CredConfig.Type                       = QUIC_CREDENTIAL_TYPE_CERTIFICATE_FILE;
                    this->CredConfig.CertificateFile            = &this->CertificateConfig;
                }
                else{
                    this->CredConfig.Flags                      = (
                        QUIC_CREDENTIAL_FLAG_CLIENT | 
                        QUIC_CREDENTIAL_FLAG_NO_CERTIFICATE_VALIDATION
                    );

                }

                QuicAddrSetFamily(&this->Address, QUIC_ADDRESS_FAMILY_INET);
                QuicAddrSetPort(&this->Address, this->port);
                this->Address.Ipv4.sin_addr.s_addr              = inet_addr(this->ipv4.c_str()); // IPアドレスを直接設定
            }

            int connection_start(){
                if (!this->retry) {
                    this->status        = MsQuicOpenVersion(QUIC_API_VERSION_2, (const void**)&this->MsQuic);
                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] MsQuicOpen : 0x%x\n", this->status);
                        return 1;
                    }

                    this->status        = this->MsQuic->RegistrationOpen(&this->RegistrationConfig, &this->Registration);
                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] RegistrationOpen : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }

                    this->status        = this->MsQuic->ConfigurationOpen(
                        this->Registration,
                        &this->AlpnBuffer,
                        1, // BufferCount
                        &this->Settings,
                        sizeof(this->Settings),
                        NULL,
                        &this->Configuration
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ConfigurationOpen : 0x%x\n", this->status);
                        this->MsQuic->RegistrationClose(this->Registration);
                        this->clear();
                        return 1;
                    }

                    this->status        = this->MsQuic->ConfigurationLoadCredential(
                        this->Configuration, 
                        &this->CredConfig
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ConfigurationLoadCredential : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }
                }
                if(this->isServer){
                    this->status    = this->MsQuic->ListenerOpen(
                        this->Registration, 
                        this->ListenerCallback, 
                        this, 
                        &this->Listener
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ListenerOpen : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }
                    
                    this->status    = this->MsQuic->ListenerStart(
                        this->Listener, 
                        &this->AlpnBuffer, 
                        1, 
                        &this->Address
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ListenerStart : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }
                
                }
                else{
                    this->status    = this->MsQuic->ConnectionOpen(
                        this->Registration, 
                        this->ConnectionCallback, 
                        this, 
                        &this->QUICConnection
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ConnectionOpen : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }
                
                    // 接続を開始
                    this->status    = this->MsQuic->ConnectionStart(
                        this->QUICConnection, 
                        this->Configuration, 
                        QUIC_ADDRESS_FAMILY_UNSPEC, 
                        this->ipv4.c_str(), 
                        this->port
                    );

                    if (QUIC_FAILED(this->status)) {
                        printf("[FAILED] ConnectionStart : 0x%x\n", this->status);
                        this->clear();
                        return 1;
                    }
                }
                if(this->debug) printf("[CONNECTED] %s : %d\n", this->ipv4.c_str(), this->port);
                return 0;
            }

            ~QUICStart(){
                this->clear();
            }

            void send_data_set(std::string sendData = "Hello QUIC World!!"){
                this->sendData = sendData;
            }

            void disp_recv_data(){
                std::string data(this->recvData.begin(), this->recvData.end());
                printf("[RECV] %s\n", data.c_str());
                this->recvData.clear();
            }

            void clear(bool retry = false){
                this->retry = retry;
                this->recvData.clear();
                if (this->Listener != NULL) {
                    this->MsQuic->ListenerClose(this->Listener);
                    this->Listener = NULL;
                }
                if (this->QUICConnection    != NULL) {
                    this->MsQuic->ConnectionClose(this->QUICConnection);
                    this->QUICConnection    = NULL;
                }
                if (!this->retry) {
                    if (this->Configuration     != NULL) {
                        this->MsQuic->ConfigurationClose(this->Configuration);
                        this->Configuration     = NULL;
                    }
                    if (this->Registration      != NULL) {
                        this->MsQuic->RegistrationClose(this->Registration);
                        this->Registration      = NULL;
                    }
                    if (this->MsQuic            != NULL) {
                        MsQuicClose(this->MsQuic);
                        this->MsQuic            = NULL;
                    }
                }
                
            }

    };
#endif

クライアントサンプルコード (quic_sample_client.cpp)
#include "QUICStart.cpp"
int main(int argc, char** argv){
    QUICStart quic = QUICStart(
        "192.168.10.64",
        9999,
        "QUICApplication",
        "echo", 
        false,
        "certificate.crt",
        "private.key", 
        5,
        true
    );
    quic.send_data_set("Hello !!");
    quic.connection_start();
    getchar();
    quic.clear(true);
    quic.send_data_set("World !!");
    quic.connection_start();
    getchar();
    return 0;
}

GCCビルド (Linux)
g++ -I /usr/local/include -L /usr/local/lib -g  quic_sample_client.cpp -lmsquic -lssl -lcrypto -ldl -lpthread -lrt -o quic_sample_client.exe

サーバサンプルコード (quic_sample_server.cpp)
#include "QUICStart.cpp"
int main(int argc, char** argv){
    QUICStart quic = QUICStart(
        "192.168.10.64",
        9999,
        "QUICApplication",
        "echo", 
        true,
        "certificate.crt",
        "private.key", 
        5,
        true
    );
    quic.connection_start();
    getchar();
    quic.disp_recv_data();
    quic.clear(true);
    printf("再受信するには<ENTER>を押してください...");
    getchar();
    quic.connection_start();
    getchar();
    quic.disp_recv_data();
    getchar();
    return 0;
}

GCCビルド (Linux)
g++ -I /usr/local/include -L /usr/local/lib -g  quic_sample_server.cpp -lmsquic -lssl -lcrypto -ldl -lpthread -lrt -o quic_sample_server.exe