-
-
Save unasuke/cadc6e9af0b2f5e2860fcdf3318bb517 to your computer and use it in GitHub Desktop.
OpsnSSL's QUIC API and HTTP/3 integrations demo codes with JA descriptions by Claude Code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/http3/ossl-nghttp3-demo-client.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * 【ossl-nghttp3-demo-client.c - HTTP/3クライアントデモ】 | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * このデモは、OpenSSL QUICとnghttp3ライブラリを組み合わせて | |
| * HTTP/3クライアントを実装する方法を示します。 | |
| * | |
| * ossl-nghttp3-demo-server.cと同様に、SSL_poll()を使用した | |
| * 非ブロッキングイベント駆動型のアーキテクチャを採用しています。 | |
| * | |
| * ============================================================================ | |
| * 【アーキテクチャ概要】 | |
| * ============================================================================ | |
| * | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ HTTP/3 Application │ | |
| * │ (このデモ: GETリクエスト送信) │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ nghttp3 Library │ | |
| * │ ・HTTP/3フレーム処理 │ | |
| * │ ・QPACKヘッダ圧縮/展開 │ | |
| * │ ・リクエスト/レスポンス管理 │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ OpenSSL QUIC │ | |
| * │ ・QUICプロトコル実装 │ | |
| * │ ・ストリーム多重化 │ | |
| * │ ・SSL_poll()によるイベント監視 │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ UDP Socket │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * | |
| * ============================================================================ | |
| * 【処理フロー】 | |
| * ============================================================================ | |
| * | |
| * main() | |
| * │ | |
| * ├── コマンドライン引数解析 (host:port [path]) | |
| * │ | |
| * ├── BIO_parse_hostserv() ... ホスト名とポートを分離 | |
| * │ | |
| * ├── BIO_lookup_ex() ... DNSルックアップ | |
| * │ | |
| * ├── create_ctx() ... SSL_CTX作成(OSSL_QUIC_client_method) | |
| * │ | |
| * ├── create_socket_and_connect() ... UDPソケット作成・接続 | |
| * │ | |
| * ├── SSL_new() + SSL_connect() ... QUIC接続確立 | |
| * │ | |
| * ├── nghttp3_conn_client_new() ... HTTP/3クライアント接続作成 | |
| * │ | |
| * ├── quic_client_h3streams() ... HTTP/3制御ストリーム作成 | |
| * │ ├── 制御ストリーム (control) | |
| * │ ├── QPACKエンコーダストリーム | |
| * │ └── QPACKデコーダストリーム | |
| * │ | |
| * ├── submit_request() ... GETリクエスト送信 | |
| * │ | |
| * └── for (;;) { ... メインループ | |
| * │ | |
| * ├── wait_for_activity() ... select()で待機 | |
| * ├── handle_events() ... SSL_handle_events() | |
| * ├── read_from_ssl_ids() ... SSL_poll()でイベント処理 | |
| * │ ├── SSL_POLL_EVENT_ISU → SSL_accept_stream() (サーバーのuni) | |
| * │ ├── SSL_POLL_EVENT_R → quic_client_read() | |
| * │ ├── SSL_POLL_EVENT_W → nghttp3_conn_writev_stream() | |
| * │ └── SSL_POLL_EVENT_EC → 接続終了処理 | |
| * │ | |
| * └── write_from_h3() ... nghttp3データをQUICに送信 | |
| * } | |
| * | |
| * ============================================================================ | |
| * 【ossl-nghttp3-demo-server.cとの違い】 | |
| * ============================================================================ | |
| * | |
| * 項目 サーバー クライアント | |
| * ───────────────────────────────────────────────────────────────────────── | |
| * SSL_METHOD OSSL_QUIC_server_method OSSL_QUIC_client_method | |
| * 接続開始 SSL_new_listener SSL_connect | |
| * + SSL_accept_connection | |
| * nghttp3 nghttp3_conn_server_new nghttp3_conn_client_new | |
| * 単方向ストリーム サーバーが作成 サーバーが作成 | |
| * (3, 7, 11) (受け入れる側: 3, 7, 11) | |
| * 双方向ストリーム クライアントが作成 クライアントが作成 | |
| * (受け入れる側: 0, 4, 8) (0, 4, 8) | |
| * | |
| * ============================================================================ | |
| * 【使用方法】 | |
| * ============================================================================ | |
| * | |
| * $ ./ossl-nghttp3-demo-client <host:port> [path] | |
| * | |
| * 例: | |
| * ./ossl-nghttp3-demo-client localhost:4433 | |
| * ./ossl-nghttp3-demo-client localhost:4433 /index.html | |
| * | |
| * ============================================================================ | |
| * 【出力】 | |
| * ============================================================================ | |
| * | |
| * stderr: デバッグ情報、HTTPレスポンスヘッダ | |
| * stdout: HTTPレスポンスボディ | |
| * | |
| * ============================================================================ | |
| */ | |
| #include <assert.h> | |
| #include <netinet/in.h> | |
| #include <nghttp3/nghttp3.h> | |
| #include <openssl/err.h> | |
| #include <openssl/quic.h> | |
| #include <openssl/ssl.h> | |
| #include <unistd.h> | |
| #include <sys/socket.h> | |
| #include <sys/select.h> | |
| /* | |
| * 【定数定義】 | |
| */ | |
| #define nghttp3_arraylen(A) (sizeof(A) / sizeof(*(A))) | |
| /* | |
| * 【グローバル変数: nghttp3設定】 | |
| * | |
| * settings: HTTP/3の設定(ヘッダリスト最大サイズ、QPACKパラメータ等) | |
| * mem: nghttp3が使用するメモリアロケータ | |
| * callbacks: nghttp3からのコールバック関数群 | |
| */ | |
| static nghttp3_settings settings; | |
| static const nghttp3_mem *mem; | |
| static nghttp3_callbacks callbacks = {0}; | |
| /* | |
| * 【ssl_id構造体 - ストリーム管理】 | |
| * | |
| * OpenSSL QUICストリームとnghttp3のストリームIDを対応付ける構造体。 | |
| * | |
| * 【QUICストリームID規則】 | |
| * - ビット0: 開始者 (0=クライアント, 1=サーバー) | |
| * - ビット1: 方向性 (0=双方向, 1=単方向) | |
| * | |
| * クライアント開始 双方向: 0, 4, 8, 12, ... (リクエスト/レスポンス用) | |
| * クライアント開始 単方向: 2, 6, 10, ... (HTTP/3制御ストリーム) | |
| * サーバー開始 双方向: (HTTP/3では未使用) | |
| * サーバー開始 単方向: 3, 7, 11, ... (HTTP/3制御ストリーム) | |
| */ | |
| struct ssl_id { | |
| SSL *s; /* OpenSSLがSSL_read()/SSL_write()で使用するストリームSSL* */ | |
| uint64_t id; /* nghttp3が使用するストリーム識別子(QUIC stream ID) */ | |
| int status; /* ストリームの状態フラグ */ | |
| }; | |
| /* | |
| * 【ストリーム状態フラグ】 | |
| */ | |
| #define SERVERUNIOPEN 0x01 /* サーバーが開いた単方向ストリーム (ID: 3, 7, 11) */ | |
| #define SERVERCLOSED 0x02 /* サーバーがストリームを閉じた */ | |
| #define CLIENTBIDIOPEN 0x04 /* クライアントが開いた双方向ストリーム (ID: 0, 4, 8) */ | |
| #define CLIENTUNIOPEN 0x08 /* クライアントが開いた単方向ストリーム (ID: 2, 6, 10) */ | |
| #define CLIENTCLOSED 0x10 /* クライアント(自分)がストリームを閉じた */ | |
| #define TOBEREMOVED 0x20 /* 削除予約: 全イベント処理後に削除 */ | |
| #define ISCONNECTION 0x40 /* QUIC接続オブジェクト */ | |
| /* | |
| * 【定数】 | |
| */ | |
| #define MAXSSL_IDS 15 | |
| #define MAXURL 255 | |
| /* | |
| * 【h3ssl構造体 - HTTP/3接続状態管理】 | |
| * | |
| * 1つのHTTP/3接続に関する全ての状態を管理する構造体。 | |
| */ | |
| struct h3ssl { | |
| /* ストリーム管理配列 */ | |
| struct ssl_id ssl_ids[MAXSSL_IDS]; | |
| /* 状態フラグ群 */ | |
| int end_headers_received; /* on_end_headers()コールバックが呼ばれた */ | |
| int datadone; /* レスポンスボディ受信完了 */ | |
| int has_uni; /* HTTP/3必須の単方向ストリーム3本を作成済み */ | |
| int close_done; /* SSL_POLL_EVENT_ECを受信、接続終了開始 */ | |
| int done; /* レスポンス受信完了またはエラー */ | |
| /* リクエスト情報 */ | |
| uint64_t id_bidi; /* リクエスト用双方向ストリームID */ | |
| char url[MAXURL]; /* リクエストパス */ | |
| char hostname[MAXURL]; /* ホスト名(:authority用) */ | |
| }; | |
| /* | |
| * ============================================================================ | |
| * 【ヘルパー関数】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * make_nv() - nghttp3用ヘッダ名前/値ペアを初期化 | |
| */ | |
| static void make_nv(nghttp3_nv *nv, const char *name, const char *value) | |
| { | |
| nv->name = (uint8_t *)name; | |
| nv->value = (uint8_t *)value; | |
| nv->namelen = strlen(name); | |
| nv->valuelen = strlen(value); | |
| nv->flags = NGHTTP3_NV_FLAG_NONE; | |
| } | |
| /* | |
| * init_ids() - h3ssl構造体を初期化 | |
| */ | |
| static void init_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| memset(h3ssl, 0, sizeof(struct h3ssl)); | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) | |
| ssl_ids[i].id = UINT64_MAX; | |
| h3ssl->id_bidi = UINT64_MAX; | |
| } | |
| /* | |
| * add_id_status() - ストリームをssl_ids配列に登録 | |
| */ | |
| static void add_id_status(uint64_t id, SSL *ssl, struct h3ssl *h3ssl, int status) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].s == NULL) { | |
| ssl_ids[i].s = ssl; | |
| ssl_ids[i].id = id; | |
| ssl_ids[i].status = status; | |
| return; | |
| } | |
| } | |
| printf("Oops too many streams to add!!!\n"); | |
| exit(1); | |
| } | |
| /* | |
| * add_ids_connection() - 接続をssl_ids配列に登録 | |
| */ | |
| static void add_ids_connection(struct h3ssl *h3ssl, SSL *ssl) | |
| { | |
| add_id_status(UINT64_MAX, ssl, h3ssl, ISCONNECTION); | |
| } | |
| /* | |
| * get_ids_connection() - 現在の接続SSLを取得 | |
| */ | |
| static SSL *get_ids_connection(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].status & ISCONNECTION) { | |
| return ssl_ids[i].s; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* | |
| * remove_marked_ids() - TOBEREMOVEDフラグが付いたストリームを削除 | |
| */ | |
| static void remove_marked_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].status & TOBEREMOVED) { | |
| printf("remove_id %llu\n", (unsigned long long) ssl_ids[i].id); | |
| SSL_free(ssl_ids[i].s); | |
| ssl_ids[i].s = NULL; | |
| ssl_ids[i].id = UINT64_MAX; | |
| ssl_ids[i].status = 0; | |
| return; | |
| } | |
| } | |
| } | |
| /* | |
| * set_id_status() - 指定IDのストリームにステータスフラグを追加 | |
| */ | |
| static void set_id_status(uint64_t id, int status, struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == id) { | |
| printf("set_id_status: %llu to %d\n", (unsigned long long) ssl_ids[i].id, status); | |
| ssl_ids[i].status = ssl_ids[i].status | status; | |
| return; | |
| } | |
| } | |
| printf("Oops can't set status, can't find stream!!!\n"); | |
| } | |
| /* | |
| * get_id_status() - 指定IDのストリームのステータスを取得 | |
| */ | |
| static int get_id_status(uint64_t id, struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == id) { | |
| return ssl_ids[i].status; | |
| } | |
| } | |
| return -1; | |
| } | |
| /* | |
| * close_all_ids() - 全てのストリームを解放(接続以外) | |
| */ | |
| static void close_all_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == UINT64_MAX) | |
| continue; | |
| SSL_free(ssl_ids[i].s); | |
| ssl_ids[i].s = NULL; | |
| ssl_ids[i].id = UINT64_MAX; | |
| } | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【nghttp3コールバック関数】 | |
| * | |
| * nghttp3ライブラリからHTTP/3イベントを受け取るコールバック。 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * on_recv_header() - HTTPレスポンスヘッダを1つ受信 | |
| * | |
| * サーバーから1つのHTTPヘッダを受信するたびに呼び出される。 | |
| * ヘッダ名と値をstderrに出力する。 | |
| */ | |
| static int on_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, | |
| nghttp3_rcbuf *name, nghttp3_rcbuf *value, | |
| uint8_t flags, void *user_data, | |
| void *stream_user_data) | |
| { | |
| nghttp3_vec vname, vvalue; | |
| vname = nghttp3_rcbuf_get_buf(name); | |
| vvalue = nghttp3_rcbuf_get_buf(value); | |
| /* ヘッダをstderrに出力: "name: value\n" */ | |
| fwrite(vname.base, vname.len, 1, stderr); | |
| fprintf(stderr, ": "); | |
| fwrite(vvalue.base, vvalue.len, 1, stderr); | |
| fprintf(stderr, "\n"); | |
| return 0; | |
| } | |
| /* | |
| * on_end_headers() - ヘッダ終了コールバック | |
| * | |
| * 全てのHTTPレスポンスヘッダを受信した後に呼び出される。 | |
| */ | |
| static int on_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, | |
| void *user_data, void *stream_user_data) | |
| { | |
| struct h3ssl *h3ssl = (struct h3ssl *)user_data; | |
| fprintf(stderr, "\n"); /* ヘッダとボディの間に空行 */ | |
| h3ssl->end_headers_received = 1; | |
| return 0; | |
| } | |
| /* | |
| * on_recv_data() - HTTPレスポンスボディを受信 | |
| * | |
| * サーバーからレスポンスボディのデータを受信するたびに呼び出される。 | |
| * 受信したデータをそのままstdoutに出力する。 | |
| */ | |
| static int on_recv_data(nghttp3_conn *conn, int64_t stream_id, | |
| const uint8_t *data, size_t datalen, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| size_t wr; | |
| while (datalen > 0) { | |
| wr = fwrite(data, 1, datalen, stdout); | |
| if (ferror(stdout)) | |
| return 1; | |
| data += wr; | |
| datalen -= wr; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * on_end_stream() - ストリーム終了コールバック | |
| * | |
| * HTTPトランザクションが完了した際に呼び出される。 | |
| */ | |
| static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| struct h3ssl *h3ssl = (struct h3ssl *)conn_user_data; | |
| printf("on_end_stream! stream_id=%lld\n", (long long)stream_id); | |
| h3ssl->done = 1; | |
| return 0; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【ストリーム読み取り処理】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * quic_client_read() - QUICストリームからデータを読み取りnghttp3に渡す | |
| * | |
| * 【重要】 | |
| * nghttp3_conn_read_stream()の第4引数finは、このデータがストリームの | |
| * 最後であることを示す。FINを正しく渡さないとon_end_stream()が呼ばれない。 | |
| */ | |
| static int quic_client_read(nghttp3_conn *h3conn, SSL *stream, uint64_t id, struct h3ssl *h3ssl) | |
| { | |
| int r; | |
| uint8_t msg2[16000]; | |
| size_t readbytes = 0; | |
| int fin = 0; | |
| /* | |
| * SSL_read_ex()でデータを読み取る。 | |
| * ストリームの状態をチェックしてFINを検出する。 | |
| */ | |
| if (!SSL_read_ex(stream, msg2, sizeof(msg2), &readbytes)) { | |
| int err = SSL_get_error(stream, 0); | |
| switch (err) { | |
| case SSL_ERROR_WANT_READ: | |
| return 0; | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* ストリームが正常終了(FIN受信) */ | |
| fin = 1; | |
| readbytes = 0; | |
| break; | |
| default: | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| } | |
| /* ストリームの読み取り状態をチェック */ | |
| if (SSL_get_stream_read_state(stream) == SSL_STREAM_STATE_FINISHED) { | |
| fin = 1; | |
| } | |
| /* nghttp3にデータを渡す(fin=1でストリーム終了を通知) */ | |
| r = nghttp3_conn_read_stream(h3conn, id, msg2, readbytes, fin); | |
| printf("nghttp3_conn_read_stream used %d of %zu on %llu (fin=%d)\n", r, | |
| readbytes, (unsigned long long) id, fin); | |
| if (r < 0) { | |
| if (!nghttp3_err_is_fatal(r)) { | |
| printf("nghttp3_conn_read_stream: non-fatal error %d on %llu\n", r, | |
| (unsigned long long) id); | |
| return 1; | |
| } | |
| fprintf(stderr, "nghttp3_conn_read_stream: fatal error %d\n", r); | |
| return -1; | |
| } | |
| return 1; | |
| } | |
| /* | |
| * quic_client_h3streams() - HTTP/3必須の単方向ストリームを作成 | |
| * | |
| * HTTP/3プロトコルでは、クライアントは以下の3つの単方向ストリームを | |
| * サーバーに対して作成する必要がある: | |
| * | |
| * 1. 制御ストリーム (Control Stream) | |
| * 2. QPACKエンコーダストリーム | |
| * 3. QPACKデコーダストリーム | |
| * | |
| * クライアント開始の単方向ストリームは、ID 2, 6, 10, ... の順で割り当てられる。 | |
| */ | |
| static int quic_client_h3streams(nghttp3_conn *h3conn, struct h3ssl *h3ssl) | |
| { | |
| SSL *rstream = NULL; /* QPACKデコーダストリーム */ | |
| SSL *pstream = NULL; /* QPACKエンコーダストリーム */ | |
| SSL *cstream = NULL; /* 制御ストリーム */ | |
| SSL *conn; | |
| uint64_t r_streamid, p_streamid, c_streamid; | |
| conn = get_ids_connection(h3ssl); | |
| if (conn == NULL) { | |
| fprintf(stderr, "quic_client_h3streams no connection\n"); | |
| return -1; | |
| } | |
| /* 3つの単方向ストリームを作成 */ | |
| rstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (rstream == NULL) { | |
| fprintf(stderr, "=> QPACKデコーダストリーム作成失敗\n"); | |
| goto err; | |
| } | |
| printf("=> Opened QPACK decoder stream on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(rstream)); | |
| pstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (pstream == NULL) { | |
| fprintf(stderr, "=> QPACKエンコーダストリーム作成失敗\n"); | |
| goto err; | |
| } | |
| printf("=> Opened QPACK encoder stream on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(pstream)); | |
| cstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (cstream == NULL) { | |
| fprintf(stderr, "=> 制御ストリーム作成失敗\n"); | |
| goto err; | |
| } | |
| printf("=> Opened control stream on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(cstream)); | |
| r_streamid = SSL_get_stream_id(rstream); | |
| p_streamid = SSL_get_stream_id(pstream); | |
| c_streamid = SSL_get_stream_id(cstream); | |
| /* nghttp3にストリームをバインド */ | |
| if (nghttp3_conn_bind_qpack_streams(h3conn, p_streamid, r_streamid)) { | |
| fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n"); | |
| goto err; | |
| } | |
| if (nghttp3_conn_bind_control_stream(h3conn, c_streamid)) { | |
| fprintf(stderr, "nghttp3_conn_bind_control_stream failed!\n"); | |
| goto err; | |
| } | |
| printf("control: %llu enc %llu dec %llu\n", | |
| (unsigned long long)c_streamid, | |
| (unsigned long long)p_streamid, | |
| (unsigned long long)r_streamid); | |
| /* 作成したストリームをssl_ids配列に登録 */ | |
| add_id_status(r_streamid, rstream, h3ssl, CLIENTUNIOPEN); | |
| add_id_status(p_streamid, pstream, h3ssl, CLIENTUNIOPEN); | |
| add_id_status(c_streamid, cstream, h3ssl, CLIENTUNIOPEN); | |
| return 0; | |
| err: | |
| SSL_free(rstream); | |
| SSL_free(pstream); | |
| SSL_free(cstream); | |
| return -1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【SSL_poll()イベントループ】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * read_from_ssl_ids() - SSL_poll()でイベントを監視し処理 | |
| */ | |
| static int read_from_ssl_ids(nghttp3_conn *h3conn, struct h3ssl *h3ssl) | |
| { | |
| int hassomething = 0, i; | |
| struct ssl_id *ssl_ids = h3ssl->ssl_ids; | |
| SSL_POLL_ITEM items[MAXSSL_IDS] = {0}, *item = items; | |
| static const struct timeval nz_timeout = {0, 0}; | |
| size_t result_count = SIZE_MAX; | |
| int numitem = 0, ret; | |
| uint64_t processed_event = 0; | |
| int has_ids_to_remove = 0; | |
| /* SSL_POLL_ITEM配列を構築 */ | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].s != NULL) { | |
| item->desc = SSL_as_poll_descriptor(ssl_ids[i].s); | |
| item->events = UINT64_MAX; | |
| item->revents = UINT64_MAX; | |
| numitem++; | |
| item++; | |
| } | |
| } | |
| /* SSL_poll()を呼び出し */ | |
| ret = SSL_poll(items, numitem, sizeof(SSL_POLL_ITEM), &nz_timeout, | |
| SSL_POLL_FLAG_NO_HANDLE_EVENTS, &result_count); | |
| if (!ret) { | |
| fprintf(stderr, "SSL_poll failed\n"); | |
| return -1; | |
| } | |
| printf("read_from_ssl_ids %ld events\n", (unsigned long)result_count); | |
| if (result_count == 0) { | |
| return 0; | |
| } | |
| /* イベントをディスパッチ */ | |
| for (i = 0, item = items; i < numitem; i++, item++) { | |
| SSL *s; | |
| if (item->revents == SSL_POLL_EVENT_NONE) | |
| continue; | |
| processed_event = 0; | |
| s = item->desc.value.ssl; | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ISU】単方向ストリーム受け入れ可能 | |
| * | |
| * サーバーからの単方向ストリーム(制御、QPACKエンコーダ/デコーダ)を | |
| * SSL_accept_stream()で受け入れる。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_ISU) { | |
| SSL *stream = SSL_accept_stream(s, 0); | |
| uint64_t new_id; | |
| int r; | |
| if (stream == NULL) { | |
| continue; | |
| } | |
| new_id = SSL_get_stream_id(stream); | |
| printf("=> Accepted server uni stream on %llu\n", (unsigned long long) new_id); | |
| add_id_status(new_id, stream, h3ssl, SERVERUNIOPEN); | |
| r = quic_client_read(h3conn, stream, new_id, h3ssl); | |
| if (r == -1) { | |
| ret = -1; | |
| goto err; | |
| } | |
| if (r == 1) | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_ISU; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_OSU】単方向ストリーム作成可能 | |
| * | |
| * クライアントから単方向ストリームを作成できる状態。 | |
| * HTTP/3の必須ストリームをまだ作成していなければ、ここで作成する。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_OSU) { | |
| printf("OSU event - can create uni streams\n"); | |
| processed_event = processed_event | SSL_POLL_EVENT_OSU; | |
| if (!h3ssl->has_uni) { | |
| printf("Creating HTTP/3 control streams\n"); | |
| ret = quic_client_h3streams(h3conn, h3ssl); | |
| if (ret == -1) { | |
| fprintf(stderr, "quic_client_h3streams failed!\n"); | |
| goto err; | |
| } | |
| h3ssl->has_uni = 1; | |
| hassomething++; | |
| } | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_EC】接続終了開始 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_EC) { | |
| printf("Connection terminating\n"); | |
| if (!h3ssl->close_done) { | |
| h3ssl->close_done = 1; | |
| } else { | |
| h3ssl->done = 1; | |
| } | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_EC; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ECD】接続終了完了 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_ECD) { | |
| printf("Connection terminated\n"); | |
| h3ssl->done = 1; | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_ECD; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_R】読み取り可能 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_R) { | |
| uint64_t id = SSL_get_stream_id(s); | |
| int r; | |
| printf("revent READ on %llu\n", (unsigned long long)id); | |
| r = quic_client_read(h3conn, s, id, h3ssl); | |
| if (r == 0) { | |
| uint8_t msg[1]; | |
| size_t l = sizeof(msg); | |
| r = SSL_read(s, msg, l); | |
| if (r > 0) { | |
| ret = -1; | |
| goto err; | |
| } | |
| r = SSL_get_error(s, r); | |
| if (r != SSL_ERROR_ZERO_RETURN) { | |
| ret = -1; | |
| goto err; | |
| } | |
| set_id_status(id, TOBEREMOVED, h3ssl); | |
| has_ids_to_remove++; | |
| continue; | |
| } | |
| if (r == -1) { | |
| ret = -1; | |
| goto err; | |
| } | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_R; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ER】読み取り側例外 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_ER) { | |
| uint64_t id = SSL_get_stream_id(s); | |
| int status = get_id_status(id, h3ssl); | |
| printf("revent exception READ on %llu\n", (unsigned long long)id); | |
| if (status != -1 && (status & SERVERUNIOPEN)) { | |
| set_id_status(id, SERVERCLOSED, h3ssl); | |
| hassomething++; | |
| } | |
| processed_event = processed_event | SSL_POLL_EVENT_ER; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_W】書き込み可能 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_W) { | |
| processed_event = processed_event | SSL_POLL_EVENT_W; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_EW】書き込み側例外 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_EW) { | |
| uint64_t id = SSL_get_stream_id(s); | |
| int status = get_id_status(id, h3ssl); | |
| if (status != -1 && (status & CLIENTCLOSED)) { | |
| printf("both sides closed on %llu\n", (unsigned long long)id); | |
| set_id_status(id, TOBEREMOVED, h3ssl); | |
| has_ids_to_remove++; | |
| hassomething++; | |
| } | |
| processed_event = processed_event | SSL_POLL_EVENT_EW; | |
| } | |
| if (item->revents != processed_event) { | |
| uint64_t id = SSL_get_stream_id(s); | |
| printf("revent %llu on %llu NOT PROCESSED!\n", | |
| (unsigned long long)item->revents, | |
| (unsigned long long)id); | |
| } | |
| } | |
| ret = hassomething; | |
| err: | |
| if (has_ids_to_remove) | |
| remove_marked_ids(h3ssl); | |
| return ret; | |
| } | |
| /* | |
| * handle_events_from_ids() - QUIC内部イベントを処理 | |
| */ | |
| static void handle_events_from_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids = h3ssl->ssl_ids; | |
| int i; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].s != NULL && (ssl_ids[i].status & ISCONNECTION)) { | |
| SSL_handle_events(ssl_ids[i].s); | |
| } | |
| } | |
| } | |
| /* | |
| * quic_client_write() - QUICストリームにデータを書き込む | |
| */ | |
| static int quic_client_write(struct h3ssl *h3ssl, uint64_t streamid, | |
| uint8_t *buff, size_t len, uint64_t flags, | |
| size_t *written) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == streamid) { | |
| if (!SSL_write_ex2(ssl_ids[i].s, buff, len, flags, written) || | |
| *written != len) { | |
| fprintf(stderr, "couldn't write on connection\n"); | |
| ERR_print_errors_fp(stderr); | |
| return 0; | |
| } | |
| printf("written %lld on %lld flags %lld\n", (unsigned long long)len, | |
| (unsigned long long)streamid, (unsigned long long)flags); | |
| return 1; | |
| } | |
| } | |
| printf("quic_client_write %lld on %lld (NOT FOUND!)\n", (unsigned long long)len, | |
| (unsigned long long)streamid); | |
| return 0; | |
| } | |
| /* | |
| * write_from_h3() - nghttp3からデータを取得してQUICに書き込む | |
| * | |
| * nghttp3_conn_writev_stream()でnghttp3が送信したいデータを取得し、 | |
| * quic_client_write()でQUICストリームに書き込む。 | |
| */ | |
| static int write_from_h3(nghttp3_conn *h3conn, struct h3ssl *h3ssl) | |
| { | |
| nghttp3_vec vec[16]; | |
| nghttp3_ssize sveccnt; | |
| int64_t streamid; | |
| int fin; | |
| int ret = 0; | |
| for (;;) { | |
| sveccnt = nghttp3_conn_writev_stream(h3conn, &streamid, &fin, vec, | |
| nghttp3_arraylen(vec)); | |
| printf("nghttp3_conn_writev_stream: %lld vectors, stream %lld, fin %d\n", | |
| (long long)sveccnt, (long long)streamid, fin); | |
| if (sveccnt < 0) { | |
| fprintf(stderr, "nghttp3_conn_writev_stream failed: %lld\n", | |
| (long long)sveccnt); | |
| return -1; | |
| } | |
| if (sveccnt == 0 && streamid == -1) { | |
| /* 送信するデータがない */ | |
| break; | |
| } | |
| if (sveccnt > 0) { | |
| int i; | |
| uint64_t flags = fin ? SSL_WRITE_FLAG_CONCLUDE : 0; | |
| size_t written; | |
| for (i = 0; i < sveccnt; i++) { | |
| if (!quic_client_write(h3ssl, streamid, vec[i].base, vec[i].len, | |
| (i == sveccnt - 1) ? flags : 0, &written)) { | |
| return -1; | |
| } | |
| } | |
| } else if (fin) { | |
| /* データなしでFINのみ送信 */ | |
| size_t written; | |
| if (!quic_client_write(h3ssl, streamid, NULL, 0, | |
| SSL_WRITE_FLAG_CONCLUDE, &written)) { | |
| return -1; | |
| } | |
| } | |
| /* nghttp3に書き込み完了を通知 */ | |
| if (nghttp3_conn_add_write_offset(h3conn, streamid, | |
| nghttp3_vec_len(vec, sveccnt))) { | |
| fprintf(stderr, "nghttp3_conn_add_write_offset failed\n"); | |
| return -1; | |
| } | |
| ret = 1; | |
| } | |
| return ret; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【リクエスト送信】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * submit_request() - HTTP/3 GETリクエストを送信 | |
| */ | |
| static int submit_request(nghttp3_conn *h3conn, struct h3ssl *h3ssl) | |
| { | |
| nghttp3_nv nva[5]; | |
| size_t num_nv = 0; | |
| SSL *conn; | |
| SSL *stream; | |
| uint64_t stream_id; | |
| int rv; | |
| conn = get_ids_connection(h3ssl); | |
| if (conn == NULL) { | |
| fprintf(stderr, "submit_request: no connection\n"); | |
| return -1; | |
| } | |
| /* リクエスト用の双方向ストリームを作成 */ | |
| stream = SSL_new_stream(conn, 0); /* 0 = 双方向ストリーム */ | |
| if (stream == NULL) { | |
| fprintf(stderr, "Failed to create request stream\n"); | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| stream_id = SSL_get_stream_id(stream); | |
| printf("=> Created request stream on %llu\n", (unsigned long long)stream_id); | |
| /* ストリームを登録 */ | |
| add_id_status(stream_id, stream, h3ssl, CLIENTBIDIOPEN); | |
| h3ssl->id_bidi = stream_id; | |
| /* HTTPリクエストヘッダを構築 */ | |
| make_nv(&nva[num_nv++], ":method", "GET"); | |
| make_nv(&nva[num_nv++], ":scheme", "https"); | |
| make_nv(&nva[num_nv++], ":authority", h3ssl->hostname); | |
| make_nv(&nva[num_nv++], ":path", h3ssl->url); | |
| make_nv(&nva[num_nv++], "user-agent", "OpenSSL-nghttp3-demo-client"); | |
| /* nghttp3にリクエストを登録 */ | |
| rv = nghttp3_conn_submit_request(h3conn, stream_id, nva, num_nv, NULL, NULL); | |
| if (rv != 0) { | |
| fprintf(stderr, "nghttp3_conn_submit_request failed: %d\n", rv); | |
| return -1; | |
| } | |
| printf("Submitted GET request for %s%s\n", h3ssl->hostname, h3ssl->url); | |
| return 0; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【SSL_CTX / ソケット初期化】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * ALPN設定 (クライアント側) | |
| */ | |
| static const unsigned char alpn_h3[] = { 2, 'h', '3' }; | |
| /* | |
| * create_ctx() - SSL_CTXを作成・設定 | |
| * | |
| * パラメータ: | |
| * insecure: 1の場合、証明書検証をスキップ(-kオプション) | |
| */ | |
| static SSL_CTX *create_ctx(int insecure) | |
| { | |
| SSL_CTX *ctx; | |
| ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
| if (ctx == NULL) | |
| goto err; | |
| if (insecure) { | |
| /* 証明書検証を無効化(自己署名証明書用) */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); | |
| } else { | |
| /* サーバー証明書の検証を有効化 */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| /* システムデフォルトのCA証明書パスを使用 */ | |
| if (SSL_CTX_set_default_verify_paths(ctx) == 0) | |
| goto err; | |
| } | |
| return ctx; | |
| err: | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| /* | |
| * wait_for_activity() - ソケットアクティビティ待機 | |
| */ | |
| static void wait_for_activity(SSL *ssl) | |
| { | |
| fd_set wfds, rfds; | |
| int fd, width, ret; | |
| struct timeval tv, *tvp = NULL; | |
| int isinfinite; | |
| fd = SSL_get_fd(ssl); | |
| if (fd < 0) | |
| return; | |
| FD_ZERO(&wfds); | |
| FD_ZERO(&rfds); | |
| if (SSL_net_write_desired(ssl)) | |
| FD_SET(fd, &wfds); | |
| if (SSL_net_read_desired(ssl)) | |
| FD_SET(fd, &rfds); | |
| width = fd + 1; | |
| if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite) | |
| tvp = &tv; | |
| else { | |
| tv.tv_sec = 0; | |
| tv.tv_usec = 100000; /* 100ms */ | |
| tvp = &tv; | |
| } | |
| ret = select(width, &rfds, &wfds, NULL, tvp); | |
| (void)ret; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【メイン関数】 | |
| * ============================================================================ | |
| */ | |
| static void print_usage(const char *prog) | |
| { | |
| fprintf(stderr, "usage: %s [-k] <host:port> [path]\n", prog); | |
| fprintf(stderr, " -k Skip certificate verification (insecure)\n"); | |
| } | |
| int main(int argc, char **argv) | |
| { | |
| int ret = 1; | |
| SSL_CTX *ctx = NULL; | |
| SSL *ssl = NULL; | |
| nghttp3_conn *h3conn = NULL; | |
| struct h3ssl h3ssl; | |
| BIO *bio = NULL; | |
| BIO_ADDRINFO *bai = NULL; | |
| const BIO_ADDRINFO *bai_walk; | |
| char *hostname = NULL, *service = NULL; | |
| const char *path = "/"; | |
| const char *hostport = NULL; | |
| int ok; | |
| int insecure = 0; | |
| int argidx = 1; | |
| /* オプション解析 */ | |
| while (argidx < argc && argv[argidx][0] == '-') { | |
| if (strcmp(argv[argidx], "-k") == 0) { | |
| insecure = 1; | |
| argidx++; | |
| } else { | |
| fprintf(stderr, "Unknown option: %s\n", argv[argidx]); | |
| print_usage(argv[0]); | |
| goto err; | |
| } | |
| } | |
| /* コマンドライン引数の検証 */ | |
| if (argidx >= argc) { | |
| print_usage(argv[0]); | |
| goto err; | |
| } | |
| hostport = argv[argidx++]; | |
| if (argidx < argc) { | |
| path = argv[argidx]; | |
| } | |
| /* ホスト名とポートの分離 */ | |
| if (BIO_parse_hostserv(hostport, &hostname, &service, 0) != 1) { | |
| fprintf(stderr, "Failed to parse host:port\n"); | |
| goto err; | |
| } | |
| if (hostname == NULL || service == NULL) { | |
| fprintf(stderr, "usage: %s <host:port> [path]\n", argv[0]); | |
| goto err; | |
| } | |
| printf("Connecting to %s:%s, path=%s\n", hostname, service, path); | |
| /* DNSルックアップ */ | |
| ok = BIO_lookup_ex(hostname, service, BIO_LOOKUP_CLIENT, | |
| 0, SOCK_DGRAM, IPPROTO_UDP, &bai); | |
| if (ok == 0) { | |
| fprintf(stderr, "Host %s not found\n", hostname); | |
| goto err; | |
| } | |
| /* SSL_CTX作成 */ | |
| ctx = create_ctx(insecure); | |
| if (ctx == NULL) { | |
| fprintf(stderr, "Failed to create SSL_CTX\n"); | |
| ERR_print_errors_fp(stderr); | |
| goto err; | |
| } | |
| /* h3ssl構造体を初期化 */ | |
| init_ids(&h3ssl); | |
| strncpy(h3ssl.hostname, hostname, MAXURL - 1); | |
| strncpy(h3ssl.url, path, MAXURL - 1); | |
| /* nghttp3コールバックを設定 */ | |
| callbacks.recv_header = on_recv_header; | |
| callbacks.end_headers = on_end_headers; | |
| callbacks.recv_data = on_recv_data; | |
| callbacks.end_stream = on_end_stream; | |
| /* DNSアドレスリストへの接続試行 */ | |
| for (bai_walk = bai; bai_walk != NULL; | |
| bai_walk = BIO_ADDRINFO_next(bai_walk)) { | |
| int fd; | |
| /* UDPソケット作成 */ | |
| fd = BIO_socket(BIO_ADDRINFO_family(bai_walk), SOCK_DGRAM, IPPROTO_UDP, 0); | |
| if (fd < 0) | |
| continue; | |
| /* ソケットを接続 */ | |
| if (!BIO_connect(fd, BIO_ADDRINFO_address(bai_walk), 0)) { | |
| BIO_closesocket(fd); | |
| continue; | |
| } | |
| /* 非ブロッキングモードに設定 */ | |
| if (!BIO_socket_nbio(fd, 1)) { | |
| BIO_closesocket(fd); | |
| continue; | |
| } | |
| /* BIO作成 */ | |
| bio = BIO_new(BIO_s_datagram()); | |
| if (bio == NULL) { | |
| BIO_closesocket(fd); | |
| continue; | |
| } | |
| BIO_set_fd(bio, fd, BIO_CLOSE); | |
| /* SSL作成 */ | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| BIO_free(bio); | |
| bio = NULL; | |
| continue; | |
| } | |
| /* BIOを設定 */ | |
| SSL_set_bio(ssl, bio, bio); | |
| bio = NULL; /* SSLが所有権を取得 */ | |
| /* ピアアドレスを設定 */ | |
| if (!SSL_set1_initial_peer_addr(ssl, BIO_ADDRINFO_address(bai_walk))) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* SNIを設定 */ | |
| if (!SSL_set_tlsext_host_name(ssl, hostname)) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* 証明書検証用ホスト名を設定 */ | |
| if (!SSL_set1_host(ssl, hostname)) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* ALPNを設定 */ | |
| if (SSL_set_alpn_protos(ssl, alpn_h3, sizeof(alpn_h3)) != 0) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* デフォルトストリームモードを無効化(HTTP/3では必須) */ | |
| if (!SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE)) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* 受信ストリームポリシーを設定 */ | |
| if (!SSL_set_incoming_stream_policy(ssl, SSL_INCOMING_STREAM_POLICY_ACCEPT, 0)) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* 非ブロッキングモードを設定 */ | |
| if (!SSL_set_blocking_mode(ssl, 0)) { | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| continue; | |
| } | |
| /* QUIC接続を開始 */ | |
| printf("Attempting QUIC connection...\n"); | |
| while (1) { | |
| int connret = SSL_connect(ssl); | |
| if (connret == 1) { | |
| printf("QUIC connection established!\n"); | |
| break; | |
| } | |
| if (connret == 0) { | |
| fprintf(stderr, "SSL_connect failed\n"); | |
| ERR_print_errors_fp(stderr); | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| break; | |
| } | |
| int err = SSL_get_error(ssl, connret); | |
| if (err == SSL_ERROR_WANT_READ || err == SSL_ERROR_WANT_WRITE) { | |
| wait_for_activity(ssl); | |
| continue; | |
| } | |
| fprintf(stderr, "SSL_connect error: %d\n", err); | |
| ERR_print_errors_fp(stderr); | |
| SSL_free(ssl); | |
| ssl = NULL; | |
| break; | |
| } | |
| if (ssl != NULL) | |
| break; /* 接続成功 */ | |
| } | |
| if (ssl == NULL) { | |
| fprintf(stderr, "Failed to connect to any address\n"); | |
| goto err; | |
| } | |
| /* 接続をssl_idsに登録 */ | |
| add_ids_connection(&h3ssl, ssl); | |
| /* nghttp3クライアント接続を作成 */ | |
| mem = nghttp3_mem_default(); | |
| nghttp3_settings_default(&settings); | |
| if (nghttp3_conn_client_new(&h3conn, &callbacks, &settings, mem, &h3ssl)) { | |
| fprintf(stderr, "nghttp3_conn_client_new failed!\n"); | |
| goto err; | |
| } | |
| printf("nghttp3 client connection created\n"); | |
| /* HTTP/3制御ストリームを作成 */ | |
| if (quic_client_h3streams(h3conn, &h3ssl) != 0) { | |
| fprintf(stderr, "quic_client_h3streams failed!\n"); | |
| goto err; | |
| } | |
| h3ssl.has_uni = 1; | |
| /* 初期データを送信 */ | |
| if (write_from_h3(h3conn, &h3ssl) < 0) { | |
| fprintf(stderr, "write_from_h3 failed!\n"); | |
| goto err; | |
| } | |
| /* リクエストを送信 */ | |
| if (submit_request(h3conn, &h3ssl) != 0) { | |
| fprintf(stderr, "submit_request failed!\n"); | |
| goto err; | |
| } | |
| /* リクエストデータを送信 */ | |
| if (write_from_h3(h3conn, &h3ssl) < 0) { | |
| fprintf(stderr, "write_from_h3 failed!\n"); | |
| goto err; | |
| } | |
| /* メインイベントループ */ | |
| printf("Entering main event loop...\n"); | |
| while (!h3ssl.done) { | |
| int r; | |
| /* ソケットアクティビティを待機 */ | |
| wait_for_activity(ssl); | |
| /* QUIC内部イベントを処理 */ | |
| handle_events_from_ids(&h3ssl); | |
| /* SSL_poll()でイベントを処理 */ | |
| r = read_from_ssl_ids(h3conn, &h3ssl); | |
| if (r < 0) { | |
| fprintf(stderr, "read_from_ssl_ids failed!\n"); | |
| goto err; | |
| } | |
| /* nghttp3のデータを書き込む */ | |
| if (write_from_h3(h3conn, &h3ssl) < 0) { | |
| fprintf(stderr, "write_from_h3 failed!\n"); | |
| goto err; | |
| } | |
| } | |
| printf("\nRequest completed successfully!\n"); | |
| ret = 0; | |
| err: | |
| if (ret != 0) | |
| ERR_print_errors_fp(stderr); | |
| nghttp3_conn_del(h3conn); | |
| close_all_ids(&h3ssl); | |
| SSL_free(ssl); | |
| SSL_CTX_free(ctx); | |
| BIO_ADDRINFO_free(bai); | |
| OPENSSL_free(hostname); | |
| OPENSSL_free(service); | |
| return ret; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/http3/ossl-nghttp3-demo-server.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * 【ossl-nghttp3-demo-server.c - HTTP/3サーバーデモ】 | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * このデモは、OpenSSL QUICとnghttp3ライブラリを組み合わせて | |
| * HTTP/3サーバーを実装する方法を示します。 | |
| * | |
| * ============================================================================ | |
| * 【アーキテクチャ概要】 | |
| * ============================================================================ | |
| * | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ HTTP/3 Application │ | |
| * │ (このデモ: ファイル配信) │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ nghttp3 Library │ | |
| * │ ・HTTP/3フレーム処理 │ | |
| * │ ・QPACKヘッダ圧縮/展開 │ | |
| * │ ・リクエスト/レスポンス管理 │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ OpenSSL QUIC │ | |
| * │ ・QUICプロトコル実装 │ | |
| * │ ・ストリーム多重化 │ | |
| * │ ・SSL_poll()によるイベント監視 │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * ┌─────────────────────────────────────────────────────────────────────┐ | |
| * │ UDP Socket │ | |
| * └─────────────────────────────────────────────────────────────────────┘ | |
| * | |
| * ============================================================================ | |
| * 【処理フロー】 | |
| * ============================================================================ | |
| * | |
| * main() | |
| * │ | |
| * ├── create_ctx() ... SSL_CTX作成(OSSL_QUIC_server_method) | |
| * ├── create_socket() ... UDPソケット作成・バインド | |
| * └── run_quic_server() ... メインサーバーループ | |
| * │ | |
| * ├── SSL_new_listener() | |
| * ├── nghttp3コールバック設定 | |
| * │ | |
| * └── for (;;) { ... メインループ | |
| * │ | |
| * ├── wait_for_activity() ... select()で待機 | |
| * ├── read_from_ssl_ids() ... SSL_poll()でイベント処理 | |
| * │ ├── SSL_POLL_EVENT_IC → SSL_accept_connection() | |
| * │ ├── SSL_POLL_EVENT_ISB → SSL_accept_stream() (bidi) | |
| * │ ├── SSL_POLL_EVENT_ISU → SSL_accept_stream() (uni) | |
| * │ ├── SSL_POLL_EVENT_OSU → quic_server_h3streams() | |
| * │ ├── SSL_POLL_EVENT_R → quic_server_read() | |
| * │ └── SSL_POLL_EVENT_EC → 接続終了処理 | |
| * │ | |
| * ├── quic_server_h3streams() ... HTTP/3制御ストリーム作成 | |
| * │ ├── 制御ストリーム (control) | |
| * │ ├── QPACKエンコーダストリーム | |
| * │ └── QPACKデコーダストリーム | |
| * │ | |
| * ├── nghttp3_conn_submit_response() ... レスポンス送信 | |
| * └── nghttp3_conn_writev_stream() ... データ書き込み | |
| * } | |
| * | |
| * ============================================================================ | |
| * 【他のデモとの比較】 | |
| * ============================================================================ | |
| * | |
| * demos/quic/server/server.c: | |
| * - ブロッキングモード、HTTP無し、単純な文字列送信 | |
| * - 最もシンプルなQUICサーバー | |
| * | |
| * demos/quic/poll-server/quic-server-ssl-poll-http.c: | |
| * - 非ブロッキング、HTTP/1.0、カスタムHTTPパーサー | |
| * - poll_event構造体による高度な状態管理 | |
| * | |
| * このファイル (ossl-nghttp3-demo-server.c): | |
| * - 非ブロッキング、HTTP/3、nghttp3ライブラリ使用 | |
| * - 実際のHTTP/3実装に近いアプローチ | |
| * | |
| * ============================================================================ | |
| * 【HTTP/3必須ストリーム】 | |
| * ============================================================================ | |
| * | |
| * HTTP/3では、以下の3つの単方向ストリームが必須: | |
| * | |
| * 1. 制御ストリーム (Control Stream) | |
| * - SETTINGS、GOAWAY等のフレーム送受信 | |
| * - stream ID: サーバー開始のuni = 3, 7, 11, ... | |
| * | |
| * 2. QPACKエンコーダストリーム | |
| * - 動的テーブル更新の送信 | |
| * | |
| * 3. QPACKデコーダストリーム | |
| * - 動的テーブル確認応答 | |
| * | |
| * クライアントからも同様に3つのストリームが来る (ID: 2, 6, 10) | |
| * | |
| * ============================================================================ | |
| * 【使用方法】 | |
| * ============================================================================ | |
| * | |
| * $ ./ossl-nghttp3-demo-server <port> <cert.pem> <key.pem> | |
| * | |
| * 環境変数: | |
| * FILEPREFIX: 静的ファイルを配信するディレクトリのプレフィックス | |
| * | |
| * ============================================================================ | |
| */ | |
| #include <assert.h> | |
| #include <netinet/in.h> | |
| #include <nghttp3/nghttp3.h> | |
| #include <openssl/err.h> | |
| #include <openssl/quic.h> | |
| #include <openssl/ssl.h> | |
| #include <unistd.h> | |
| #include <sys/stat.h> | |
| #include <fcntl.h> | |
| #include <sys/socket.h> | |
| /* | |
| * 【定数定義】 | |
| */ | |
| #ifndef PATH_MAX | |
| # define PATH_MAX 255 | |
| #endif | |
| /* nghttp3_arraylen: 配列の要素数を計算するマクロ */ | |
| #define nghttp3_arraylen(A) (sizeof(A) / sizeof(*(A))) | |
| /* | |
| * 【デフォルトレスポンスデータ】 | |
| * | |
| * ファイルが見つからない場合に返すデフォルトのペイロード。 | |
| * テスト用に20バイトの文字列を使用。 | |
| */ | |
| /* The crappy test wants 20 bytes */ | |
| #define NULL_PAYLOAD "12345678901234567890" | |
| static uint8_t *nulldata = (uint8_t *) NULL_PAYLOAD; | |
| static size_t nulldata_sz = sizeof(NULL_PAYLOAD) - 1; | |
| /* | |
| * 【nghttp3グローバル変数】 | |
| * | |
| * nghttp3ライブラリで使用するグローバル変数。 | |
| * これらはメインループとread_from_ssl_ids()の両方で参照される。 | |
| * | |
| * settings: HTTP/3の設定(ヘッダリスト最大サイズ、QPACKパラメータ等) | |
| * nghttp3_settings_default()でデフォルト値を設定 | |
| * | |
| * mem: nghttp3が使用するメモリアロケータ | |
| * nghttp3_mem_default()で標準のmalloc/freeを使用 | |
| * | |
| * callbacks: nghttp3からのコールバック関数群 | |
| * recv_header, end_headers, recv_data, end_stream等を設定 | |
| */ | |
| /* The nghttp3 variable we need in the main part and read_from_ssl_ids */ | |
| static nghttp3_settings settings; | |
| static const nghttp3_mem *mem; | |
| static nghttp3_callbacks callbacks = {0}; | |
| /* | |
| * 【ssl_id構造体 - ストリーム管理】 | |
| * | |
| * OpenSSL QUICストリームとnghttp3のストリームIDを対応付ける構造体。 | |
| * | |
| * HTTP/3では、クライアントとサーバーがそれぞれストリームを作成する: | |
| * | |
| * 【QUICストリームID規則】 | |
| * - ビット0: 開始者 (0=クライアント, 1=サーバー) | |
| * - ビット1: 方向性 (0=双方向, 1=単方向) | |
| * | |
| * クライアント開始 双方向: 0, 4, 8, 12, ... (リクエスト/レスポンス用) | |
| * クライアント開始 単方向: 2, 6, 10, ... (HTTP/3制御ストリーム) | |
| * サーバー開始 双方向: (HTTP/3では未使用) | |
| * サーバー開始 単方向: 3, 7, 11, ... (HTTP/3制御ストリーム) | |
| * | |
| * 典型的なHTTP/3接続では: | |
| * クライアント: ID 0 (bidi: リクエスト), 2, 6, 10 (uni: control, qpack enc/dec) | |
| * サーバー: 3, 7, 11 (uni: control, qpack enc/dec) | |
| */ | |
| /* 3 streams created by the server and 4 by the client (one is bidi) */ | |
| struct ssl_id { | |
| SSL *s; /* the stream openssl uses in SSL_read(), SSL_write etc */ | |
| /* OpenSSLがSSL_read()/SSL_write()で使用するストリームSSL* */ | |
| uint64_t id; /* the stream identifier the nghttp3 uses */ | |
| /* nghttp3が使用するストリーム識別子(QUIC stream ID) */ | |
| int status; /* 0 or one the below status and origin */ | |
| /* ストリームの状態フラグ(下記定数のビットOR) */ | |
| }; | |
| /* | |
| * 【ストリーム状態フラグ】 | |
| * | |
| * ssl_id.statusフィールドに設定するビットフラグ。 | |
| * 複数のフラグを組み合わせてストリームの現在状態を表す。 | |
| */ | |
| /* status and origin of the streams the possible values are: */ | |
| #define CLIENTUNIOPEN 0x01 /* unidirectional open by the client (2, 6 and 10) */ | |
| /* クライアントが開いた単方向ストリーム (ID: 2, 6, 10) */ | |
| #define CLIENTCLOSED 0x02 /* closed by the client */ | |
| /* クライアントがストリームを閉じた */ | |
| #define CLIENTBIDIOPEN 0x04 /* bidirectional open by the client (something like 0, 4, 8 ...) */ | |
| /* クライアントが開いた双方向ストリーム (ID: 0, 4, 8, ...) */ | |
| #define SERVERUNIOPEN 0x08 /* unidirectional open by the server (3, 7 and 11) */ | |
| /* サーバーが開いた単方向ストリーム (ID: 3, 7, 11) */ | |
| #define SERVERCLOSED 0x10 /* closed by the server (us) */ | |
| /* サーバー(自分)がストリームを閉じた */ | |
| #define TOBEREMOVED 0x20 /* marked for removing in read_from_ssl_ids, */ | |
| /* it will be removed after processing all events */ | |
| /* 削除予約: 全イベント処理後にremove_marked_ids()で削除 */ | |
| #define ISLISTENER 0x40 /* the stream is a listener from SSL_new_listener() */ | |
| /* SSL_new_listener()で作成したリスナー */ | |
| #define ISCONNECTION 0x80 /* the stream is a connection from SSL_accept_connection() */ | |
| /* SSL_accept_connection()で受け入れた接続 */ | |
| /* | |
| * 【定数】 | |
| * | |
| * MAXSSL_IDS: 管理可能なストリームの最大数 | |
| * HTTP/3では通常、制御用3+3=6ストリーム + リクエストストリーム | |
| * | |
| * MAXURL: URLパスの最大長 | |
| */ | |
| #define MAXSSL_IDS 20 | |
| #define MAXURL 255 | |
| /* | |
| * 【h3ssl構造体 - HTTP/3接続状態管理】 | |
| * | |
| * 1つのHTTP/3接続に関する全ての状態を管理する構造体。 | |
| * 接続ごとに1つのインスタンスを使用し、接続のライフサイクル全体を追跡。 | |
| * | |
| * 【状態遷移の概要】 | |
| * | |
| * 初期化 (init_ids) | |
| * │ | |
| * ▼ | |
| * 接続受け入れ (new_conn = 1) | |
| * │ | |
| * ▼ | |
| * HTTP/3制御ストリーム作成 (has_uni = 1) | |
| * │ | |
| * ▼ | |
| * ヘッダ受信完了 (end_headers_received = 1) | |
| * │ | |
| * ▼ | |
| * レスポンス送信 (datadone = 1) | |
| * │ | |
| * ▼ | |
| * 終了待機 (close_wait = 1) | |
| * │ | |
| * ├── 新しいリクエスト → restart = 1 → ヘッダ受信待機へ戻る | |
| * │ | |
| * └── 接続終了 → close_done = 1 → done = 1 | |
| */ | |
| struct h3ssl { | |
| /* | |
| * 【ストリーム管理配列】 | |
| * 全てのアクティブなストリーム(リスナー、接続、各種QUICストリーム)を管理。 | |
| * ssl_ids[i].s == NULLのエントリは空きスロット。 | |
| */ | |
| struct ssl_id ssl_ids[MAXSSL_IDS]; | |
| /* | |
| * 【状態フラグ群】 | |
| * HTTP/3リクエスト/レスポンスのライフサイクルを追跡。 | |
| */ | |
| int end_headers_received; /* h3 header received call back called */ | |
| /* on_end_headers()コールバックが呼ばれた */ | |
| /* HTTPヘッダの受信が完了 */ | |
| int datadone; /* h3 has given openssl all the data of the response */ | |
| /* レスポンスデータの送信が完了 */ | |
| /* step_read_data()でNGHTTP3_DATA_FLAG_EOFを返した */ | |
| int has_uni; /* we have the 3 uni directional stream needed */ | |
| /* HTTP/3必須の単方向ストリーム3本を作成済み */ | |
| /* (control, qpack encoder, qpack decoder) */ | |
| int close_done; /* connection begins terminating EVENT_EC */ | |
| /* SSL_POLL_EVENT_ECを受信、接続終了開始 */ | |
| int close_wait; /* we are waiting for a close or a new request */ | |
| /* レスポンス送信後、クローズまたは次リクエスト待ち */ | |
| int done; /* connection terminated EVENT_ECD, after EVENT_EC */ | |
| /* SSL_POLL_EVENT_ECDを受信、接続完全終了 */ | |
| int new_conn; /* a new connection has been received */ | |
| /* 新しい接続をSSL_accept_connection()で受け入れた */ | |
| int received_from_two; /* workaround for -607 on nghttp3_conn_read_stream on stream 2 */ | |
| /* stream ID 2でnghttp3が-607エラーを返す問題の回避用 */ | |
| /* Chromeとの互換性のためのワークアラウンド */ | |
| int restart; /* new request/response cycle started */ | |
| /* 同一接続上で新しいリクエストが開始された */ | |
| /* HTTP/3のストリーム多重化による複数リクエスト対応 */ | |
| /* | |
| * 【リクエスト/レスポンス処理用】 | |
| */ | |
| uint64_t id_bidi; /* the id of the stream used to read request and send response */ | |
| /* 現在処理中のリクエスト/レスポンス用双方向ストリームID */ | |
| /* クライアント開始なので 0, 4, 8, ... */ | |
| char *fileprefix; /* prefix of the directory to fetch files from */ | |
| /* ファイル配信のベースディレクトリ */ | |
| /* 環境変数FILEPREFIXから設定 */ | |
| char url[MAXURL]; /* url to serve the request */ | |
| /* リクエストされたURLパス(:path疑似ヘッダの値) */ | |
| /* | |
| * 【レスポンスデータ管理】 | |
| */ | |
| uint8_t *ptr_data; /* pointer to the data to send */ | |
| /* 送信するレスポンスデータへのポインタ */ | |
| /* ファイル内容またはnulldata */ | |
| size_t ldata; /* amount of bytes to send */ | |
| /* 残り送信バイト数 */ | |
| int offset_data; /* offset to next data to send */ | |
| /* 次に送信するデータの開始オフセット */ | |
| /* チャンク送信時に使用 */ | |
| }; | |
| /* | |
| * ============================================================================ | |
| * 【ヘルパー関数】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * make_nv() - nghttp3用ヘッダ名前/値ペアを初期化 | |
| * | |
| * nghttp3_nv構造体は、HTTP/3ヘッダのname/valueペアを表す。 | |
| * HTTPヘッダ(例: "content-type: text/html")や | |
| * HTTP/3疑似ヘッダ(例: ":status: 200")の設定に使用。 | |
| * | |
| * 注意: nameとvalueは、nghttp3がこの構造体を使用する間、 | |
| * 有効なメモリを指し続ける必要がある(コピーされない)。 | |
| */ | |
| static void make_nv(nghttp3_nv *nv, const char *name, const char *value) | |
| { | |
| nv->name = (uint8_t *)name; /* ヘッダ名(例: ":status", "content-type") */ | |
| nv->value = (uint8_t *)value; /* ヘッダ値(例: "200", "text/html") */ | |
| nv->namelen = strlen(name); /* 名前の長さ */ | |
| nv->valuelen = strlen(value); /* 値の長さ */ | |
| nv->flags = NGHTTP3_NV_FLAG_NONE; /* 特別なフラグなし */ | |
| } | |
| /* | |
| * init_ids() - h3ssl構造体を初期化 | |
| * | |
| * 新しい接続を処理する前にh3ssl構造体をクリアする。 | |
| * | |
| * 処理内容: | |
| * 1. 前回のレスポンスデータを解放(nulldataでない場合) | |
| * 2. 構造体全体をゼロクリア | |
| * 3. 全ssl_idのIDをUINT64_MAX(未使用マーカー)に設定 | |
| * 4. fileprefixを復元(環境変数から設定されるため保持) | |
| * | |
| * 注意: SSL*ポインタは解放しない。それは別途close_all_ids()で行う。 | |
| */ | |
| static void init_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| char *prior_fileprefix = h3ssl->fileprefix; | |
| /* 前回のレスポンスデータを解放(動的に確保した場合のみ) */ | |
| if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata) | |
| free(h3ssl->ptr_data); | |
| /* 構造体全体をゼロクリア */ | |
| memset(h3ssl, 0, sizeof(struct h3ssl)); | |
| /* 全スロットを未使用状態に初期化 */ | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) | |
| ssl_ids[i].id = UINT64_MAX; /* UINT64_MAX = 未使用マーカー */ | |
| h3ssl->id_bidi = UINT64_MAX; /* 双方向ストリームも未設定 */ | |
| /* restore the fileprefix */ | |
| /* fileprefixは環境変数由来なので復元 */ | |
| h3ssl->fileprefix = prior_fileprefix; | |
| } | |
| /* | |
| * reuse_h3ssl() - 同一接続上で新しいリクエストを処理するためにリセット | |
| * | |
| * HTTP/3では、1つの接続上で複数のリクエストを処理できる(ストリーム多重化)。 | |
| * 新しいリクエストを受け取った際に、リクエスト固有の状態のみをリセットし、 | |
| * 接続やストリーム管理の状態は維持する。 | |
| * | |
| * init_ids()との違い: | |
| * - init_ids(): 接続全体をリセット(新規接続時) | |
| * - reuse_h3ssl(): リクエスト状態のみリセット(同一接続での次リクエスト時) | |
| */ | |
| static void reuse_h3ssl(struct h3ssl *h3ssl) | |
| { | |
| /* リクエスト処理の状態フラグをリセット */ | |
| h3ssl->end_headers_received = 0; /* ヘッダ未受信状態に戻す */ | |
| h3ssl->datadone = 0; /* データ送信未完了 */ | |
| h3ssl->close_done = 0; /* クローズ未開始 */ | |
| h3ssl->close_wait = 0; /* クローズ待ち解除 */ | |
| h3ssl->done = 0; /* 完了フラグリセット */ | |
| /* URL情報をクリア */ | |
| memset(h3ssl->url, '\0', sizeof(h3ssl->url)); | |
| /* 前回のレスポンスデータを解放 */ | |
| if (h3ssl->ptr_data != NULL && h3ssl->ptr_data != nulldata) | |
| free(h3ssl->ptr_data); | |
| h3ssl->ptr_data = NULL; | |
| h3ssl->offset_data = 0; | |
| h3ssl->ldata = 0; | |
| } | |
| /* | |
| * add_id_status() - ストリームをssl_ids配列に登録 | |
| * | |
| * 新しいストリームを空きスロットに追加する。 | |
| * QUIC stream IDとOpenSSL SSL*ポインタを対応付けて管理。 | |
| * | |
| * パラメータ: | |
| * id: QUICストリームID(nghttp3が使用) | |
| * ssl: OpenSSL SSL*(SSL_read/SSL_writeが使用) | |
| * h3ssl: 状態管理構造体 | |
| * status: 初期ステータスフラグ(CLIENTUNIOPEN等) | |
| */ | |
| static void add_id_status(uint64_t id, SSL *ssl, struct h3ssl *h3ssl, int status) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| /* 空きスロット(s == NULL)を探す */ | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].s == NULL) { | |
| ssl_ids[i].s = ssl; | |
| ssl_ids[i].id = id; | |
| ssl_ids[i].status = status; | |
| return; | |
| } | |
| } | |
| /* 空きスロットがない場合はエラー終了 */ | |
| printf("Oops too many streams to add!!!\n"); | |
| exit(1); | |
| } | |
| /* | |
| * add_id() - ストリームをステータス0で登録 | |
| * | |
| * add_id_status()のラッパー。通常のストリーム追加用。 | |
| */ | |
| static void add_id(uint64_t id, SSL *ssl, struct h3ssl *h3ssl) | |
| { | |
| add_id_status(id, ssl, h3ssl, 0); | |
| } | |
| /* | |
| * 【リスナー・コネクション管理関数群】 | |
| * | |
| * QUICサーバーでは、以下の階層構造でオブジェクトを管理: | |
| * | |
| * リスナー (SSL_new_listener) | |
| * │ | |
| * └── 接続 (SSL_accept_connection) | |
| * │ | |
| * ├── 双方向ストリーム (リクエスト/レスポンス) | |
| * └── 単方向ストリーム (HTTP/3制御) | |
| * | |
| * リスナーと接続は、通常のストリームとは別に特別なステータスで管理。 | |
| * IDはUINT64_MAX(ストリームIDを持たないため)。 | |
| */ | |
| /* Add listener and connection */ | |
| /* | |
| * add_ids_listener() - リスナーをssl_ids配列に登録 | |
| * | |
| * SSL_new_listener()で作成したリスナーを追加。 | |
| * リスナーはSSL_poll()でSSL_POLL_EVENT_ICを監視するために必要。 | |
| */ | |
| static void add_ids_listener(SSL *ssl, struct h3ssl *h3ssl) | |
| { | |
| add_id_status(UINT64_MAX, ssl, h3ssl, ISLISTENER); | |
| } | |
| /* | |
| * add_ids_connection() - 接続をssl_ids配列に登録 | |
| * | |
| * SSL_accept_connection()で受け入れた接続を追加。 | |
| * 接続からストリームを作成・受け入れるために必要。 | |
| */ | |
| static void add_ids_connection(struct h3ssl *h3ssl, SSL *ssl) | |
| { | |
| add_id_status(UINT64_MAX, ssl, h3ssl, ISCONNECTION); | |
| } | |
| /* | |
| * get_ids_connection() - 現在の接続SSLを取得 | |
| * | |
| * ssl_ids配列からISCONNECTIONフラグが付いたエントリを探して返す。 | |
| * 新しいストリームを作成する際に接続SSLが必要。 | |
| * | |
| * 戻り値: 接続SSL*、または見つからない場合はNULL | |
| */ | |
| static SSL *get_ids_connection(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].status & ISCONNECTION) { | |
| printf("get_ids_connection\n"); | |
| return ssl_ids[i].s; | |
| } | |
| } | |
| return NULL; | |
| } | |
| /* | |
| * replace_ids_connection() - 接続SSLを置換 | |
| * | |
| * 古い接続を新しい接続で置き換える。 | |
| * 新しい接続が来た際に、古い接続をクローズして新しい接続で | |
| * 継続する場合に使用。 | |
| */ | |
| static void replace_ids_connection(struct h3ssl *h3ssl, SSL *oldstream, SSL *newstream) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].status & ISCONNECTION && ssl_ids[i].s == oldstream) { | |
| printf("replace_ids_connection\n"); | |
| ssl_ids[i].s = newstream; | |
| } | |
| } | |
| } | |
| /* | |
| * remove_marked_ids() - TOBEREMOVEDフラグが付いたストリームを削除 | |
| * | |
| * イベント処理中にストリームを直接削除すると、SSL_poll()の | |
| * 結果配列が不整合になる可能性がある。そのため、削除予定の | |
| * ストリームにTOBEREMOVEDフラグを付け、全イベント処理後に | |
| * この関数で実際の削除を行う。 | |
| * | |
| * 処理内容: | |
| * 1. TOBEREMOVEDフラグを持つエントリを探す | |
| * 2. SSL_free()でストリームを解放 | |
| * 3. スロットを未使用状態にリセット | |
| * | |
| * 注意: 1回の呼び出しで1つのエントリのみ削除(returnで終了) | |
| */ | |
| /* remove the ids marked for removal */ | |
| static void remove_marked_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].status & TOBEREMOVED) { | |
| printf("remove_id %llu\n", (unsigned long long) ssl_ids[i].id); | |
| SSL_free(ssl_ids[i].s); /* ストリームを解放 */ | |
| ssl_ids[i].s = NULL; /* ポインタをクリア */ | |
| ssl_ids[i].id = UINT64_MAX; /* 未使用マーカーに戻す */ | |
| ssl_ids[i].status = 0; /* ステータスをクリア */ | |
| return; /* 1つ削除したら終了 */ | |
| } | |
| } | |
| } | |
| /* | |
| * set_id_status() - 指定IDのストリームにステータスフラグを追加 | |
| * | |
| * 既存のstatusにビットORで新しいフラグを追加する。 | |
| * 例: CLIENTUNIOPEN状態のストリームにCLIENTCLOSEDを追加 | |
| * | |
| * パラメータ: | |
| * id: 対象のQUICストリームID | |
| * status: 追加するステータスフラグ | |
| * h3ssl: 状態管理構造体 | |
| */ | |
| /* add the status bytes to the status */ | |
| static void set_id_status(uint64_t id, int status, struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == id) { | |
| printf("set_id_status: %llu to %d\n", (unsigned long long) ssl_ids[i].id, status); | |
| ssl_ids[i].status = ssl_ids[i].status | status; /* ビットORで追加 */ | |
| return; | |
| } | |
| } | |
| /* 指定IDが見つからない場合はプログラムエラー */ | |
| printf("Oops can't set status, can't find stream!!!\n"); | |
| assert(0); | |
| } | |
| /* | |
| * get_id_status() - 指定IDのストリームのステータスを取得 | |
| * | |
| * パラメータ: | |
| * id: 対象のQUICストリームID | |
| * h3ssl: 状態管理構造体 | |
| * | |
| * 戻り値: ステータスフラグのビットOR値 | |
| */ | |
| static int get_id_status(uint64_t id, struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == id) { | |
| printf("get_id_status: %llu to %d\n", | |
| (unsigned long long) ssl_ids[i].id, ssl_ids[i].status); | |
| return ssl_ids[i].status; | |
| } | |
| } | |
| /* 指定IDが見つからない場合はプログラムエラー */ | |
| printf("Oops can't get status, can't find stream!!!\n"); | |
| assert(0); | |
| return -1; | |
| } | |
| /* | |
| * are_all_clientid_closed() - クライアントの全ストリームが閉じたか確認 | |
| * | |
| * HTTP/3接続の正常終了を判定するために使用。 | |
| * クライアントが開いた単方向ストリーム(制御、QPACKエンコーダ、デコーダ)が | |
| * 全て閉じられているかを確認する。 | |
| * | |
| * 処理内容: | |
| * 1. CLIENTUNIOPENフラグを持つストリームを探す | |
| * 2. CLIENTCLOSEDフラグも付いていれば、そのストリームを解放 | |
| * 3. まだ開いているストリームがあれば0を返す | |
| * | |
| * 戻り値: | |
| * 1: 全クライアントストリームが閉じている | |
| * 0: まだ開いているストリームがある | |
| */ | |
| /* check that all streams opened by the client are closed */ | |
| static int are_all_clientid_closed(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| /* 未使用スロットはスキップ */ | |
| if (ssl_ids[i].id == UINT64_MAX) | |
| continue; | |
| printf("are_all_clientid_closed: %llu status %d : %d\n", | |
| (unsigned long long) ssl_ids[i].id, ssl_ids[i].status, CLIENTUNIOPEN | CLIENTCLOSED); | |
| /* クライアントが開いた単方向ストリームをチェック */ | |
| if (ssl_ids[i].status & CLIENTUNIOPEN) { | |
| if (ssl_ids[i].status & CLIENTCLOSED) { | |
| /* 閉じられたストリームは解放 */ | |
| printf("are_all_clientid_closed: %llu closed\n", | |
| (unsigned long long) ssl_ids[i].id); | |
| SSL_free(ssl_ids[i].s); | |
| ssl_ids[i].s = NULL; | |
| ssl_ids[i].id = UINT64_MAX; | |
| continue; | |
| } | |
| /* まだ開いているストリームがある */ | |
| printf("are_all_clientid_closed: %llu open\n", (unsigned long long) ssl_ids[i].id); | |
| return 0; | |
| } | |
| } | |
| return 1; /* 全てのクライアントストリームが閉じた */ | |
| } | |
| /* | |
| * close_all_ids() - 全てのストリームを解放(リスナー・接続以外) | |
| * | |
| * 接続終了時に呼び出し、管理中の全ストリームを解放する。 | |
| * リスナーと接続は別途管理されるため、ここでは解放しない。 | |
| * | |
| * 注意: IDがUINT64_MAX以外の全エントリを解放。 | |
| * リスナー/接続はIDがUINT64_MAXなので影響を受けない。 | |
| */ | |
| /* free all the ids except listener and connection */ | |
| static void close_all_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| /* 未使用スロット(リスナー/接続含む)はスキップ */ | |
| if (ssl_ids[i].id == UINT64_MAX) | |
| continue; | |
| /* ストリームを解放してスロットをクリア */ | |
| SSL_free(ssl_ids[i].s); | |
| ssl_ids[i].s = NULL; | |
| ssl_ids[i].id = UINT64_MAX; | |
| } | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【nghttp3コールバック関数】 | |
| * | |
| * nghttp3ライブラリからHTTP/3イベントを受け取るコールバック。 | |
| * これらはnghttp3_callbacksに設定し、nghttp3_conn_server_new()に渡す。 | |
| * | |
| * 呼び出しタイミング: | |
| * nghttp3_conn_read_stream() → データをパース | |
| * → 適切なコールバックを呼び出し | |
| * | |
| * 【HTTP/3リクエスト受信の流れ】 | |
| * | |
| * 1. on_recv_header() × N回 (各ヘッダごと) | |
| * ↓ | |
| * 2. on_end_headers() × 1回 (ヘッダ終了) | |
| * ↓ | |
| * 3. on_recv_data() × N回 (リクエストボディがある場合) | |
| * ↓ | |
| * 4. on_end_stream() × 1回 (リクエスト終了) | |
| * | |
| * ============================================================================ | |
| */ | |
| /* | |
| * on_recv_header() - HTTPヘッダを1つ受信 | |
| * | |
| * HTTPリクエストの各ヘッダ(疑似ヘッダ含む)を受信するたびに呼ばれる。 | |
| * | |
| * パラメータ: | |
| * conn: nghttp3接続 | |
| * stream_id: リクエストのQUICストリームID | |
| * token: QPACKトークン(既知のヘッダ名の識別子) | |
| * name: ヘッダ名(参照カウント付きバッファ) | |
| * value: ヘッダ値(参照カウント付きバッファ) | |
| * flags: フラグ | |
| * user_data: h3ssl構造体 | |
| * stream_user_data: ストリーム固有データ(未使用) | |
| * | |
| * HTTP/3疑似ヘッダ: | |
| * :method - HTTPメソッド(GET, POST等) | |
| * :scheme - スキーム(https) | |
| * :authority - ホスト名 | |
| * :path - リクエストパス ← この関数で抽出してurl[]に保存 | |
| */ | |
| static int on_recv_header(nghttp3_conn *conn, int64_t stream_id, int32_t token, | |
| nghttp3_rcbuf *name, nghttp3_rcbuf *value, | |
| uint8_t flags, void *user_data, | |
| void *stream_user_data) | |
| { | |
| nghttp3_vec vname, vvalue; | |
| struct h3ssl *h3ssl = (struct h3ssl *)user_data; | |
| /* Received a single HTTP header. */ | |
| /* 参照カウント付きバッファからデータポインタを取得 */ | |
| vname = nghttp3_rcbuf_get_buf(name); | |
| vvalue = nghttp3_rcbuf_get_buf(value); | |
| /* デバッグ用: 受信したヘッダを出力 */ | |
| fwrite(vname.base, vname.len, 1, stdout); | |
| fprintf(stdout, ": "); | |
| fwrite(vvalue.base, vvalue.len, 1, stdout); | |
| fprintf(stdout, "\n"); | |
| /* | |
| * :path疑似ヘッダの処理 | |
| * | |
| * 例: :path: /index.html → url = "index.html" | |
| * :path: / → url = "index.html"(デフォルト) | |
| * :path: /foo/bar → url = "foo/bar" | |
| * | |
| * 先頭の'/'は削除して保存(ファイルパス構築時に便利) | |
| */ | |
| if (token == NGHTTP3_QPACK_TOKEN__PATH) { | |
| int len = (((vvalue.len) < (MAXURL)) ? (vvalue.len) : (MAXURL)); | |
| memset(h3ssl->url, 0, sizeof(h3ssl->url)); | |
| if (vvalue.base[0] == '/') { | |
| if (vvalue.base[1] == '\0') { | |
| /* ルートパス "/" → デフォルトファイル */ | |
| strncpy(h3ssl->url, "index.html", MAXURL); | |
| } else { | |
| /* 先頭の'/'を除いてコピー */ | |
| memcpy(h3ssl->url, vvalue.base + 1, len - 1); | |
| h3ssl->url[len - 1] = '\0'; | |
| } | |
| } else { | |
| /* '/'で始まらない場合はそのままコピー */ | |
| memcpy(h3ssl->url, vvalue.base, len); | |
| } | |
| } | |
| return 0; /* 0 = 成功 */ | |
| } | |
| /* | |
| * on_end_headers() - HTTPヘッダの受信完了通知 | |
| * | |
| * 全てのHTTPヘッダを受信し終わった時に呼ばれる。 | |
| * この時点でリクエストの内容(メソッド、パス等)が確定する。 | |
| * | |
| * パラメータ: | |
| * fin: ストリームが終了するか(ボディなしの場合は1) | |
| * | |
| * このデモでは: | |
| * end_headers_receivedフラグを立てて、メインループに | |
| * レスポンス送信の準備ができたことを通知する。 | |
| */ | |
| static int on_end_headers(nghttp3_conn *conn, int64_t stream_id, int fin, | |
| void *user_data, void *stream_user_data) | |
| { | |
| struct h3ssl *h3ssl = (struct h3ssl *)user_data; | |
| fprintf(stderr, "on_end_headers!\n"); | |
| h3ssl->end_headers_received = 1; /* レスポンス送信可能になった */ | |
| return 0; | |
| } | |
| /* | |
| * on_recv_data() - HTTPリクエストボディを受信 | |
| * | |
| * POSTリクエスト等でリクエストボディがある場合に呼ばれる。 | |
| * GETリクエストでは通常呼ばれない。 | |
| * | |
| * このデモでは: | |
| * デバッグ出力のみ。実際の処理は行わない。 | |
| * ファイルアップロード等を実装する場合は、ここでデータを処理。 | |
| */ | |
| static int on_recv_data(nghttp3_conn *conn, int64_t stream_id, | |
| const uint8_t *data, size_t datalen, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| fprintf(stderr, "on_recv_data! %ld\n", (unsigned long)datalen); | |
| fprintf(stderr, "on_recv_data! %.*s\n", (int)datalen, data); | |
| return 0; | |
| } | |
| /* | |
| * on_end_stream() - リクエストストリームの終了通知 | |
| * | |
| * クライアントがリクエストストリームを閉じた時に呼ばれる。 | |
| * これは通常、リクエスト全体の送信が完了したことを意味する。 | |
| * | |
| * このデモでは: | |
| * doneフラグを立てて、接続終了処理を開始できることを通知。 | |
| * | |
| * 注意: HTTP/3では、レスポンス送信後にこのコールバックが来る場合もある。 | |
| */ | |
| static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| struct h3ssl *h3ssl = (struct h3ssl *)conn_user_data; | |
| printf("on_end_stream!\n"); | |
| h3ssl->done = 1; /* ストリーム終了 */ | |
| return 0; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【ストリーム読み取り処理】 | |
| * | |
| * OpenSSL QUICからデータを読み取り、nghttp3に渡すブリッジ関数群。 | |
| * | |
| * 【データフロー】 | |
| * | |
| * [QUIC Packet] → SSL_read() → quic_server_read() | |
| * ↓ | |
| * nghttp3_conn_read_stream() | |
| * ↓ | |
| * [コールバック呼び出し] | |
| * on_recv_header(), on_end_headers(), etc. | |
| * | |
| * ============================================================================ | |
| */ | |
| /* | |
| * quic_server_read() - QUICストリームからデータを読み取りnghttp3に渡す | |
| * | |
| * OpenSSL SSL_read()でQUICストリームからデータを読み取り、 | |
| * nghttp3_conn_read_stream()に渡してHTTP/3フレームをパースさせる。 | |
| * | |
| * パラメータ: | |
| * h3conn: nghttp3接続 | |
| * stream: OpenSSL SSL*(読み取り元ストリーム) | |
| * id: QUICストリームID | |
| * h3ssl: 状態管理構造体 | |
| * | |
| * 戻り値: | |
| * 1: データを読み取った | |
| * 0: 読み取るデータがない(SSL_ERROR_WANT_READ) | |
| * -1: エラー | |
| */ | |
| /* Read from the stream and push to the h3conn */ | |
| static int quic_server_read(nghttp3_conn *h3conn, SSL *stream, uint64_t id, struct h3ssl *h3ssl) | |
| { | |
| int ret, r; | |
| uint8_t msg2[16000]; /* 読み取りバッファ */ | |
| size_t l = sizeof(msg2); | |
| /* 読み取り可能なデータがあるかチェック */ | |
| if (!SSL_has_pending(stream)) | |
| return 0; /* Nothing to read */ | |
| /* QUICストリームからデータを読み取る */ | |
| ret = SSL_read(stream, msg2, l); | |
| if (ret <= 0) { | |
| fprintf(stderr, "SSL_read %d on %llu failed\n", | |
| SSL_get_error(stream, ret), | |
| (unsigned long long) id); | |
| switch (SSL_get_error(stream, ret)) { | |
| case SSL_ERROR_WANT_READ: | |
| /* 非ブロッキングモードで、読み取るデータがない */ | |
| return 0; | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* ストリームが正常終了(FIN受信) */ | |
| return 1; | |
| default: | |
| /* その他のエラー */ | |
| ERR_print_errors_fp(stderr); | |
| return -1; | |
| } | |
| return -1; | |
| } | |
| /* | |
| * nghttp3にデータを渡してパースさせる | |
| * | |
| * 【ワークアラウンド】 | |
| * stream ID 2(クライアントのQPACKエンコーダストリーム)で | |
| * nghttp3が-607エラー(NGHTTP3_ERR_QPACK_ENCODER_STREAM_ERROR)を | |
| * 返すことがある(特にChromeで発生)。 | |
| * | |
| * 一度エラーが発生したら、以降はそのストリームからの | |
| * データをnghttp3に渡さないようにする。 | |
| */ | |
| /* XXX: work around nghttp3_conn_read_stream returning -607 on stream 2 */ | |
| if (!h3ssl->received_from_two && id != 2) { | |
| /* 通常のストリーム処理 */ | |
| r = nghttp3_conn_read_stream(h3conn, id, msg2, ret, 0); | |
| } else { | |
| /* stream 2は無視(ワークアラウンド) */ | |
| r = ret; /* ignore it for the moment ... */ | |
| } | |
| printf("nghttp3_conn_read_stream used %d of %d on %llu\n", r, | |
| ret, (unsigned long long) id); | |
| if (r != ret) { | |
| /* chrome returns -607 on stream 2 */ | |
| /* nghttp3が全てのデータを消費しなかった場合 */ | |
| if (!nghttp3_err_is_fatal(r)) { | |
| /* 致命的でないエラーは警告のみで続行 */ | |
| printf("nghttp3_conn_read_stream used %d of %d (not fatal) on %llu\n", r, | |
| ret, (unsigned long long) id); | |
| if (id == 2) | |
| h3ssl->received_from_two = 1; /* stream 2のワークアラウンドフラグ */ | |
| return 1; | |
| } | |
| /* 致命的エラー */ | |
| return -1; | |
| } | |
| return 1; /* 正常に読み取り完了 */ | |
| } | |
| /* | |
| * quic_server_h3streams() - HTTP/3必須の単方向ストリームを作成 | |
| * | |
| * HTTP/3プロトコルでは、サーバーは以下の3つの単方向ストリームを | |
| * クライアントに対して作成する必要がある: | |
| * | |
| * 1. 制御ストリーム (Control Stream) | |
| * - SETTINGSフレーム、GOAWAYフレーム等を送受信 | |
| * - 接続の設定情報を交換 | |
| * | |
| * 2. QPACKエンコーダストリーム (QPACK Encoder Stream) | |
| * - QPACKヘッダ圧縮の動的テーブル更新を送信 | |
| * - ヘッダブロックで参照される前に動的テーブルエントリを挿入 | |
| * | |
| * 3. QPACKデコーダストリーム (QPACK Decoder Stream) | |
| * - 動的テーブルエントリの受信確認を送信 | |
| * - 相手のエンコーダに対してACKを返す | |
| * | |
| * 【ストリームID】 | |
| * サーバー開始の単方向ストリームは、ID 3, 7, 11, ... の順で割り当てられる。 | |
| * (ビット0=1: サーバー開始, ビット1=1: 単方向) | |
| * | |
| * 戻り値: | |
| * 0: 成功 | |
| * -1: エラー | |
| */ | |
| /* | |
| * creates the control stream, the encoding and decoding streams. | |
| * nghttp3_conn_bind_control_stream() is for the control stream. | |
| */ | |
| static int quic_server_h3streams(nghttp3_conn *h3conn, struct h3ssl *h3ssl) | |
| { | |
| SSL *rstream = NULL; /* QPACKデコーダストリーム (receiver/decoder) */ | |
| SSL *pstream = NULL; /* QPACKエンコーダストリーム (pusher/encoder) */ | |
| SSL *cstream = NULL; /* 制御ストリーム (control) */ | |
| SSL *conn; | |
| uint64_t r_streamid, p_streamid, c_streamid; | |
| /* QUIC接続を取得 */ | |
| conn = get_ids_connection(h3ssl); | |
| if (conn == NULL) { | |
| fprintf(stderr, "quic_server_h3streams no connection\n"); | |
| fflush(stderr); | |
| return -1; | |
| } | |
| /* | |
| * 3つの単方向ストリームを作成 | |
| * SSL_STREAM_FLAG_UNI: 単方向ストリーム(送信のみ可能) | |
| */ | |
| /* QPACKデコーダストリーム */ | |
| rstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (rstream != NULL) { | |
| printf("=> Opened on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(rstream)); | |
| } else { | |
| fprintf(stderr, "=> Stream == NULL!\n"); | |
| goto err; | |
| } | |
| /* QPACKエンコーダストリーム */ | |
| pstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (pstream != NULL) { | |
| printf("=> Opened on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(pstream)); | |
| } else { | |
| fprintf(stderr, "=> Stream == NULL!\n"); | |
| goto err; | |
| } | |
| /* 制御ストリーム */ | |
| cstream = SSL_new_stream(conn, SSL_STREAM_FLAG_UNI); | |
| if (cstream != NULL) { | |
| fprintf(stderr, "=> Opened on %llu\n", | |
| (unsigned long long)SSL_get_stream_id(cstream)); | |
| fflush(stderr); | |
| } else { | |
| fprintf(stderr, "=> Stream == NULL!\n"); | |
| goto err; | |
| } | |
| /* 各ストリームのIDを取得 */ | |
| r_streamid = SSL_get_stream_id(rstream); | |
| p_streamid = SSL_get_stream_id(pstream); | |
| c_streamid = SSL_get_stream_id(cstream); | |
| /* | |
| * nghttp3にストリームをバインド | |
| * | |
| * nghttp3_conn_bind_qpack_streams(): | |
| * QPACKエンコーダ/デコーダストリームを関連付け | |
| * これによりnghttp3がQPACKの動的テーブル管理を行える | |
| * | |
| * nghttp3_conn_bind_control_stream(): | |
| * 制御ストリームを関連付け | |
| * これによりSETTINGS等のフレームを送受信できる | |
| */ | |
| if (nghttp3_conn_bind_qpack_streams(h3conn, p_streamid, r_streamid)) { | |
| fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n"); | |
| goto err; | |
| } | |
| if (nghttp3_conn_bind_control_stream(h3conn, c_streamid)) { | |
| fprintf(stderr, "nghttp3_conn_bind_qpack_streams failed!\n"); | |
| goto err; | |
| } | |
| printf("control: %llu enc %llu dec %llu\n", | |
| (unsigned long long)c_streamid, | |
| (unsigned long long)p_streamid, | |
| (unsigned long long)r_streamid); | |
| /* 作成したストリームをssl_ids配列に登録 */ | |
| add_id(SSL_get_stream_id(rstream), rstream, h3ssl); | |
| add_id(SSL_get_stream_id(pstream), pstream, h3ssl); | |
| add_id(SSL_get_stream_id(cstream), cstream, h3ssl); | |
| return 0; | |
| err: | |
| fflush(stderr); | |
| SSL_free(rstream); | |
| SSL_free(pstream); | |
| SSL_free(cstream); | |
| return -1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【SSL_poll()イベントループ】 | |
| * | |
| * OpenSSL SSL_poll()を使用して、複数のQUICオブジェクトを効率的に監視する。 | |
| * | |
| * 【SSL_poll()の概要】 | |
| * | |
| * SSL_poll()は、複数のSSL/QUICオブジェクトの状態変化を一度に監視できる | |
| * OpenSSL 3.3で追加されたAPI。UNIX poll(2)に似たセマンティクス。 | |
| * | |
| * 【SSL_POLL_ITEM構造体】 | |
| * | |
| * struct SSL_POLL_ITEM { | |
| * SSL_POLL_DESCRIPTOR desc; // 監視対象のSSLオブジェクト | |
| * uint64_t events; // 監視したいイベント | |
| * uint64_t revents; // 発生したイベント(出力) | |
| * }; | |
| * | |
| * 【主要なイベントタイプ】 | |
| * | |
| * SSL_POLL_EVENT_IC: Incoming Connection - 新規接続受け入れ可能 | |
| * SSL_POLL_EVENT_ISB: Incoming Stream Bidi - 双方向ストリーム受け入れ可能 | |
| * SSL_POLL_EVENT_ISU: Incoming Stream Uni - 単方向ストリーム受け入れ可能 | |
| * SSL_POLL_EVENT_OSB: Outgoing Stream Bidi - 双方向ストリーム作成可能 | |
| * SSL_POLL_EVENT_OSU: Outgoing Stream Uni - 単方向ストリーム作成可能 | |
| * SSL_POLL_EVENT_R: Readable - 読み取り可能データあり | |
| * SSL_POLL_EVENT_W: Writable - 書き込み可能 | |
| * SSL_POLL_EVENT_ER: Exception Read - 読み取り側でエラー/終了 | |
| * SSL_POLL_EVENT_EW: Exception Write - 書き込み側でエラー/終了 | |
| * SSL_POLL_EVENT_EC: Exception Connection - 接続終了開始 | |
| * SSL_POLL_EVENT_ECD: Exception Connection Done - 接続終了完了 | |
| * | |
| * ============================================================================ | |
| */ | |
| /* | |
| * read_from_ssl_ids() - SSL_poll()でイベントを監視し処理 | |
| * | |
| * このデモのメインイベント処理関数。ssl_ids配列内の全オブジェクトを | |
| * SSL_poll()で監視し、発生したイベントに応じた処理を行う。 | |
| * | |
| * パラメータ: | |
| * curh3conn: nghttp3接続へのポインタ(新規接続時に再作成される) | |
| * h3ssl: 状態管理構造体 | |
| * | |
| * 戻り値: | |
| * >0: 何らかのイベントを処理した | |
| * 0: イベントなし(タイムアウト) | |
| * -1: エラー | |
| */ | |
| /* Try to read from the streams we have */ | |
| static int read_from_ssl_ids(nghttp3_conn **curh3conn, struct h3ssl *h3ssl) | |
| { | |
| int hassomething = 0, i; | |
| struct ssl_id *ssl_ids = h3ssl->ssl_ids; | |
| SSL_POLL_ITEM items[MAXSSL_IDS] = {0}, *item = items; | |
| static const struct timeval nz_timeout = {0, 0}; /* 即時リターン(ノンブロッキング) */ | |
| size_t result_count = SIZE_MAX; | |
| int numitem = 0, ret; | |
| uint64_t processed_event = 0; | |
| int has_ids_to_remove = 0; | |
| nghttp3_conn *h3conn = *curh3conn; | |
| /* | |
| * 【ステップ1】SSL_POLL_ITEM配列を構築 | |
| * | |
| * ssl_ids配列内の全アクティブなSSLオブジェクトを | |
| * SSL_poll()に渡すための配列に追加する。 | |
| */ | |
| /* | |
| * Process all the streams | |
| * the first one is the connection if we get something here is a new stream | |
| */ | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].s != NULL) { | |
| /* SSL*をSSL_POLL_DESCRIPTORに変換 */ | |
| item->desc = SSL_as_poll_descriptor(ssl_ids[i].s); | |
| item->events = UINT64_MAX; /* TODO adjust to the event we need process */ | |
| /* 全イベントを監視(本番では必要なもののみ) */ | |
| item->revents = UINT64_MAX; /* TODO adjust to the event we need process */ | |
| numitem++; | |
| item++; | |
| } | |
| } | |
| /* | |
| * 【ステップ2】SSL_poll()を呼び出し | |
| * | |
| * SSL_POLL_FLAG_NO_HANDLE_EVENTS: | |
| * SSL_poll()内部でSSL_handle_events()を自動呼び出ししない。 | |
| * 代わりにhandle_events_from_ids()で明示的に呼び出す。 | |
| * | |
| * これにより、イベント処理のタイミングをより細かく制御できる。 | |
| * 通常はSSL_poll()が内部で自動的にtickingを行う。 | |
| */ | |
| /* | |
| * SSL_POLL_FLAG_NO_HANDLE_EVENTS would require to use: | |
| * SSL_get_event_timeout on the connection stream | |
| * select/wait using the timeout value (which could be no wait time) | |
| * SSL_handle_events | |
| * SSL_poll | |
| * for the moment we let SSL_poll to performs ticking internally | |
| * on an automatic basis. | |
| */ | |
| ret = SSL_poll(items, numitem, sizeof(SSL_POLL_ITEM), &nz_timeout, | |
| SSL_POLL_FLAG_NO_HANDLE_EVENTS, &result_count); | |
| if (!ret) { | |
| fprintf(stderr, "SSL_poll failed\n"); | |
| printf("SSL_poll failed\n"); | |
| return -1; /* something is wrong */ | |
| } | |
| printf("read_from_ssl_ids %ld events\n", (unsigned long)result_count); | |
| if (result_count == 0) { | |
| /* Timeout may be something somewhere */ | |
| /* イベントなし - タイムアウト */ | |
| return 0; | |
| } | |
| /* | |
| * 【ステップ3】状態フラグをリセット | |
| * | |
| * イベント処理中に設定される可能性のあるフラグを | |
| * 事前にリセットしておく。 | |
| */ | |
| /* reset the states */ | |
| h3ssl->new_conn = 0; | |
| h3ssl->restart = 0; | |
| h3ssl->done = 0; | |
| /* | |
| * 【ステップ4】イベントをディスパッチ | |
| * | |
| * SSL_poll()の結果を順に処理し、各イベントタイプに応じた | |
| * 処理を実行する。 | |
| */ | |
| /* Process all the item we have polled */ | |
| for (i = 0, item = items; i < numitem; i++, item++) { | |
| SSL *s; | |
| /* イベントが発生していないアイテムはスキップ */ | |
| if (item->revents == SSL_POLL_EVENT_NONE) | |
| continue; | |
| processed_event = 0; | |
| /* get the stream */ | |
| s = item->desc.value.ssl; | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_IC】新規接続受け入れ可能 | |
| * | |
| * リスナーに対して発生するイベント。クライアントからの | |
| * 新しいQUIC接続をSSL_accept_connection()で受け入れる。 | |
| */ | |
| /* New connection */ | |
| if (item->revents & SSL_POLL_EVENT_IC) { | |
| SSL *conn = SSL_accept_connection(item->desc.value.ssl, 0); | |
| SSL *oldconn; | |
| printf("SSL_accept_connection\n"); | |
| if (conn == NULL) { | |
| fprintf(stderr, "error while accepting connection\n"); | |
| ret = -1; | |
| goto err; | |
| } | |
| /* the previous might be still there */ | |
| oldconn = get_ids_connection(h3ssl); | |
| if (oldconn != NULL) { | |
| /* XXX we support only one connection for the moment */ | |
| printf("SSL_accept_connection closing previous\n"); | |
| SSL_free(oldconn); | |
| replace_ids_connection(h3ssl, oldconn, conn); | |
| reuse_h3ssl(h3ssl); | |
| close_all_ids(h3ssl); | |
| h3ssl->id_bidi = UINT64_MAX; | |
| h3ssl->has_uni = 0; | |
| } else { | |
| printf("SSL_accept_connection first connection\n"); | |
| add_ids_connection(h3ssl, conn); | |
| } | |
| h3ssl->new_conn = 1; | |
| /* create the new h3conn */ | |
| nghttp3_conn_del(*curh3conn); | |
| nghttp3_settings_default(&settings); | |
| if (nghttp3_conn_server_new(curh3conn, &callbacks, &settings, mem, | |
| h3ssl)) { | |
| fprintf(stderr, "nghttp3_conn_client_new failed!\n"); | |
| exit(1); | |
| } | |
| h3conn = *curh3conn; | |
| hassomething++; | |
| if (!SSL_set_incoming_stream_policy(conn, | |
| SSL_INCOMING_STREAM_POLICY_ACCEPT, 0)) { | |
| fprintf(stderr, "error while setting inccoming stream policy\n"); | |
| ret = -1; | |
| goto err; | |
| } | |
| printf("SSL_accept_connection\n"); | |
| processed_event = processed_event | SSL_POLL_EVENT_IC; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ISB / SSL_POLL_EVENT_ISU】 | |
| * 新規ストリーム受け入れ可能 | |
| * | |
| * ISB: 双方向ストリーム(Bidirectional)- リクエスト/レスポンス用 | |
| * ISU: 単方向ストリーム(Unidirectional)- HTTP/3制御用 | |
| * | |
| * SSL_accept_stream()で新しいストリームを受け入れる。 | |
| */ | |
| /* SSL_accept_stream if SSL_POLL_EVENT_ISB or SSL_POLL_EVENT_ISU */ | |
| if ((item->revents & SSL_POLL_EVENT_ISB) || | |
| (item->revents & SSL_POLL_EVENT_ISU)) { | |
| SSL *stream = SSL_accept_stream(item->desc.value.ssl, 0); | |
| uint64_t new_id; | |
| int r; | |
| if (stream == NULL) { | |
| ret = -1; | |
| goto err; | |
| } | |
| new_id = SSL_get_stream_id(stream); | |
| printf("=> Received connection on %lld %d\n", (unsigned long long) new_id, | |
| SSL_get_stream_type(stream)); | |
| add_id(new_id, stream, h3ssl); | |
| if (h3ssl->close_wait) { | |
| printf("in close_wait so we will have a new request\n"); | |
| reuse_h3ssl(h3ssl); | |
| h3ssl->restart = 1; /* Checked in wait_close loop */ | |
| } | |
| if (SSL_get_stream_type(stream) == SSL_STREAM_TYPE_BIDI) { | |
| /* bidi that is the id where we have to send the response */ | |
| if (h3ssl->id_bidi != UINT64_MAX) { | |
| set_id_status(h3ssl->id_bidi, TOBEREMOVED, h3ssl); | |
| has_ids_to_remove++; | |
| } | |
| h3ssl->id_bidi = new_id; | |
| reuse_h3ssl(h3ssl); | |
| h3ssl->restart = 1; | |
| } else { | |
| set_id_status(new_id, CLIENTUNIOPEN, h3ssl); | |
| } | |
| r = quic_server_read(h3conn, stream, new_id, h3ssl); | |
| if (r == -1) { | |
| ret = -1; | |
| goto err; | |
| } | |
| if (r == 1) | |
| hassomething++; | |
| if (item->revents & SSL_POLL_EVENT_ISB) | |
| processed_event = processed_event | SSL_POLL_EVENT_ISB; | |
| if (item->revents & SSL_POLL_EVENT_ISU) | |
| processed_event = processed_event | SSL_POLL_EVENT_ISU; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_OSB】双方向ストリーム作成可能 | |
| * | |
| * サーバーから双方向ストリームを作成できる状態。 | |
| * HTTP/3では通常サーバーからの双方向ストリームは使用しないため、 | |
| * このイベントは無視する。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_OSB) { | |
| /* Create new streams when allowed */ | |
| /* at least one bidi */ | |
| processed_event = processed_event | SSL_POLL_EVENT_OSB; | |
| printf("Create bidi?\n"); | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_OSU】単方向ストリーム作成可能 | |
| * | |
| * サーバーから単方向ストリームを作成できる状態。 | |
| * HTTP/3の必須ストリーム(制御、QPACKエンコーダ/デコーダ)を | |
| * まだ作成していなければ、ここで作成する。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_OSU) { | |
| /* at least one uni */ | |
| /* we have 4 streams from the client 2, 6 , 10 and 0 */ | |
| /* need 3 streams to the client */ | |
| printf("Create uni?\n"); | |
| processed_event = processed_event | SSL_POLL_EVENT_OSU; | |
| if (!h3ssl->has_uni) { | |
| /* HTTP/3必須の3つの単方向ストリームを作成 */ | |
| printf("Create uni\n"); | |
| ret = quic_server_h3streams(h3conn, h3ssl); | |
| if (ret == -1) { | |
| fprintf(stderr, "quic_server_h3streams failed!\n"); | |
| goto err; | |
| } | |
| h3ssl->has_uni = 1; /* 作成済みフラグを立てる */ | |
| hassomething++; | |
| } | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_EC】接続終了開始 | |
| * | |
| * QUIC接続の終了処理が開始された。 | |
| * CONNECTION_CLOSE フレームを受信した、または送信した状態。 | |
| * | |
| * 終了処理は2段階: | |
| * 1. EC (Connection closing) - 終了開始 | |
| * 2. ECD (Connection done) - 終了完了 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_EC) { | |
| /* the connection begins terminating */ | |
| printf("Connection terminating\n"); | |
| printf("Connection terminating restart %d\n", h3ssl->restart); | |
| if (!h3ssl->close_done) { | |
| h3ssl->close_done = 1; /* 終了開始をマーク */ | |
| } else { | |
| h3ssl->done = 1; /* 既にclose_doneなら完全終了 */ | |
| } | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_EC; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ECD】接続終了完了 | |
| * | |
| * QUIC接続の終了処理が完了した。 | |
| * この後、接続に関連するリソースを解放できる。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_ECD) { | |
| /* the connection is terminated */ | |
| printf("Connection terminated\n"); | |
| h3ssl->done = 1; /* 完全終了 */ | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_ECD; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_R】読み取り可能 | |
| * | |
| * ストリームから読み取り可能なデータがある。 | |
| * quic_server_read()でデータを読み取り、nghttp3に渡す。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_R) { | |
| /* try to read */ | |
| uint64_t id = UINT64_MAX; | |
| int r; | |
| /* get the id, well the connection has no id... */ | |
| id = SSL_get_stream_id(item->desc.value.ssl); | |
| printf("revent READ on %llu\n", (unsigned long long)id); | |
| r = quic_server_read(h3conn, s, id, h3ssl); | |
| if (r == 0) { | |
| /* 読み取るデータがない - ストリーム終了を確認 */ | |
| uint8_t msg[1]; | |
| size_t l = sizeof(msg); | |
| /* check that the other side is closed */ | |
| r = SSL_read(s, msg, l); | |
| printf("SSL_read tells %d\n", r); | |
| if (r > 0) { | |
| ret = -1; | |
| goto err; | |
| } | |
| r = SSL_get_error(s, r); | |
| if (r != SSL_ERROR_ZERO_RETURN) { | |
| /* SSL_ERROR_ZERO_RETURN以外はエラー */ | |
| ret = -1; | |
| goto err; | |
| } | |
| /* ストリームが正常終了 - 削除予約 */ | |
| set_id_status(id, TOBEREMOVED, h3ssl); | |
| has_ids_to_remove++; | |
| continue; | |
| } | |
| if (r == -1) { | |
| ret = -1; | |
| goto err; | |
| } | |
| hassomething++; | |
| processed_event = processed_event | SSL_POLL_EVENT_R; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_ER】読み取り側例外 | |
| * | |
| * ストリームの読み取り側が閉じられた。 | |
| * クライアントがFINを送信した状態。 | |
| * | |
| * クライアントの単方向ストリームの場合、 | |
| * CLIENTCLOSEDフラグを追加する。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_ER) { | |
| /* mark it closed */ | |
| uint64_t id = UINT64_MAX; | |
| int status; | |
| id = SSL_get_stream_id(item->desc.value.ssl); | |
| status = get_id_status(id, h3ssl); | |
| printf("revent exception READ on %llu\n", (unsigned long long)id); | |
| if (status & CLIENTUNIOPEN) { | |
| /* クライアントの単方向ストリームが閉じた */ | |
| set_id_status(id, CLIENTCLOSED, h3ssl); | |
| hassomething++; | |
| } | |
| processed_event = processed_event | SSL_POLL_EVENT_ER; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_W】書き込み可能 | |
| * | |
| * ストリームに書き込み可能。 | |
| * このデモでは無視(レスポンス送信は別の場所で行う)。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_W) { | |
| /* we ignore those for the moment */ | |
| processed_event = processed_event | SSL_POLL_EVENT_W; | |
| } | |
| /* | |
| * 【イベント: SSL_POLL_EVENT_EW】書き込み側例外 | |
| * | |
| * ストリームの書き込み側で例外が発生。 | |
| * 通常はSTOP_SENDINGフレームを受信した場合。 | |
| * | |
| * サーバー側が既に閉じていれば、両方向とも閉じたので | |
| * ストリームを削除できる。 | |
| */ | |
| if (item->revents & SSL_POLL_EVENT_EW) { | |
| /* write part received a STOP_SENDING */ | |
| uint64_t id = UINT64_MAX; | |
| int status; | |
| id = SSL_get_stream_id(item->desc.value.ssl); | |
| status = get_id_status(id, h3ssl); | |
| if (status & SERVERCLOSED) { | |
| /* サーバーも閉じていれば完全に削除可能 */ | |
| printf("both sides closed on %llu\n", (unsigned long long)id); | |
| set_id_status(id, TOBEREMOVED, h3ssl); | |
| has_ids_to_remove++; | |
| hassomething++; | |
| } | |
| processed_event = processed_event | SSL_POLL_EVENT_EW; | |
| } | |
| /* 未処理のイベントがあれば警告を出力 */ | |
| if (item->revents != processed_event) { | |
| /* Figure out ??? */ | |
| uint64_t id = UINT64_MAX; | |
| id = SSL_get_stream_id(item->desc.value.ssl); | |
| printf("revent %llu (%d) on %llu NOT PROCESSED!\n", | |
| (unsigned long long)item->revents, SSL_POLL_EVENT_W, | |
| (unsigned long long)id); | |
| } | |
| } | |
| ret = hassomething; | |
| err: | |
| /* 削除予約されたストリームを実際に削除 */ | |
| if (has_ids_to_remove) | |
| remove_marked_ids(h3ssl); | |
| return ret; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【イベントハンドリング・ファイル処理】 | |
| * ============================================================================ | |
| */ | |
| /* | |
| * handle_events_from_ids() - QUIC内部イベントを処理 | |
| * | |
| * SSL_handle_events()を呼び出して、QUICプロトコルの内部処理を行う。 | |
| * | |
| * QUICは時間ベースの処理(再送タイマー、アイドルタイムアウト等)が必要。 | |
| * SSL_handle_events()は、リスナーや接続に対してこれらの処理を実行する。 | |
| * | |
| * SSL_POLL_FLAG_NO_HANDLE_EVENTSを使用している場合、 | |
| * 手動でこの関数を呼び出す必要がある。 | |
| */ | |
| static void handle_events_from_ids(struct h3ssl *h3ssl) | |
| { | |
| struct ssl_id *ssl_ids = h3ssl->ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| /* リスナーまたは接続に対してのみ呼び出す */ | |
| if (ssl_ids[i].s != NULL && | |
| (ssl_ids[i].status & ISCONNECTION || ssl_ids[i].status & ISLISTENER)) { | |
| if (SSL_handle_events(ssl_ids[i].s)) | |
| ERR_print_errors_fp(stderr); | |
| } | |
| } | |
| } | |
| /* | |
| * get_file_length() - リクエストされたファイルのサイズを取得 | |
| * | |
| * h3ssl->urlとh3ssl->fileprefixからファイルパスを構築し、 | |
| * stat()でファイルサイズを取得する。 | |
| * | |
| * 特別なURL "big" の場合は INT_MAX を返す(テスト用の無限ストリーム)。 | |
| * | |
| * 戻り値: | |
| * ファイルサイズ(バイト) | |
| * ファイルが見つからない/通常ファイルでない場合は0 | |
| */ | |
| static size_t get_file_length(struct h3ssl *h3ssl) | |
| { | |
| char filename[PATH_MAX]; | |
| struct stat st; | |
| /* ファイルパスを構築: fileprefix + url */ | |
| memset(filename, 0, PATH_MAX); | |
| if (h3ssl->fileprefix != NULL) | |
| strcat(filename, h3ssl->fileprefix); | |
| strcat(filename, h3ssl->url); | |
| /* "big" は特別なテスト用パス - 無限ストリーム */ | |
| if (strcmp(h3ssl->url, "big") == 0) { | |
| printf("big!!!\n"); | |
| return (size_t)INT_MAX; | |
| } | |
| /* ファイルのメタデータを取得 */ | |
| if (stat(filename, &st) == 0) { | |
| /* Only process regular files */ | |
| /* 通常ファイルのみ処理(ディレクトリ等は除外) */ | |
| if (S_ISREG(st.st_mode)) { | |
| printf("get_file_length %s %lld\n", filename, (unsigned long long) st.st_size); | |
| return (size_t)st.st_size; | |
| } | |
| } | |
| printf("Can't get_file_length %s\n", filename); | |
| return 0; | |
| } | |
| /* | |
| * get_file_data() - ファイルの内容を読み込む | |
| * | |
| * リクエストされたファイルをメモリに読み込んで返す。 | |
| * | |
| * 戻り値: | |
| * ファイル内容を含む動的確保されたバッファ(呼び出し元が解放責任) | |
| * エラー時はNULL | |
| */ | |
| static char *get_file_data(struct h3ssl *h3ssl) | |
| { | |
| char filename[PATH_MAX]; | |
| size_t size = get_file_length(h3ssl); | |
| char *res; | |
| int fd; | |
| if (size == 0) | |
| return NULL; | |
| /* ファイルパスを構築 */ | |
| memset(filename, 0, PATH_MAX); | |
| if (h3ssl->fileprefix != NULL) | |
| strcat(filename, h3ssl->fileprefix); | |
| strcat(filename, h3ssl->url); | |
| /* バッファを確保してファイル内容を読み込む */ | |
| res = malloc(size+1); | |
| res[size] = '\0'; | |
| fd = open(filename, O_RDONLY); | |
| if (read(fd, res, size) == -1) { | |
| close(fd); | |
| free(res); | |
| return NULL; | |
| } | |
| close(fd); | |
| printf("read from %s : %zu\n", filename, size); | |
| return res; | |
| } | |
| /* | |
| * step_read_data() - nghttp3からのデータ読み取りコールバック | |
| * | |
| * nghttp3_conn_submit_response()で設定したnghttp3_data_readerの | |
| * read_dataコールバック。nghttp3がレスポンスボディのデータを | |
| * 必要とする時に呼び出される。 | |
| * | |
| * 【チャンク送信の仕組み】 | |
| * 大きなファイルは4096バイトずつに分割して送信。 | |
| * nghttp3_vecにデータへのポインタと長さを設定し、 | |
| * nghttp3がそれをHTTP/3フレームにパッケージングする。 | |
| * | |
| * パラメータ: | |
| * conn: nghttp3接続 | |
| * stream_id: レスポンスストリームID | |
| * vec: データを設定するベクタ配列(出力) | |
| * veccnt: vecの要素数 | |
| * pflags: フラグ(NGHTTP3_DATA_FLAG_EOF等) | |
| * user_data: h3ssl構造体 | |
| * stream_user_data: ストリーム固有データ | |
| * | |
| * 戻り値: | |
| * 設定したvec要素数(通常は1) | |
| * 0: データ終了 | |
| */ | |
| static nghttp3_ssize step_read_data(nghttp3_conn *conn, int64_t stream_id, | |
| nghttp3_vec *vec, size_t veccnt, | |
| uint32_t *pflags, void *user_data, | |
| void *stream_user_data) | |
| { | |
| struct h3ssl *h3ssl = (struct h3ssl *)user_data; | |
| /* 既にデータ送信完了の場合 */ | |
| if (h3ssl->datadone) { | |
| *pflags = NGHTTP3_DATA_FLAG_EOF; /* End Of File フラグ */ | |
| return 0; | |
| } | |
| /* send the data */ | |
| printf("step_read_data for %s %zu\n", h3ssl->url, h3ssl->ldata); | |
| if (h3ssl->ldata <= 4096) { | |
| /* | |
| * 残りデータが4096バイト以下 - 最後のチャンク | |
| */ | |
| vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]); | |
| vec[0].len = h3ssl->ldata; | |
| h3ssl->datadone++; /* 送信完了フラグ */ | |
| *pflags = NGHTTP3_DATA_FLAG_EOF; /* これが最後のデータ */ | |
| } else { | |
| /* | |
| * まだ4096バイト以上残っている - チャンク送信 | |
| */ | |
| vec[0].base = &(h3ssl->ptr_data[h3ssl->offset_data]); | |
| vec[0].len = 4096; | |
| if (h3ssl->ldata == INT_MAX) { | |
| /* "big" テスト用: 無限ストリーム(オフセットを進めない) */ | |
| printf("big = endless!\n"); | |
| } else { | |
| /* 通常のファイル: オフセットを進めて残りを減らす */ | |
| h3ssl->offset_data = h3ssl->offset_data + 4096; | |
| h3ssl->ldata = h3ssl->ldata - 4096; | |
| } | |
| } | |
| return 1; /* 1つのvec要素を設定 */ | |
| } | |
| /* | |
| * quic_server_write() - QUICストリームにデータを書き込む | |
| * | |
| * nghttp3_conn_writev_stream()で取得したデータを、 | |
| * 対応するQUICストリームに書き込む。 | |
| * | |
| * パラメータ: | |
| * h3ssl: 状態管理構造体 | |
| * streamid: 書き込み先のQUICストリームID | |
| * buff: 書き込むデータ | |
| * len: データ長 | |
| * flags: SSL_write_ex2()のフラグ(SSL_WRITE_FLAG_CONCLUDE等) | |
| * written: 実際に書き込んだバイト数(出力) | |
| * | |
| * 戻り値: | |
| * 1: 成功 | |
| * 0: エラー(ストリームが見つからない等) | |
| */ | |
| static int quic_server_write(struct h3ssl *h3ssl, uint64_t streamid, | |
| uint8_t *buff, size_t len, uint64_t flags, | |
| size_t *written) | |
| { | |
| struct ssl_id *ssl_ids; | |
| int i; | |
| ssl_ids = h3ssl->ssl_ids; | |
| /* 指定IDのストリームを探す */ | |
| for (i = 0; i < MAXSSL_IDS; i++) { | |
| if (ssl_ids[i].id == streamid) { | |
| /* | |
| * SSL_write_ex2() でデータを書き込む | |
| * | |
| * flags引数により、以下のオプションが指定可能: | |
| * SSL_WRITE_FLAG_CONCLUDE: データと同時にFINを送信 | |
| * (ストリーム終了を効率的に通知) | |
| */ | |
| if (!SSL_write_ex2(ssl_ids[i].s, buff, len, flags, written) || | |
| *written != len) { | |
| fprintf(stderr, "couldn't write on connection\n"); | |
| ERR_print_errors_fp(stderr); | |
| return 0; | |
| } | |
| printf("written %lld on %lld flags %lld\n", (unsigned long long)len, | |
| (unsigned long long)streamid, (unsigned long long)flags); | |
| return 1; | |
| } | |
| } | |
| /* ストリームが見つからない */ | |
| printf("quic_server_write %lld on %lld (NOT FOUND!)\n", (unsigned long long)len, | |
| (unsigned long long)streamid); | |
| return 0; | |
| } | |
| /* | |
| * ============================================================================ | |
| * 【SSL_CTX / ソケット初期化】 | |
| * ============================================================================ | |
| */ | |
| #define OSSL_NELEM(x) (sizeof(x) / sizeof((x)[0])) | |
| /* | |
| * This is a basic demo of QUIC server functionality in which one connection at | |
| * a time is accepted in a blocking loop. | |
| */ | |
| /* | |
| * 【ALPN (Application-Layer Protocol Negotiation) 設定】 | |
| * | |
| * HTTP/3では、ALPNを使用してプロトコルを識別する。 | |
| * TLSハンドシェイク中にクライアントとサーバーが | |
| * 使用するアプリケーションプロトコルをネゴシエートする。 | |
| * | |
| * フォーマット: 長さプレフィックス + プロトコル名 | |
| * [5] "h3-29" - HTTP/3 draft-29(開発版) | |
| * [2] "h3" - HTTP/3 正式版(RFC 9114) | |
| * | |
| * クライアントが両方をサポートしていれば "h3" が優先される。 | |
| */ | |
| /* ALPN string for TLS handshake. We pretent h3-29 and h3 */ | |
| static const unsigned char alpn_ossltest[] = { 5, 'h', '3', '-', '2', | |
| '9', 2, 'h', '3' }; | |
| /* | |
| * select_alpn() - ALPNネゴシエーションコールバック | |
| * | |
| * TLSハンドシェイク中に呼び出され、クライアントが提示した | |
| * プロトコルリストからサーバーがサポートするプロトコルを選択する。 | |
| * | |
| * パラメータ: | |
| * ssl: SSL接続 | |
| * out: 選択されたプロトコル名(出力) | |
| * out_len: 選択されたプロトコル名の長さ(出力) | |
| * in: クライアントが提示したプロトコルリスト | |
| * in_len: リストの長さ | |
| * arg: コールバック引数(未使用) | |
| * | |
| * 戻り値: | |
| * SSL_TLSEXT_ERR_OK: ネゴシエーション成功 | |
| * SSL_TLSEXT_ERR_ALERT_FATAL: 一致するプロトコルなし(接続を拒否) | |
| */ | |
| /* | |
| * This callback validates and negotiates the desired ALPN on the server side. | |
| */ | |
| static int select_alpn(SSL *ssl, const unsigned char **out, | |
| unsigned char *out_len, const unsigned char *in, | |
| unsigned int in_len, void *arg) | |
| { | |
| /* | |
| * SSL_select_next_proto(): NPN/ALPNプロトコル選択ヘルパー | |
| * | |
| * サーバーのリスト (alpn_ossltest) とクライアントのリスト (in) を比較し、 | |
| * 最初に一致したプロトコルを選択する。 | |
| */ | |
| if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, | |
| sizeof(alpn_ossltest), in, | |
| in_len) != OPENSSL_NPN_NEGOTIATED) | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; /* 一致なし - 接続拒否 */ | |
| return SSL_TLSEXT_ERR_OK; /* ネゴシエーション成功 */ | |
| } | |
| /* | |
| * create_ctx() - SSL_CTXを作成・設定 | |
| * | |
| * QUICサーバー用のSSL_CTXを作成し、証明書、秘密鍵、 | |
| * ALPNコールバックを設定する。 | |
| * | |
| * パラメータ: | |
| * cert_path: サーバー証明書ファイルのパス(PEM形式) | |
| * key_path: 秘密鍵ファイルのパス(PEM形式) | |
| * | |
| * 戻り値: | |
| * 成功時: 設定済みのSSL_CTX | |
| * エラー時: NULL | |
| */ | |
| /* Create SSL_CTX. */ | |
| static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) | |
| { | |
| SSL_CTX *ctx; | |
| /* | |
| * OSSL_QUIC_server_method(): QUICサーバー用のSSL_METHOD | |
| * | |
| * HTTP/3はQUICの上で動作するため、従来のTLS用メソッド | |
| * (TLS_server_method等)ではなく、QUIC専用のメソッドを使用。 | |
| */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_server_method()); | |
| if (ctx == NULL) | |
| goto err; | |
| /* Load certificate and corresponding private key. */ | |
| /* サーバー証明書チェーンをロード */ | |
| if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { | |
| fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); | |
| goto err; | |
| } | |
| /* 秘密鍵をロード */ | |
| if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { | |
| fprintf(stderr, "couldn't load key file: %s\n", key_path); | |
| goto err; | |
| } | |
| /* 証明書と秘密鍵が一致するか確認 */ | |
| if (!SSL_CTX_check_private_key(ctx)) { | |
| fprintf(stderr, "private key check failed\n"); | |
| goto err; | |
| } | |
| /* Setup ALPN negotiation callback. */ | |
| /* | |
| * ALPNコールバックを設定 | |
| * | |
| * TLSハンドシェイク中にクライアントのALPN拡張を処理し、 | |
| * サーバーがサポートするプロトコル(h3, h3-29)を選択する。 | |
| */ | |
| SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); | |
| return ctx; | |
| err: | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| /* | |
| * create_socket() - UDPソケットを作成・バインド | |
| * | |
| * QUICはUDP上で動作するため、TCPソケットではなく | |
| * UDPソケットを作成する。 | |
| * | |
| * パラメータ: | |
| * port: リスンするポート番号 | |
| * | |
| * 戻り値: | |
| * 成功時: ソケットファイルディスクリプタ | |
| * エラー時: -1 | |
| */ | |
| /* Create UDP socket using given port. */ | |
| static int create_socket(uint16_t port) | |
| { | |
| int fd = -1; | |
| struct sockaddr_in sa = {0}; | |
| /* UDPソケットを作成 (SOCK_DGRAM, IPPROTO_UDP) */ | |
| if ((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | |
| fprintf(stderr, "cannot create socket"); | |
| goto err; | |
| } | |
| /* IPv4アドレス、全インターフェースでリスン */ | |
| sa.sin_family = AF_INET; | |
| sa.sin_port = htons(port); /* ポート番号をネットワークバイトオーダーに変換 */ | |
| /* ソケットをアドレスにバインド */ | |
| if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { | |
| fprintf(stderr, "cannot bind to %u\n", port); | |
| goto err; | |
| } | |
| return fd; | |
| err: | |
| if (fd >= 0) | |
| BIO_closesocket(fd); /* OpenSSL提供のクロスプラットフォームclose */ | |
| return -1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * wait_for_activity() - ソケットアクティビティ待機 | |
| * ============================================================================ | |
| * | |
| * 【概要】 | |
| * select()システムコールを使用して、QUICソケットに対する読み書き可能状態を | |
| * 待機する関数。SSL_poll()を使用したイベントループとは別に、単純なブロッキング | |
| * 待機が必要な場合に使用される。 | |
| * | |
| * 【処理フロー】 | |
| * | |
| * 1. SSL_get_fd()でソケットFDを取得 | |
| * ↓ | |
| * 2. SSL_net_write_desired()で書き込み必要性を確認 | |
| * → trueなら write_fd に追加 | |
| * ↓ | |
| * 3. SSL_net_read_desired()で読み取り必要性を確認 | |
| * → trueなら read_fd に追加 | |
| * ※ 常に read_fd にも追加(新規データ受信のため) | |
| * ↓ | |
| * 4. SSL_get_event_timeout()でタイムアウト取得 | |
| * → QUICの内部タイマー(ACK遅延、PTO等)に基づく | |
| * ↓ | |
| * 5. select()で待機 | |
| * → ソケット活動またはタイムアウトで復帰 | |
| * | |
| * 【SSL_net_write_desired() / SSL_net_read_desired()】 | |
| * これらの関数は、OpenSSL QUICスタックが次に何を必要としているかを示す: | |
| * - write_desired: 送信バッファにデータがあり、ソケットへの書き込みが必要 | |
| * - read_desired: ソケットからの読み取りが必要(新規パケット待ち) | |
| * | |
| * 【SSL_get_event_timeout()】 | |
| * QUICの内部タイマーに基づく次回処理時刻を返す: | |
| * - ACK遅延タイマー: ACK送信のタイミング | |
| * - PTO (Probe Timeout): パケットロス検出のためのプローブ | |
| * - アイドルタイムアウト: 接続の自動クローズ | |
| * isinfinite=1の場合、タイムアウトなし(無期限待機可能) | |
| * | |
| * 【select()の代替】 | |
| * この実装ではselect()を使用しているが、以下の代替も可能: | |
| * - poll(): より多くのFDを扱える | |
| * - epoll() (Linux): 高性能なイベント通知 | |
| * - kqueue() (BSD/macOS): epoll相当の機能 | |
| * | |
| * 【GUIアプリケーションへの応用】 | |
| * コメントに記載の通り、GUIの更新が必要な場合は: | |
| * 1. tvpが100msより大きければ100msに制限 | |
| * 2. select()復帰時にタイムアウトか活動かを判定 | |
| * 3. GUI更新タイムアウトならGUIを更新してselect()を再開 | |
| * | |
| * 【この関数の使用箇所】 | |
| * - run_quic_server()内でリスナーの待機 | |
| * - ヘッダ受信待ちのループ | |
| * - データ送信完了待ち | |
| */ | |
| /* Copied from demos/guide/quic-server-non-block.c */ | |
| /** | |
| * @brief Waits for activity on the SSL socket, either for reading or writing. | |
| * | |
| * This function monitors the underlying file descriptor of the given SSL | |
| * connection to determine when it is ready for reading or writing, or both. | |
| * It uses the select function to wait until the socket is either readable | |
| * or writable, depending on what the SSL connection requires. | |
| * | |
| * @param ssl A pointer to the SSL object representing the connection. | |
| * | |
| * @note This function blocks until there is activity on the socket. In a real | |
| * application, you might want to perform other tasks while waiting, such as | |
| * updating a GUI or handling other connections. | |
| * | |
| * @note This function uses select for simplicity and portability. Depending | |
| * on your application's requirements, you might consider using other | |
| * mechanisms like poll or epoll for handling multiple file descriptors. | |
| */ | |
| static int wait_for_activity(SSL *ssl) | |
| { | |
| int sock, isinfinite; | |
| fd_set read_fd, write_fd; | |
| struct timeval tv; | |
| struct timeval *tvp = NULL; | |
| /* Get hold of the underlying file descriptor for the socket */ | |
| if ((sock = SSL_get_fd(ssl)) == -1) { | |
| fprintf(stderr, "Unable to get file descriptor"); | |
| return -1; | |
| } | |
| /* Initialize the fd_set structure */ | |
| FD_ZERO(&read_fd); | |
| FD_ZERO(&write_fd); | |
| /* | |
| * Determine if we would like to write to the socket, read from it, or both. | |
| */ | |
| if (SSL_net_write_desired(ssl)) | |
| FD_SET(sock, &write_fd); | |
| if (SSL_net_read_desired(ssl)) | |
| FD_SET(sock, &read_fd); | |
| /* Add the socket file descriptor to the fd_set */ | |
| FD_SET(sock, &read_fd); | |
| /* | |
| * Find out when OpenSSL would next like to be called, regardless of | |
| * whether the state of the underlying socket has changed or not. | |
| */ | |
| if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite) | |
| tvp = &tv; | |
| /* | |
| * Wait until the socket is writeable or readable. We use select here | |
| * for the sake of simplicity and portability, but you could equally use | |
| * poll/epoll or similar functions | |
| * | |
| * NOTE: For the purposes of this demonstration code this effectively | |
| * makes this demo block until it has something more useful to do. In a | |
| * real application you probably want to go and do other work here (e.g. | |
| * update a GUI, or service other connections). | |
| * | |
| * Let's say for example that you want to update the progress counter on | |
| * a GUI every 100ms. One way to do that would be to use the timeout in | |
| * the last parameter to "select" below. If the tvp value is greater | |
| * than 100ms then use 100ms instead. Then, when select returns, you | |
| * check if it did so because of activity on the file descriptors or | |
| * because of the timeout. If the 100ms GUI timeout has expired but the | |
| * tvp timeout has not then go and update the GUI and then restart the | |
| * "select" (with updated timeouts). | |
| */ | |
| return (select(sock + 1, &read_fd, &write_fd, NULL, tvp)); | |
| } | |
| /* | |
| * ============================================================================ | |
| * run_quic_server() - HTTP/3サーバーメインループ | |
| * ============================================================================ | |
| * | |
| * 【概要】 | |
| * HTTP/3サーバーの中核となるメインループ。QUIC接続の受け入れ、HTTP/3 | |
| * リクエストの処理、レスポンス送信を行う。このデモでは1つの接続を | |
| * 順次処理する(マルチスレッドではない)。 | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * ┌─────────────────────────────────────────────────────────────────┐ | |
| * │ 初期化フェーズ │ | |
| * │ SSL_new_listener() → SSL_set_fd() → SSL_listen() │ | |
| * │ → SSL_set_blocking_mode(0) → nghttp3コールバック設定 │ | |
| * └─────────────────────────────────────────────────────────────────┘ | |
| * ↓ | |
| * ┌─────────────────────────────────────────────────────────────────┐ | |
| * │ メインループ (for(;;)) │ | |
| * │ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 1. 接続待機 (wait_for_activity) │ │ | |
| * │ │ → init_ids()で状態リセット │ │ | |
| * │ │ → add_ids_listener()でリスナー登録 │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 2. ヘッダ受信ループ (restart:) │ │ | |
| * │ │ → read_from_ssl_ids()でイベント処理 │ │ | |
| * │ │ → h3ssl.end_headers_received待ち │ │ | |
| * │ │ → タイムアウト25回でエラー │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 3. HTTP/3ストリーム作成 (初回のみ) │ │ | |
| * │ │ → quic_server_h3streams() │ │ | |
| * │ │ → 制御/QPACKエンコーダ/QPACKデコーダストリーム │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 4. レスポンス構築 │ │ | |
| * │ │ → get_file_length()でファイル取得試行 │ │ | |
| * │ │ → content-type判定(.png, .ico, .htm等) │ │ | |
| * │ │ → nghttp3_conn_submit_response() │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 5. データ送信ループ │ │ | |
| * │ │ → nghttp3_conn_writev_stream()でデータ取得 │ │ | |
| * │ │ → quic_server_write()でQUIC送信 │ │ | |
| * │ │ → nghttp3_conn_add_write_offset()で消費報告 │ │ | |
| * │ │ → finフラグでストリーム終了 │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 6. クローズ待機 (wait_close:) │ │ | |
| * │ │ → クライアントからのクローズ待ち │ │ | |
| * │ │ → 新規接続があればnewconn:へジャンプ │ │ | |
| * │ │ → 同一接続で新リクエストならrestart:へジャンプ │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * │ ↓ │ | |
| * │ ┌──────────────────────────────────────────────────────────┐ │ | |
| * │ │ 7. クリーンアップ │ │ | |
| * │ │ → close_all_ids()で全ストリームクローズ │ │ | |
| * │ │ → SSL_free()で接続解放 │ │ | |
| * │ │ → 次の接続へループ │ │ | |
| * │ └──────────────────────────────────────────────────────────┘ │ | |
| * └─────────────────────────────────────────────────────────────────┘ | |
| * | |
| * 【重要なラベル】 | |
| * newconn: - 新規接続処理開始点(SSL_accept_connection後) | |
| * restart: - 同一接続での新規リクエスト処理開始点 | |
| * wait_close: - レスポンス送信後のクローズ待機 | |
| * | |
| * 【nghttp3との連携】 | |
| * - nghttp3_conn_submit_response(): HTTPレスポンスヘッダをキュー | |
| * - nghttp3_conn_writev_stream(): 送信すべきデータを取得 | |
| * - nghttp3_conn_add_write_offset(): 送信済みバイト数を報告 | |
| * - step_read_data: データ読み取りコールバック(dr.read_data) | |
| * | |
| * 【ファイル提供機能】 | |
| * FILEPREFIX環境変数が設定されている場合、そのディレクトリから | |
| * 静的ファイルを提供する。未設定の場合はデフォルトの20バイト | |
| * ASCII文字列を返す。 | |
| * | |
| * Content-Type判定: | |
| * - .png → image/png | |
| * - .ico → image/vnd.microsoft.icon | |
| * - .htm → text/html | |
| * - その他 → application/octet-stream | |
| * | |
| * @param ctx SSL_CTX(証明書・秘密鍵設定済み) | |
| * @param fd UDPソケットファイルディスクリプタ | |
| * @return 成功時1、エラー時0 | |
| */ | |
| /* Main loop for server to accept QUIC connections. */ | |
| static int run_quic_server(SSL_CTX *ctx, int fd) | |
| { | |
| int ok = 0; | |
| int hassomething = 0; /* 処理すべきイベントがあるかどうか */ | |
| SSL *listener = NULL; /* QUICリスナーオブジェクト */ | |
| nghttp3_conn *h3conn = NULL; /* nghttp3接続オブジェクト */ | |
| struct h3ssl h3ssl; /* HTTP/3接続状態管理 */ | |
| SSL *ssl; | |
| char *fileprefix = getenv("FILEPREFIX"); /* ファイル提供用ディレクトリ */ | |
| /* | |
| * ======================================== | |
| * 初期化フェーズ: QUICリスナーのセットアップ | |
| * ======================================== | |
| */ | |
| /* Create a new QUIC listener. */ | |
| /* QUICリスナーを作成(TCPのlisten()に相当)*/ | |
| if ((listener = SSL_new_listener(ctx, 0)) == NULL) | |
| goto err; | |
| /* Provide the listener with our UDP socket. */ | |
| /* UDPソケットをリスナーに関連付け */ | |
| if (!SSL_set_fd(listener, fd)) | |
| goto err; | |
| /* Begin listening. */ | |
| /* リスニング開始 */ | |
| if (!SSL_listen(listener)) | |
| goto err; | |
| /* | |
| * Listeners, and other QUIC objects, default to operating in blocking mode. | |
| * The configured behaviour is inherited by child objects. | |
| * Make sure we won't block as we use select(). | |
| */ | |
| /* | |
| * 非ブロッキングモードを設定。 | |
| * デフォルトはブロッキングモードだが、select()を使用するため | |
| * 非ブロッキングに設定する。この設定は子オブジェクト(接続、ストリーム) | |
| * にも継承される。 | |
| */ | |
| if (!SSL_set_blocking_mode(listener, 0)) | |
| goto err; | |
| /* | |
| * ======================================== | |
| * nghttp3コールバックの設定 | |
| * ======================================== | |
| * | |
| * HTTP/3リクエスト処理のためのコールバック関数を登録。 | |
| * これらはnghttp3がHTTP/3フレームを処理する際に呼び出される。 | |
| */ | |
| /* Setup callbacks. */ | |
| callbacks.recv_header = on_recv_header; /* ヘッダ受信時 */ | |
| callbacks.end_headers = on_end_headers; /* ヘッダ終了時 */ | |
| callbacks.recv_data = on_recv_data; /* データ受信時 */ | |
| callbacks.end_stream = on_end_stream; /* ストリーム終了時 */ | |
| /* mem default */ | |
| /* nghttp3用メモリアロケータ(標準のmalloc/free使用)*/ | |
| mem = nghttp3_mem_default(); | |
| memset(&h3ssl, 0, sizeof(h3ssl)); | |
| /* | |
| * ======================================== | |
| * メインサーバーループ開始 | |
| * ======================================== | |
| * | |
| * 無限ループで接続を受け入れ、リクエストを処理し、 | |
| * レスポンスを送信する。1つの接続を処理完了してから | |
| * 次の接続を受け入れる逐次処理モデル。 | |
| */ | |
| for (;;) { | |
| nghttp3_nv resp[10]; /* HTTPレスポンスヘッダ配列 */ | |
| size_t num_nv; /* レスポンスヘッダ数 */ | |
| nghttp3_data_reader dr; /* データ読み取りコールバック */ | |
| int ret; | |
| int numtimeout; /* タイムアウトカウンタ */ | |
| char slength[22]; /* Content-Length文字列 */ | |
| int hasnothing; /* クローズ待機用フラグ */ | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ1: 接続待機の初期化 | |
| * ---------------------------------------- | |
| */ | |
| init_ids(&h3ssl); /* h3ssl状態をリセット */ | |
| h3ssl.fileprefix = fileprefix; /* ファイルプレフィックス設定 */ | |
| printf("listener: %p\n", (void *)listener); | |
| add_ids_listener(listener, &h3ssl); /* リスナーをssl_id配列に登録 */ | |
| /* 前回ループで処理すべきものがなければ、新規イベントを待機 */ | |
| if (!hassomething) { | |
| printf("waiting on socket\n"); | |
| fflush(stdout); | |
| ret = wait_for_activity(listener); | |
| if (ret == -1) { | |
| fprintf(stderr, "wait_for_activity failed!\n"); | |
| goto err; | |
| } | |
| } | |
| /* | |
| * Service the connection. In a real application this would be done | |
| * concurrently. In this demonstration program a single connection is | |
| * accepted and serviced at a time. | |
| */ | |
| /* | |
| * 接続処理開始。 | |
| * 実際のアプリケーションでは並行処理するが、このデモでは | |
| * 1つの接続を順次処理する。 | |
| */ | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ2: newconn - 新規接続処理開始点 | |
| * ---------------------------------------- | |
| * read_from_ssl_ids()内でSSL_POLL_EVENT_ICを検出し、 | |
| * SSL_accept_connection()で新規接続を受け入れた後に | |
| * ここへジャンプしてくる。 | |
| */ | |
| newconn: | |
| printf("process_server starting...\n"); | |
| fflush(stdout); | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ3: restart - ヘッダ受信待機 | |
| * ---------------------------------------- | |
| * HTTPリクエストヘッダの受信を待つ。 | |
| * on_end_headers()コールバックがh3ssl.end_headers_received | |
| * をセットするまでループ。 | |
| * | |
| * 同一接続で複数リクエストを処理する場合(HTTP/3の多重化)、 | |
| * 新しいリクエストが来るとここへジャンプしてくる。 | |
| */ | |
| /* wait until we have received the headers */ | |
| restart: | |
| numtimeout = 0; | |
| num_nv = 0; | |
| while (!h3ssl.end_headers_received) { | |
| if (!hassomething) { | |
| if (wait_for_activity(listener) == 0) { | |
| printf("waiting for end_headers_received timeout %d\n", numtimeout); | |
| numtimeout++; | |
| if (numtimeout == 25) | |
| goto err; | |
| } | |
| handle_events_from_ids(&h3ssl); | |
| } | |
| hassomething = read_from_ssl_ids(&h3conn, &h3ssl); | |
| if (hassomething == -1) { | |
| fprintf(stderr, "read_from_ssl_ids hassomething failed\n"); | |
| goto err; | |
| } else if (hassomething == 0) { | |
| printf("read_from_ssl_ids hassomething nothing...\n"); | |
| } else { | |
| numtimeout = 0; | |
| printf("read_from_ssl_ids hassomething %d...\n", hassomething); | |
| if (h3ssl.close_done) { | |
| /* Other side has closed */ | |
| break; | |
| } | |
| h3ssl.restart = 0; | |
| } | |
| } | |
| /* クライアントがリクエストなしで接続を閉じた場合 */ | |
| if (h3ssl.close_done) { | |
| printf("Other side close without request\n"); | |
| goto wait_close; | |
| } | |
| printf("end_headers_received!!!\n"); | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ4: HTTP/3必須ストリームの作成 | |
| * ---------------------------------------- | |
| * 初回のみ、HTTP/3プロトコルに必要な3つの単方向ストリーム | |
| * (制御、QPACKエンコーダ、QPACKデコーダ)を作成する。 | |
| * これがないとレスポンスをクライアントに送信できない。 | |
| */ | |
| if (!h3ssl.has_uni) { | |
| /* time to create those otherwise we can't push anything to the client */ | |
| printf("Create uni\n"); | |
| if (quic_server_h3streams(h3conn, &h3ssl) == -1) { | |
| fprintf(stderr, "quic_server_h3streams failed!\n"); | |
| goto err; | |
| } | |
| h3ssl.has_uni = 1; /* 作成済みフラグ */ | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ5: HTTPレスポンスの構築 | |
| * ---------------------------------------- | |
| * リクエストされたファイルを取得し、HTTPレスポンスヘッダを構築。 | |
| * - ファイルが見つからない場合: デフォルトのテスト文字列を返す | |
| * - 無限ファイル (INT_MAX): テスト用の無限データ | |
| * - 通常ファイル: Content-Typeを拡張子から判定 | |
| */ | |
| /* we have receive the request build the response and send it */ | |
| /* XXX add MAKE_NV("connection", "close"), to resp[] and recheck */ | |
| make_nv(&resp[num_nv++], ":status", "200"); /* HTTPステータス200 OK */ | |
| h3ssl.ldata = get_file_length(&h3ssl); | |
| if (h3ssl.ldata == 0) { | |
| /* We don't find the file: use default test string */ | |
| h3ssl.ptr_data = nulldata; | |
| h3ssl.ldata = nulldata_sz; | |
| snprintf(slength, sizeof(slength), "%zu", h3ssl.ldata); | |
| /* content-type: text/html */ | |
| make_nv(&resp[num_nv++], "content-type", "text/html"); | |
| } else if (h3ssl.ldata == INT_MAX) { | |
| /* endless file for tests */ | |
| snprintf(slength, sizeof(slength), "%zu", h3ssl.ldata); | |
| h3ssl.ptr_data = (uint8_t *) malloc(4096); | |
| memset(h3ssl.ptr_data, 'A', 4096); | |
| } else { | |
| /* normal file we have opened */ | |
| snprintf(slength, sizeof(slength), "%zu", h3ssl.ldata); | |
| h3ssl.ptr_data = (uint8_t *) get_file_data(&h3ssl); | |
| if (h3ssl.ptr_data == NULL) | |
| abort(); | |
| printf("before nghttp3_conn_submit_response on %llu for %s ...\n", | |
| (unsigned long long) h3ssl.id_bidi, h3ssl.url); | |
| if (strstr(h3ssl.url, ".png")) | |
| make_nv(&resp[num_nv++], "content-type", "image/png"); | |
| else if (strstr(h3ssl.url, ".ico")) | |
| make_nv(&resp[num_nv++], "content-type", "image/vnd.microsoft.icon"); | |
| else if (strstr(h3ssl.url, ".htm")) | |
| make_nv(&resp[num_nv++], "content-type", "text/html"); | |
| else | |
| make_nv(&resp[num_nv++], "content-type", "application/octet-stream"); | |
| make_nv(&resp[num_nv++], "content-length", slength); | |
| } | |
| /* | |
| * nghttp3にレスポンスを登録。 | |
| * dr.read_data (step_read_data) はデータ読み取りコールバック。 | |
| * nghttp3がデータを必要とする際に呼び出される。 | |
| */ | |
| dr.read_data = step_read_data; | |
| if (nghttp3_conn_submit_response(h3conn, h3ssl.id_bidi, resp, num_nv, &dr)) { | |
| fprintf(stderr, "nghttp3_conn_submit_response failed!\n"); | |
| goto err; | |
| } | |
| printf("nghttp3_conn_submit_response on %llu...\n", (unsigned long long) h3ssl.id_bidi); | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ6: データ送信ループ | |
| * ---------------------------------------- | |
| * | |
| * nghttp3_conn_writev_stream()で送信すべきデータを取得し、 | |
| * quic_server_write()でQUICストリームに書き込む。 | |
| * | |
| * 【処理の流れ】 | |
| * 1. nghttp3_conn_writev_stream(): 送信データをvec配列に取得 | |
| * - sveccnt: 取得したベクタ数 | |
| * - streamid: 送信先ストリームID | |
| * - fin: ストリーム終了フラグ | |
| * | |
| * 2. quic_server_write(): 各ベクタをQUICストリームに書き込み | |
| * - 最後のベクタでfin=1ならSSL_WRITE_FLAG_CONCLUDE | |
| * | |
| * 3. nghttp3_conn_add_write_offset(): 送信済みバイト数をnghttp3に報告 | |
| * - nghttp3の内部状態を更新 | |
| * | |
| * 4. sveccnt <= 0 かつ datadone でループ終了 | |
| */ | |
| for (;;) { | |
| nghttp3_vec vec[256]; /* 送信データベクタ配列 */ | |
| nghttp3_ssize sveccnt; /* 取得したベクタ数 */ | |
| int fin, i; /* fin: ストリーム終了フラグ */ | |
| int64_t streamid; /* 送信先ストリームID */ | |
| /* nghttp3から送信すべきデータを取得 */ | |
| sveccnt = nghttp3_conn_writev_stream(h3conn, &streamid, &fin, vec, | |
| nghttp3_arraylen(vec)); | |
| /* 送信データがない場合 */ | |
| if (sveccnt <= 0) { | |
| printf("nghttp3_conn_writev_stream done: %ld stream: %llu fin %d\n", | |
| (long int)sveccnt, | |
| (unsigned long long)streamid, | |
| fin); | |
| /* | |
| * fin=1 かつ streamid有効の場合: | |
| * データは全て読み取られたが、finフラグの送信が必要。 | |
| * 0バイトのwrite_offsetでfinを送信。 | |
| */ | |
| if (streamid != -1 && fin) { | |
| printf("Sending end data on %llu fin %d\n", | |
| (unsigned long long) streamid, fin); | |
| nghttp3_conn_add_write_offset(h3conn, streamid, 0); | |
| continue; | |
| } | |
| /* datadone でない場合はエラー、そうでなければ送信完了 */ | |
| if (!h3ssl.datadone) | |
| goto err; | |
| else | |
| break; /* Done - 全データ送信完了 */ | |
| } | |
| printf("nghttp3_conn_writev_stream: %ld fin: %d\n", (long int)sveccnt, fin); | |
| /* 取得した各ベクタをQUICストリームに書き込み */ | |
| for (i = 0; i < sveccnt; i++) { | |
| size_t numbytes = vec[i].len; | |
| int flagwrite = 0; | |
| printf("quic_server_write on %llu for %ld\n", | |
| (unsigned long long)streamid, (unsigned long)vec[i].len); | |
| /* | |
| * 最後のベクタでfin=1の場合、SSL_WRITE_FLAG_CONCLUDE を設定。 | |
| * これにより、データ送信と同時にストリームのFINを送信できる。 | |
| */ | |
| if (fin && i == sveccnt - 1) | |
| flagwrite = SSL_WRITE_FLAG_CONCLUDE; | |
| if (!quic_server_write(&h3ssl, streamid, vec[i].base, | |
| vec[i].len, flagwrite, &numbytes)) { | |
| fprintf(stderr, "quic_server_write failed!\n"); | |
| goto err; | |
| } | |
| } | |
| /* | |
| * 送信済みバイト数をnghttp3に報告。 | |
| * これにより nghttp3 は内部バッファを解放し、 | |
| * 次のデータを準備できる。 | |
| */ | |
| if (nghttp3_conn_add_write_offset( | |
| h3conn, streamid, | |
| (size_t)nghttp3_vec_len(vec, (size_t)sveccnt))) { | |
| fprintf(stderr, "nghttp3_conn_add_write_offset failed!\n"); | |
| goto err; | |
| } | |
| } | |
| printf("nghttp3_conn_submit_response DONE!!!\n"); | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ7: レスポンス送信完了後の処理 | |
| * ---------------------------------------- | |
| */ | |
| if (h3ssl.datadone) { | |
| /* | |
| * All the data was sent. | |
| * close stream zero | |
| */ | |
| /* 全データ送信完了。ストリームをSERVERCLOSED状態に設定 */ | |
| if (!h3ssl.close_done) { | |
| set_id_status(h3ssl.id_bidi, SERVERCLOSED, &h3ssl); | |
| h3ssl.close_wait = 1; /* クローズ待機フラグ */ | |
| } | |
| } else { | |
| printf("nghttp3_conn_submit_response still not finished\n"); | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ8: wait_close - クローズ待機 | |
| * ---------------------------------------- | |
| * | |
| * レスポンス送信後、クライアントからの接続終了を待つ。 | |
| * この間に以下のイベントが発生する可能性がある: | |
| * - h3ssl.done: 接続完全終了 | |
| * - h3ssl.new_conn: 新規接続受け入れ(→ newconn:へ) | |
| * - h3ssl.restart: 同一接続で新規リクエスト(→ restart:へ) | |
| * - are_all_clientid_closed(): クライアントが全ストリームを閉じた | |
| */ | |
| /* wait until closed */ | |
| wait_close: | |
| hasnothing = 0; | |
| for (;;) { | |
| /* イベントがない場合、ソケット活動を待機 */ | |
| if (!hasnothing) { | |
| SSL *newssl = get_ids_connection(&h3ssl); | |
| printf("hasnothing nothing WAIT %d!!!\n", h3ssl.close_done); | |
| /* 接続がなければリスナーで待機 */ | |
| if (newssl == NULL) | |
| newssl = listener; | |
| ret = wait_for_activity(newssl); | |
| if (ret == -1) | |
| goto err; | |
| if (ret == 0) | |
| printf("hasnothing timeout\n"); | |
| /* we have something or a timeout */ | |
| /* 何らかのイベントまたはタイムアウト。内部イベント処理を実行 */ | |
| handle_events_from_ids(&h3ssl); | |
| } | |
| hasnothing = read_from_ssl_ids(&h3conn, &h3ssl); | |
| if (hasnothing == -1) { | |
| printf("hasnothing failed\n"); | |
| break; | |
| /* goto err; well in fact not */ | |
| /* エラーでも致命的ではない場合があるのでbreakのみ */ | |
| } else if (hasnothing == 0) { | |
| printf("hasnothing nothing\n"); | |
| continue; /* イベントなし、再度待機 */ | |
| } else { | |
| printf("hasnothing something\n"); | |
| /* 何らかのイベントを処理 */ | |
| /* 接続完全終了 */ | |
| if (h3ssl.done) { | |
| printf("hasnothing something... DONE\n"); | |
| /* we might already have the next connection to accept */ | |
| /* 次の接続がある可能性があるのでhassomething=1 */ | |
| hassomething = 1; | |
| break; | |
| } | |
| /* 新規接続が来た - newconn:へジャンプ */ | |
| if (h3ssl.new_conn) { | |
| printf("hasnothing something... NEW CONN\n"); | |
| h3ssl.new_conn = 0; | |
| goto newconn; | |
| } | |
| /* 同一接続で新規リクエスト - restart:へジャンプ */ | |
| if (h3ssl.restart) { | |
| printf("hasnothing something... RESTART\n"); | |
| h3ssl.restart = 0; | |
| goto restart; | |
| } | |
| /* クライアントが全ストリームを閉じた */ | |
| if (are_all_clientid_closed(&h3ssl)) { | |
| printf("hasnothing something... DONE other side closed\n"); | |
| /* there might 2 or 3 message we will ignore */ | |
| /* 残りの2-3メッセージは無視される場合がある */ | |
| hassomething = 0; | |
| break; | |
| } | |
| } | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * フェーズ9: クリーンアップと次の接続への準備 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Free the streams, then loop again, accepting another connection. | |
| */ | |
| close_all_ids(&h3ssl); /* 全ストリームをクローズ */ | |
| ssl = get_ids_connection(&h3ssl); | |
| if (ssl != NULL) { | |
| SSL_free(ssl); /* QUIC接続を解放 */ | |
| replace_ids_connection(&h3ssl, ssl, NULL); /* 参照をクリア */ | |
| } | |
| hassomething = 0; /* 次のループで新規接続を待機 */ | |
| } /* メインループ終了 */ | |
| ok = 1; | |
| err: | |
| if (!ok) | |
| ERR_print_errors_fp(stderr); /* エラー詳細を出力 */ | |
| SSL_free(listener); /* リスナーを解放 */ | |
| return ok; /* 成功時1、エラー時0 */ | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - HTTP/3デモサーバーのエントリポイント | |
| * ============================================================================ | |
| * | |
| * 【概要】 | |
| * HTTP/3デモサーバーのメイン関数。コマンドライン引数を解析し、 | |
| * SSL_CTXとUDPソケットを初期化してサーバーループを開始する。 | |
| * | |
| * 【使用方法】 | |
| * ./ossl-nghttp3-demo-server <port> <server.crt> <server.key> | |
| * | |
| * 引数: | |
| * <port> - リッスンするポート番号 (1-65535) | |
| * <server.crt> - サーバー証明書ファイルパス(PEM形式) | |
| * <server.key> - 秘密鍵ファイルパス(PEM形式) | |
| * | |
| * 【環境変数】 | |
| * FILEPREFIX - 設定すると、そのディレクトリから静的ファイルを提供 | |
| * 未設定の場合はデフォルトのテスト文字列を返す | |
| * | |
| * 【処理フロー】 | |
| * 1. コマンドライン引数の検証 | |
| * 2. SSL_CTXの作成(証明書・秘密鍵のロード) | |
| * 3. ポート番号の解析と検証 | |
| * 4. UDPソケットの作成とバインド | |
| * 5. サーバーループの開始 (run_quic_server) | |
| * 6. クリーンアップ | |
| * | |
| * @return 成功時0、エラー時1 | |
| */ | |
| /* | |
| * demo server... just return a 20 bytes ascii string as response for any | |
| * request single h3 connection and single threaded. | |
| */ | |
| int main(int argc, char **argv) | |
| { | |
| int rc = 1; /* 戻り値(エラー時1、成功時0)*/ | |
| SSL_CTX *ctx = NULL; /* SSL/TLSコンテキスト */ | |
| int fd = -1; /* UDPソケットFD */ | |
| unsigned long port; /* リスンポート番号 */ | |
| /* | |
| * ---------------------------------------- | |
| * コマンドライン引数の検証 | |
| * ---------------------------------------- | |
| */ | |
| if (argc < 4) { | |
| fprintf(stderr, "usage: %s <port> <server.crt> <server.key>\n", | |
| argv[0]); | |
| goto err; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * SSL_CTXの作成 | |
| * ---------------------------------------- | |
| * QUIC用のSSL_CTXを作成し、証明書と秘密鍵をロード。 | |
| * ALPNコールバックも設定される。 | |
| */ | |
| /* Create SSL_CTX. */ | |
| if ((ctx = create_ctx(argv[2], argv[3])) == NULL) | |
| goto err; | |
| /* | |
| * ---------------------------------------- | |
| * ポート番号の解析 | |
| * ---------------------------------------- | |
| * 有効な範囲 (1-65535) であることを確認。 | |
| */ | |
| /* Parse port number from command line arguments. */ | |
| port = strtoul(argv[1], NULL, 0); | |
| if (port == 0 || port > UINT16_MAX) { | |
| fprintf(stderr, "invalid port: %lu\n", port); | |
| goto err; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * UDPソケットの作成 | |
| * ---------------------------------------- | |
| * QUICはUDP上で動作するため、UDPソケットを作成。 | |
| */ | |
| /* Create UDP socket. */ | |
| if ((fd = create_socket((uint16_t)port)) < 0) | |
| goto err; | |
| /* | |
| * ---------------------------------------- | |
| * サーバーループの開始 | |
| * ---------------------------------------- | |
| * 接続受け入れ、リクエスト処理、レスポンス送信を | |
| * 無限ループで実行。 | |
| */ | |
| /* Enter QUIC server connection acceptance loop. */ | |
| if (!run_quic_server(ctx, fd)) | |
| goto err; | |
| rc = 0; /* 正常終了 */ | |
| err: | |
| /* | |
| * ---------------------------------------- | |
| * クリーンアップ | |
| * ---------------------------------------- | |
| */ | |
| if (rc != 0) | |
| ERR_print_errors_fp(stderr); /* エラー詳細を出力 */ | |
| SSL_CTX_free(ctx); /* SSL_CTXを解放(NULLでも安全)*/ | |
| if (fd != -1) | |
| BIO_closesocket(fd); /* ソケットをクローズ */ | |
| return rc; /* 成功時0、エラー時1 */ | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/http3/ossl-nghttp3-demo.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * ossl-nghttp3-demo.c - HTTP/3クライアントデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICとnghttp3ライブラリを使用したHTTP/3クライアントのデモ。 | |
| * 指定されたURLに対してGETリクエストを送信し、レスポンスを表示する。 | |
| * | |
| * 【関連ファイル】 | |
| * - ossl-nghttp3.h / ossl-nghttp3.c: HTTP/3接続のヘルパーライブラリ | |
| * (OSSL_DEMO_H3_CONN構造体と関連関数を提供) | |
| * | |
| * 【ossl-nghttp3-demo-server.cとの違い】 | |
| * - このファイル: HTTP/3クライアント(リクエスト送信側) | |
| * - ossl-nghttp3-demo-server.c: HTTP/3サーバー(リクエスト受信側) | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * main() | |
| * │ | |
| * ├── コマンドライン引数解析 (host:port) | |
| * │ | |
| * ├── BIO_parse_hostserv() ... ホスト名とポートを分離 | |
| * │ | |
| * ├── BIO_lookup_ex() ... DNSルックアップ | |
| * │ ※ IPv4/IPv6両方のアドレスを取得 | |
| * │ | |
| * ├── SSL_CTX作成 | |
| * │ ├── OSSL_QUIC_client_method() | |
| * │ ├── SSL_VERIFY_PEER ... サーバー証明書を検証 | |
| * │ └── デフォルト検証パス設定 | |
| * │ | |
| * ├── nghttp3コールバック設定 | |
| * │ ├── recv_header → on_recv_header() | |
| * │ ├── end_headers → on_end_headers() | |
| * │ ├── recv_data → on_recv_data() | |
| * │ └── end_stream → on_end_stream() | |
| * │ | |
| * └── for (各DNSアドレス) { | |
| * │ | |
| * ├── OSSL_DEMO_H3_CONN_new_for_addr() ... HTTP/3接続作成 | |
| * │ | |
| * └── try_conn() | |
| * │ | |
| * ├── HTTPリクエストヘッダ構築 | |
| * │ (:method, :scheme, :authority, :path, user-agent) | |
| * │ | |
| * ├── OSSL_DEMO_H3_CONN_submit_request() ... リクエスト送信 | |
| * │ | |
| * └── while (!done) | |
| * └── OSSL_DEMO_H3_CONN_handle_events() | |
| * │ | |
| * └── nghttp3コールバックが呼ばれる | |
| * ├── on_recv_header() → ヘッダをstderrに出力 | |
| * ├── on_end_headers() → 改行出力 | |
| * ├── on_recv_data() → ボディをstdoutに出力 | |
| * └── on_end_stream() → done=1 | |
| * } | |
| * | |
| * 【使用方法】 | |
| * ./ossl-nghttp3-demo <host:port> | |
| * | |
| * 例: | |
| * ./ossl-nghttp3-demo www.example.com:443 | |
| * | |
| * 【DNSフォールバック】 | |
| * UDPではconnect(2)がハンドシェイクを行わないため、QUICハンドシェイクが | |
| * 失敗して初めてサーバーに到達できないことが判明する。そのため、DNS | |
| * ルックアップで複数のアドレスが返された場合、順番に試行する。 | |
| * | |
| * 【出力】 | |
| * - stderr: HTTPレスポンスヘッダ | |
| * - stdout: HTTPレスポンスボディ | |
| */ | |
| #include "ossl-nghttp3.h" | |
| #include <openssl/err.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| /* | |
| * 【グローバル変数】 | |
| * done: HTTPトランザクション完了フラグ | |
| * on_end_stream()コールバックで1に設定され、メインループを終了させる。 | |
| */ | |
| static int done; | |
| /* | |
| * make_nv() - nghttp3_nv構造体の初期化ヘルパー | |
| * | |
| * HTTP/3ヘッダ(名前-値ペア)を表すnghttp3_nv構造体を初期化する。 | |
| * | |
| * 【nghttp3_nv構造体】 | |
| * HTTP/2やHTTP/3では、ヘッダは名前-値のペアとして表現される。 | |
| * nghttp3_nvは以下のフィールドを持つ: | |
| * - name/value: ヘッダ名/値へのポインタ | |
| * - namelen/valuelen: それぞれの長さ | |
| * - flags: ヘッダ処理のフラグ | |
| * | |
| * 【HTTP/3の擬似ヘッダ】 | |
| * HTTP/3では以下の擬似ヘッダ(コロンで始まる)が使用される: | |
| * - :method ... HTTPメソッド (GET, POST等) | |
| * - :scheme ... スキーム (https) | |
| * - :authority ... ホスト名 (Host相当) | |
| * - :path ... リクエストパス | |
| * | |
| * @param nv 初期化するnghttp3_nv構造体へのポインタ | |
| * @param name ヘッダ名(静的文字列) | |
| * @param value ヘッダ値(静的文字列) | |
| */ | |
| static void make_nv(nghttp3_nv *nv, const char *name, const char *value) | |
| { | |
| nv->name = (uint8_t *)name; | |
| nv->value = (uint8_t *)value; | |
| nv->namelen = strlen(name); | |
| nv->valuelen = strlen(value); | |
| nv->flags = NGHTTP3_NV_FLAG_NONE; /* 特別なフラグなし */ | |
| } | |
| /* | |
| * ============================================================================ | |
| * nghttp3コールバック関数群 | |
| * ============================================================================ | |
| * | |
| * nghttp3ライブラリがHTTP/3フレームを処理する際に呼び出される | |
| * コールバック関数。サーバーからのレスポンスを受信した際に呼ばれる。 | |
| * | |
| * 【呼び出し順序】 | |
| * 1. on_recv_header() ... 各ヘッダごとに呼ばれる(複数回) | |
| * 2. on_end_headers() ... 全ヘッダ受信完了時 | |
| * 3. on_recv_data() ... ボディデータ受信時(複数回の可能性) | |
| * 4. on_end_stream() ... ストリーム終了時 | |
| */ | |
| /* | |
| * on_recv_header() - HTTPレスポンスヘッダ受信コールバック | |
| * | |
| * サーバーから1つのHTTPヘッダを受信するたびに呼び出される。 | |
| * ヘッダ名と値をstderrに出力する。 | |
| * | |
| * 【nghttp3_rcbuf】 | |
| * 参照カウント付きバッファ。nghttp3_rcbuf_get_buf()で | |
| * 実際のデータ(nghttp3_vec: base + len)を取得できる。 | |
| * | |
| * @param h3conn nghttp3接続オブジェクト | |
| * @param stream_id ストリームID | |
| * @param token QPACKトークン(ヘッダの種類を識別) | |
| * @param name ヘッダ名(参照カウント付きバッファ) | |
| * @param value ヘッダ値(参照カウント付きバッファ) | |
| * @param flags フラグ | |
| * @param conn_user_data 接続のユーザーデータ | |
| * @param stream_user_data ストリームのユーザーデータ | |
| * @return 0: 成功, 非0: エラー | |
| */ | |
| static int on_recv_header(nghttp3_conn *h3conn, int64_t stream_id, | |
| int32_t token, | |
| nghttp3_rcbuf *name, nghttp3_rcbuf *value, | |
| uint8_t flags, | |
| void *conn_user_data, | |
| void *stream_user_data) | |
| { | |
| nghttp3_vec vname, vvalue; | |
| /* Received a single HTTP header. */ | |
| /* 参照カウント付きバッファから実際のデータを取得 */ | |
| vname = nghttp3_rcbuf_get_buf(name); | |
| vvalue = nghttp3_rcbuf_get_buf(value); | |
| /* ヘッダをstderrに出力: "name: value\n" */ | |
| fwrite(vname.base, vname.len, 1, stderr); | |
| fprintf(stderr, ": "); | |
| fwrite(vvalue.base, vvalue.len, 1, stderr); | |
| fprintf(stderr, "\n"); | |
| return 0; | |
| } | |
| /* | |
| * on_end_headers() - ヘッダ終了コールバック | |
| * | |
| * 全てのHTTPレスポンスヘッダを受信した後に呼び出される。 | |
| * ヘッダとボディの区切りとして空行を出力。 | |
| * | |
| * @param fin 1の場合、このストリームは終了(ボディなし) | |
| */ | |
| static int on_end_headers(nghttp3_conn *h3conn, int64_t stream_id, | |
| int fin, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| fprintf(stderr, "\n"); /* ヘッダとボディの間に空行 */ | |
| return 0; | |
| } | |
| /* | |
| * on_recv_data() - HTTPレスポンスボディ受信コールバック | |
| * | |
| * サーバーからレスポンスボディのデータを受信するたびに呼び出される。 | |
| * 受信したデータをそのままstdoutに出力する。 | |
| * | |
| * 【注意】 | |
| * 大きなレスポンスの場合、このコールバックは複数回呼び出される。 | |
| * 各呼び出しでdatalenバイトのデータが渡される。 | |
| * | |
| * @param data 受信したデータへのポインタ | |
| * @param datalen 受信したデータのバイト数 | |
| * @return 0: 成功, 非0: エラー(接続中断) | |
| */ | |
| static int on_recv_data(nghttp3_conn *h3conn, int64_t stream_id, | |
| const uint8_t *data, size_t datalen, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| size_t wr; | |
| /* HTTP response body data - write it to stdout. */ | |
| /* レスポンスボディをstdoutに出力 */ | |
| while (datalen > 0) { | |
| wr = fwrite(data, 1, datalen, stdout); | |
| if (ferror(stdout)) | |
| return 1; /* 書き込みエラー */ | |
| data += wr; | |
| datalen -= wr; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * on_end_stream() - ストリーム終了コールバック | |
| * | |
| * HTTPトランザクションが完了した際に呼び出される。 | |
| * サーバーがレスポンスの送信を完了し、ストリームを閉じたことを示す。 | |
| * | |
| * doneフラグを1に設定してメインループを終了させる。 | |
| */ | |
| static int on_end_stream(nghttp3_conn *h3conn, int64_t stream_id, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| /* HTTP transaction is done - set done flag so that we stop looping. */ | |
| /* HTTPトランザクション完了。doneフラグでメインループを終了させる */ | |
| done = 1; | |
| return 0; | |
| } | |
| /* | |
| * try_conn() - HTTP/3リクエストの送信と完了待機 | |
| * | |
| * 確立されたHTTP/3接続を使用して、ルートパス ("/") に対する | |
| * GETリクエストを送信し、レスポンスが完了するまで待機する。 | |
| * | |
| * 【処理フロー】 | |
| * 1. HTTPリクエストヘッダを構築 | |
| * - :method = "GET" | |
| * - :scheme = "https" | |
| * - :authority = ホスト名 | |
| * - :path = "/" | |
| * - user-agent = "OpenSSL-Demo/nghttp3" | |
| * | |
| * 2. OSSL_DEMO_H3_CONN_submit_request()でリクエスト送信 | |
| * | |
| * 3. レスポンス完了待機ループ | |
| * - OSSL_DEMO_H3_CONN_handle_events()でイベント処理 | |
| * - on_end_stream()がdone=1を設定するまでループ | |
| * | |
| * 【OSSL_DEMO_H3_CONN_submit_request()】 | |
| * ossl-nghttp3.cで定義されているヘルパー関数。 | |
| * nghttp3_conn_submit_request()をラップし、QUICストリームの | |
| * 作成とリクエスト送信を行う。 | |
| * | |
| * 【OSSL_DEMO_H3_CONN_handle_events()】 | |
| * QUICとnghttp3のイベントを処理するヘルパー関数。 | |
| * ソケットの読み書き、SSL_handle_events()の呼び出し、 | |
| * nghttp3コールバックの実行を行う。 | |
| * | |
| * @param conn HTTP/3接続オブジェクト | |
| * @param bare_hostname ホスト名(:authority用) | |
| * @return 1: 成功, 0: エラー | |
| */ | |
| static int try_conn(OSSL_DEMO_H3_CONN *conn, const char *bare_hostname) | |
| { | |
| nghttp3_nv nva[16]; /* HTTPヘッダ配列 */ | |
| size_t num_nv = 0; /* ヘッダ数 */ | |
| /* | |
| * Build HTTP headers. | |
| * HTTP/3リクエストヘッダを構築。 | |
| * 擬似ヘッダ(コロンで始まる)は最初に配置する必要がある。 | |
| */ | |
| make_nv(&nva[num_nv++], ":method", "GET"); /* HTTPメソッド */ | |
| make_nv(&nva[num_nv++], ":scheme", "https"); /* スキーム(QUICは常にhttps)*/ | |
| make_nv(&nva[num_nv++], ":authority", bare_hostname); /* ホスト名 */ | |
| make_nv(&nva[num_nv++], ":path", "/"); /* リクエストパス */ | |
| make_nv(&nva[num_nv++], "user-agent", "OpenSSL-Demo/nghttp3"); /* UA */ | |
| /* Submit request. */ | |
| /* HTTP/3リクエストを送信 */ | |
| if (!OSSL_DEMO_H3_CONN_submit_request(conn, nva, num_nv, NULL, NULL)) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, | |
| "cannot submit HTTP/3 request"); | |
| return 0; | |
| } | |
| /* | |
| * Wait for request to complete. | |
| * レスポンス完了待機ループ。 | |
| * on_end_stream()がdone=1を設定するまで、 | |
| * イベント処理を繰り返す。 | |
| */ | |
| done = 0; | |
| while (!done) | |
| if (!OSSL_DEMO_H3_CONN_handle_events(conn)) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_OPERATION_FAIL, | |
| "cannot handle events"); | |
| return 0; | |
| } | |
| return 1; /* 成功 */ | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - HTTP/3クライアントのエントリポイント | |
| * ============================================================================ | |
| * | |
| * 【使用方法】 | |
| * ./ossl-nghttp3-demo <host:port> | |
| * | |
| * 例: | |
| * ./ossl-nghttp3-demo www.example.com:443 | |
| * ./ossl-nghttp3-demo localhost:4433 | |
| * | |
| * 【処理フロー】 | |
| * 1. コマンドライン引数の解析 | |
| * 2. ホスト名とポートの分離 (BIO_parse_hostserv) | |
| * 3. DNSルックアップ (BIO_lookup_ex) | |
| * 4. SSL_CTX作成と証明書検証設定 | |
| * 5. nghttp3コールバック設定 | |
| * 6. 各DNSアドレスに対して接続試行 | |
| * 7. クリーンアップ | |
| * | |
| * @return 0: 成功, 1: エラー | |
| */ | |
| int main(int argc, char **argv) | |
| { | |
| int ret = 1; /* 戻り値(デフォルトはエラー)*/ | |
| int ok; | |
| SSL_CTX *ctx = NULL; /* SSL/TLSコンテキスト */ | |
| OSSL_DEMO_H3_CONN *conn = NULL; /* HTTP/3接続オブジェクト */ | |
| nghttp3_callbacks callbacks = {0}; /* nghttp3コールバック構造体 */ | |
| const char *addr; /* コマンドライン引数 (host:port) */ | |
| char *hostname, *service; /* 分離されたホスト名とポート */ | |
| BIO_ADDRINFO *bai = NULL; /* DNSルックアップ結果 */ | |
| const BIO_ADDRINFO *bai_walk; /* 結果リストのイテレータ */ | |
| /* | |
| * ---------------------------------------- | |
| * コマンドライン引数の検証 | |
| * ---------------------------------------- | |
| */ | |
| /* Check arguments. */ | |
| if (argc < 2) { | |
| fprintf(stderr, "usage: %s <host:port>\n", argv[0]); | |
| goto err; | |
| } | |
| addr = argv[1]; | |
| /* | |
| * ---------------------------------------- | |
| * ホスト名とポートの分離 | |
| * ---------------------------------------- | |
| * BIO_parse_hostserv()は "host:port" 形式の文字列を | |
| * ホスト名とサービス(ポート)に分離する。 | |
| */ | |
| hostname = NULL; | |
| service = NULL; | |
| if (BIO_parse_hostserv(addr, &hostname, &service, 0) != 1) { | |
| fprintf(stderr, "usage: %s <host:port>\n", argv[0]); | |
| goto err; | |
| } | |
| if (hostname == NULL || service == NULL) { | |
| fprintf(stderr, "usage: %s <host:port>\n", argv[0]); | |
| goto err; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * DNSルックアップ | |
| * ---------------------------------------- | |
| * BIO_lookup_ex()でホスト名をIPアドレスに解決。 | |
| * デュアルスタック(IPv4/IPv6両対応)環境では | |
| * 複数のアドレスが返される可能性がある。 | |
| * | |
| * BIO_LOOKUP_CLIENT: クライアント用のアドレスを取得 | |
| * SOCK_DGRAM: UDPソケット用 | |
| * IPPROTO_UDP: UDPプロトコル | |
| */ | |
| /* | |
| * Remember DNS may return more IP addresses (and it typically does these | |
| * dual-stack days). | |
| */ | |
| ok = BIO_lookup_ex(hostname, service, BIO_LOOKUP_CLIENT, | |
| 0, SOCK_DGRAM, IPPROTO_UDP, &bai); | |
| if (ok == 0) { | |
| fprintf(stderr, "host %s not found\n", hostname); | |
| goto err; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * SSL_CTXの作成 | |
| * ---------------------------------------- | |
| * QUICクライアント用のSSL_CTXを作成し、 | |
| * サーバー証明書の検証を有効化。 | |
| */ | |
| /* Setup SSL_CTX. */ | |
| if ((ctx = SSL_CTX_new(OSSL_QUIC_client_method())) == NULL) | |
| goto err; | |
| /* サーバー証明書の検証を有効化(HTTPS必須)*/ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| /* システムデフォルトのCA証明書パスを使用 */ | |
| if (SSL_CTX_set_default_verify_paths(ctx) == 0) | |
| goto err; | |
| /* | |
| * ---------------------------------------- | |
| * nghttp3コールバックの設定 | |
| * ---------------------------------------- | |
| * HTTP/3レスポンスを処理するためのコールバック関数を登録。 | |
| */ | |
| /* Setup callbacks. */ | |
| callbacks.recv_header = on_recv_header; /* ヘッダ受信 */ | |
| callbacks.end_headers = on_end_headers; /* ヘッダ終了 */ | |
| callbacks.recv_data = on_recv_data; /* データ受信 */ | |
| callbacks.end_stream = on_end_stream; /* ストリーム終了 */ | |
| /* | |
| * ---------------------------------------- | |
| * DNSアドレスリストへの接続試行 | |
| * ---------------------------------------- | |
| * | |
| * 【なぜ複数アドレスを試行するのか】 | |
| * TCPと異なり、UDPではconnect(2)がハンドシェイクを行わない。 | |
| * UDPのconnect(2)は単にローカルソケットにリモートアドレスを | |
| * 関連付けるだけで、相手に到達可能かどうかは確認しない。 | |
| * | |
| * QUICでは、TLSハンドシェイクが実行されて初めて、 | |
| * サーバーに到達できないことが判明する。そのため、 | |
| * DNSが複数アドレスを返した場合は、順番に試行する必要がある。 | |
| * | |
| * 典型的なケース: | |
| * - IPv6アドレスが返されたが、IPv6接続ができない | |
| * - 複数のIPv4アドレスのうち、一部がダウンしている | |
| */ | |
| /* | |
| * Unlike TCP there is no handshake on UDP protocol. | |
| * The BIO subsystem uses connect(2) to establish | |
| * connection. However connect(2) for UDP does not | |
| * perform handshake, so BIO just assumes the remote | |
| * service is reachable on the first address returned | |
| * by DNS. It's the SSL-handshake itself when QUIC stack | |
| * realizes the service is not reachable, so the application | |
| * needs to initiate a QUIC connection on the next address | |
| * returned by DNS. | |
| */ | |
| for (bai_walk = bai; bai_walk != NULL; | |
| bai_walk = BIO_ADDRINFO_next(bai_walk)) { | |
| /* 各アドレスに対してHTTP/3接続を試行 */ | |
| conn = OSSL_DEMO_H3_CONN_new_for_addr(ctx, bai_walk, hostname, | |
| &callbacks, NULL, NULL); | |
| if (conn != NULL) { | |
| if (try_conn(conn, addr) == 0) { | |
| /* | |
| * Failure, try the next address. | |
| */ | |
| /* 失敗。次のアドレスを試行 */ | |
| OSSL_DEMO_H3_CONN_free(conn); | |
| conn = NULL; | |
| ret = 1; | |
| } else { | |
| /* | |
| * Success, done. | |
| */ | |
| /* 成功。ループを終了 */ | |
| ret = 0; | |
| break; | |
| } | |
| } | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * クリーンアップ | |
| * ---------------------------------------- | |
| */ | |
| err: | |
| if (ret != 0) | |
| ERR_print_errors_fp(stderr); /* エラー詳細を出力 */ | |
| OSSL_DEMO_H3_CONN_free(conn); /* HTTP/3接続を解放 */ | |
| SSL_CTX_free(ctx); /* SSL_CTXを解放 */ | |
| BIO_ADDRINFO_free(bai); /* DNSルックアップ結果を解放 */ | |
| return ret; /* 成功時0、エラー時1 */ | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/http3/ossl-nghttp3.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * OpenSSL QUIC と nghttp3 のアダプテーションレイヤー | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * このファイルは、OpenSSL の QUIC 実装と nghttp3 HTTP/3 ライブラリを | |
| * 統合するためのアダプテーションレイヤーを提供します。 | |
| * | |
| * 【HTTP/3 の構造】 | |
| * | |
| * アプリケーション (ossl-nghttp3-demo.c) | |
| * ↓ | |
| * HTTP/3 レイヤー (nghttp3) | |
| * ↓ | |
| * アダプテーションレイヤー (このファイル) | |
| * ↓ | |
| * QUIC レイヤー (OpenSSL QUIC) | |
| * ↓ | |
| * UDP ソケット | |
| * | |
| * 【HTTP/3 に必要なストリーム】 | |
| * HTTP/3 では以下の単方向ストリームが必要: | |
| * | |
| * クライアント → サーバー: | |
| * - Control Stream: HTTP/3 制御情報 | |
| * - QPACK Encoder Stream: ヘッダー圧縮エンコーダ | |
| * - QPACK Decoder Stream: ヘッダー圧縮デコーダ | |
| * | |
| * サーバー → クライアント: | |
| * - 同様の3つのストリーム(サーバーが作成) | |
| * | |
| * リクエスト/レスポンス: | |
| * - 双方向ストリーム(リクエストごとに1つ) | |
| * | |
| * 【主要なデータ構造】 | |
| * - OSSL_DEMO_H3_CONN: HTTP/3 接続を表す(QUIC 接続 + nghttp3 状態) | |
| * - OSSL_DEMO_H3_STREAM: 個々のストリームを表す | |
| * | |
| * 【データフロー】 | |
| * 送信: nghttp3 → writev_stream → SSL_write_ex2 → ネットワーク | |
| * 受信: ネットワーク → SSL_read_ex → nghttp3_conn_read_stream → nghttp3 | |
| * ============================================================================ | |
| */ | |
| #include "ossl-nghttp3.h" | |
| #include <openssl/err.h> | |
| #include <assert.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #define ARRAY_LEN(x) (sizeof(x)/sizeof((x)[0])) | |
| /* | |
| * ストリームタイプの定義 | |
| * HTTP/3 では複数種類のストリームを使用する | |
| */ | |
| enum { | |
| OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND, /* 制御ストリーム(単方向、送信用) */ | |
| OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND, /* QPACK エンコーダストリーム */ | |
| OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND, /* QPACK デコーダストリーム */ | |
| OSSL_DEMO_H3_STREAM_TYPE_REQ, /* リクエストストリーム(双方向) */ | |
| }; | |
| #define BUF_SIZE 4096 | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_STREAM - 個々の QUIC ストリームを表す構造体 | |
| * ============================================================================ | |
| * | |
| * HTTP/3 では各ストリームが独立したデータチャネルとして機能する。 | |
| * この構造体は QUIC ストリームと HTTP/3 ストリームの対応を管理する。 | |
| */ | |
| struct ossl_demo_h3_stream_st { | |
| uint64_t id; /* QUIC stream ID */ | |
| SSL *s; /* QUIC stream SSL object (QSSO) */ | |
| int done_recv_fin; /* Received FIN: ストリーム終了フラグ */ | |
| void *user_data; /* アプリケーション用データ */ | |
| /* 受信バッファ: QUIC から読み取ったデータを一時保存 */ | |
| uint8_t buf[BUF_SIZE]; | |
| size_t buf_cur, buf_total; /* バッファの現在位置と合計サイズ */ | |
| }; | |
| /* | |
| * ストリーム ID をキーとするハッシュテーブルの定義 | |
| * OpenSSL の LHASH マクロを使用 | |
| */ | |
| DEFINE_LHASH_OF_EX(OSSL_DEMO_H3_STREAM); | |
| /* ストリームオブジェクトを解放 */ | |
| static void h3_stream_free(OSSL_DEMO_H3_STREAM *s) | |
| { | |
| if (s == NULL) | |
| return; | |
| SSL_free(s->s); | |
| OPENSSL_free(s); | |
| } | |
| /* ハッシュテーブル用: ストリーム ID からハッシュ値を計算 */ | |
| static unsigned long h3_stream_hash(const OSSL_DEMO_H3_STREAM *s) | |
| { | |
| return (unsigned long)s->id; | |
| } | |
| /* ハッシュテーブル用: ストリーム ID を比較 */ | |
| static int h3_stream_eq(const OSSL_DEMO_H3_STREAM *a, const OSSL_DEMO_H3_STREAM *b) | |
| { | |
| if (a->id < b->id) return -1; | |
| if (a->id > b->id) return 1; | |
| return 0; | |
| } | |
| /* ストリームに関連付けられたユーザーデータを取得 */ | |
| void *OSSL_DEMO_H3_STREAM_get_user_data(const OSSL_DEMO_H3_STREAM *s) | |
| { | |
| return s->user_data; | |
| } | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_CONN - HTTP/3 接続を表す構造体 | |
| * ============================================================================ | |
| * | |
| * QUIC 接続と nghttp3 の状態を統合し、HTTP/3 通信を管理する。 | |
| * ストリームのマップを保持し、イベント処理を行う。 | |
| */ | |
| struct ossl_demo_h3_conn_st { | |
| /* QUIC connection SSL object (QCSO) */ | |
| SSL *qconn; | |
| /* BIO wrapping QCSO */ | |
| BIO *qconn_bio; | |
| /* HTTP/3 connection object (nghttp3 の状態) */ | |
| nghttp3_conn *h3conn; | |
| /* map of stream IDs to OSSL_DEMO_H3_STREAMs */ | |
| /* ストリーム ID → OSSL_DEMO_H3_STREAM のハッシュマップ */ | |
| LHASH_OF(OSSL_DEMO_H3_STREAM) *streams; | |
| /* opaque user data pointer */ | |
| void *user_data; | |
| int pump_res; /* pump 処理の結果 */ | |
| size_t consumed_app_data; /* 消費したアプリデータ量 */ | |
| /* | |
| * Forwarding callbacks | |
| * nghttp3 のコールバックをラップして内部処理後にアプリに転送 | |
| */ | |
| nghttp3_recv_data recv_data_cb; /* データ受信 */ | |
| nghttp3_stream_close stream_close_cb; /* ストリームクローズ */ | |
| nghttp3_stop_sending stop_sending_cb; /* 送信停止要求 */ | |
| nghttp3_reset_stream reset_stream_cb; /* ストリームリセット */ | |
| nghttp3_deferred_consume deferred_consume_cb; /* 遅延消費 */ | |
| }; | |
| /* | |
| * HTTP/3 接続オブジェクトを解放 | |
| * 全ストリーム、nghttp3 状態、BIO を解放する | |
| */ | |
| void OSSL_DEMO_H3_CONN_free(OSSL_DEMO_H3_CONN *conn) | |
| { | |
| if (conn == NULL) | |
| return; | |
| /* 全ストリームを解放 */ | |
| lh_OSSL_DEMO_H3_STREAM_doall(conn->streams, h3_stream_free); | |
| nghttp3_conn_del(conn->h3conn); | |
| BIO_free_all(conn->qconn_bio); | |
| lh_OSSL_DEMO_H3_STREAM_free(conn->streams); | |
| OPENSSL_free(conn); | |
| } | |
| /* | |
| * 新しい QUIC ストリームを作成 | |
| * | |
| * type: | |
| * OSSL_DEMO_H3_STREAM_TYPE_REQ: 双方向ストリーム(リクエスト用) | |
| * その他: 単方向ストリーム(制御、QPACK 用) | |
| */ | |
| static OSSL_DEMO_H3_STREAM *h3_conn_create_stream(OSSL_DEMO_H3_CONN *conn, int type) | |
| { | |
| OSSL_DEMO_H3_STREAM *s; | |
| uint64_t flags = SSL_STREAM_FLAG_ADVANCE; | |
| if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL) | |
| return NULL; | |
| /* リクエスト以外は単方向ストリーム */ | |
| if (type != OSSL_DEMO_H3_STREAM_TYPE_REQ) | |
| flags |= SSL_STREAM_FLAG_UNI; | |
| /* QUIC ストリームを作成 */ | |
| if ((s->s = SSL_new_stream(conn->qconn, flags)) == NULL) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "could not create QUIC stream object"); | |
| goto err; | |
| } | |
| /* ストリーム ID を取得してハッシュマップに登録 */ | |
| s->id = SSL_get_stream_id(s->s); | |
| lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s); | |
| return s; | |
| err: | |
| OPENSSL_free(s); | |
| return NULL; | |
| } | |
| /* | |
| * サーバーから受け入れたストリームをラップ | |
| * サーバーが開始した単方向ストリーム(制御、QPACK)を管理に追加 | |
| */ | |
| static OSSL_DEMO_H3_STREAM *h3_conn_accept_stream(OSSL_DEMO_H3_CONN *conn, SSL *qstream) | |
| { | |
| OSSL_DEMO_H3_STREAM *s; | |
| if ((s = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_STREAM))) == NULL) | |
| return NULL; | |
| s->id = SSL_get_stream_id(qstream); | |
| s->s = qstream; | |
| lh_OSSL_DEMO_H3_STREAM_insert(conn->streams, s); | |
| return s; | |
| } | |
| /* ストリームを管理から削除して解放 */ | |
| static void h3_conn_remove_stream(OSSL_DEMO_H3_CONN *conn, OSSL_DEMO_H3_STREAM *s) | |
| { | |
| if (s == NULL) | |
| return; | |
| lh_OSSL_DEMO_H3_STREAM_delete(conn->streams, s); | |
| h3_stream_free(s); | |
| } | |
| /* | |
| * ============================================================================ | |
| * nghttp3 コールバック関数群 | |
| * ============================================================================ | |
| * | |
| * nghttp3 からのイベントを受け取り、内部処理を行った後、 | |
| * アプリケーションのコールバックに転送する。 | |
| */ | |
| /* | |
| * データ受信コールバック | |
| * HTTP/3 のボディデータを受信した時に呼ばれる | |
| */ | |
| static int h3_conn_recv_data(nghttp3_conn *h3conn, int64_t stream_id, | |
| const uint8_t *data, size_t datalen, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| OSSL_DEMO_H3_CONN *conn = conn_user_data; | |
| /* 消費したアプリデータ量を記録 */ | |
| conn->consumed_app_data += datalen; | |
| if (conn->recv_data_cb == NULL) | |
| return 0; | |
| /* アプリケーションのコールバックに転送 */ | |
| return conn->recv_data_cb(h3conn, stream_id, data, datalen, | |
| conn_user_data, stream_user_data); | |
| } | |
| /* | |
| * ストリームクローズコールバック | |
| * HTTP/3 ストリームが閉じられた時に呼ばれる | |
| */ | |
| static int h3_conn_stream_close(nghttp3_conn *h3conn, int64_t stream_id, | |
| uint64_t app_error_code, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| int ret = 0; | |
| OSSL_DEMO_H3_CONN *conn = conn_user_data; | |
| OSSL_DEMO_H3_STREAM *stream = stream_user_data; | |
| if (conn->stream_close_cb != NULL) | |
| ret = conn->stream_close_cb(h3conn, stream_id, app_error_code, | |
| conn_user_data, stream_user_data); | |
| /* 内部管理からストリームを削除 */ | |
| h3_conn_remove_stream(conn, stream); | |
| return ret; | |
| } | |
| /* | |
| * 送信停止コールバック | |
| * ピアが STOP_SENDING フレームを送信した時に呼ばれる | |
| */ | |
| static int h3_conn_stop_sending(nghttp3_conn *h3conn, int64_t stream_id, | |
| uint64_t app_error_code, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| int ret = 0; | |
| OSSL_DEMO_H3_CONN *conn = conn_user_data; | |
| OSSL_DEMO_H3_STREAM *stream = stream_user_data; | |
| if (conn->stop_sending_cb != NULL) | |
| ret = conn->stop_sending_cb(h3conn, stream_id, app_error_code, | |
| conn_user_data, stream_user_data); | |
| /* ストリームを解放(以降このストリームは使用しない) */ | |
| SSL_free(stream->s); | |
| stream->s = NULL; | |
| return ret; | |
| } | |
| /* | |
| * ストリームリセットコールバック | |
| * nghttp3 がストリームのリセットを要求した時に呼ばれる | |
| */ | |
| static int h3_conn_reset_stream(nghttp3_conn *h3conn, int64_t stream_id, | |
| uint64_t app_error_code, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| int ret = 0; | |
| OSSL_DEMO_H3_CONN *conn = conn_user_data; | |
| OSSL_DEMO_H3_STREAM *stream = stream_user_data; | |
| SSL_STREAM_RESET_ARGS args = {0}; | |
| if (conn->reset_stream_cb != NULL) | |
| ret = conn->reset_stream_cb(h3conn, stream_id, app_error_code, | |
| conn_user_data, stream_user_data); | |
| if (stream->s != NULL) { | |
| args.quic_error_code = app_error_code; | |
| /* QUIC ストリームをリセット */ | |
| if (!SSL_stream_reset(stream->s, &args, sizeof(args))) | |
| return 1; | |
| } | |
| return ret; | |
| } | |
| /* | |
| * 遅延消費コールバック | |
| * nghttp3 がデータの消費を遅延して報告する時に呼ばれる | |
| */ | |
| static int h3_conn_deferred_consume(nghttp3_conn *h3conn, int64_t stream_id, | |
| size_t consumed, | |
| void *conn_user_data, void *stream_user_data) | |
| { | |
| int ret = 0; | |
| OSSL_DEMO_H3_CONN *conn = conn_user_data; | |
| if (conn->deferred_consume_cb != NULL) | |
| ret = conn->deferred_consume_cb(h3conn, stream_id, consumed, | |
| conn_user_data, stream_user_data); | |
| conn->consumed_app_data += consumed; | |
| return ret; | |
| } | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_CONN_new_for_conn - 既存の QUIC 接続から HTTP/3 接続を作成 | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * 既に確立された QUIC 接続 (SSL オブジェクト) を使用して、 | |
| * HTTP/3 接続オブジェクトを作成する。 | |
| * | |
| * 【処理内容】 | |
| * 1. ALPN を "h3" に設定 | |
| * 2. デフォルトストリームモードを無効化 | |
| * 3. 制御ストリーム、QPACK ストリームを作成 | |
| * 4. nghttp3 のクライアント状態を初期化 | |
| * 5. ストリーム ID を nghttp3 にバインド | |
| * | |
| * 【引数】 | |
| * - qconn: QUIC 接続 SSL オブジェクト | |
| * - callbacks: nghttp3 コールバック(NULL 可) | |
| * - settings: nghttp3 設定(NULL でデフォルト) | |
| * - user_data: アプリケーション用データ | |
| * | |
| * 【戻り値】 | |
| * - 成功: HTTP/3 接続オブジェクト | |
| * - 失敗: NULL | |
| */ | |
| OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_conn(SSL *qconn, | |
| const nghttp3_callbacks *callbacks, | |
| const nghttp3_settings *settings, | |
| void *user_data) | |
| { | |
| int ec; | |
| OSSL_DEMO_H3_CONN *conn; | |
| OSSL_DEMO_H3_STREAM *s_ctl_send = NULL; /* 制御ストリーム */ | |
| OSSL_DEMO_H3_STREAM *s_qpenc_send = NULL; /* QPACK エンコーダ */ | |
| OSSL_DEMO_H3_STREAM *s_qpdec_send = NULL; /* QPACK デコーダ */ | |
| nghttp3_settings dsettings = {0}; | |
| nghttp3_callbacks intl_callbacks = {0}; | |
| /* HTTP/3 の ALPN: 長さプレフィックス付き "h3" */ | |
| static const unsigned char alpn[] = {2, 'h', '3'}; | |
| if (qconn == NULL) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, | |
| "QUIC connection BIO must be provided"); | |
| return NULL; | |
| } | |
| if ((conn = OPENSSL_zalloc(sizeof(OSSL_DEMO_H3_CONN))) == NULL) | |
| return NULL; | |
| conn->qconn = qconn; | |
| conn->user_data = user_data; | |
| /* Create the map of stream IDs to OSSL_DEMO_H3_STREAM structures. */ | |
| /* ストリーム ID → ストリームオブジェクトのハッシュマップを作成 */ | |
| if ((conn->streams = lh_OSSL_DEMO_H3_STREAM_new(h3_stream_hash, h3_stream_eq)) == NULL) | |
| goto err; | |
| /* | |
| * HTTP/3 の ALPN "h3" を設定 | |
| * 注意: SSL_set_alpn_protos は失敗時に 1 を返す(成功時は 0) | |
| */ | |
| if (SSL_set_alpn_protos(conn->qconn, alpn, sizeof(alpn))) { | |
| /* SSL_set_alpn_protos returns 1 on failure */ | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "failed to configure ALPN"); | |
| goto err; | |
| } | |
| /* | |
| * Disable default stream mode and create all streams explicitly. Each QUIC | |
| * stream will be represented by its own QUIC stream SSL object (QSSO). This | |
| * also automatically enables us to accept incoming streams (see | |
| * SSL_set_incoming_stream_policy(3)). | |
| */ | |
| /* | |
| * デフォルトストリームモードを無効化 | |
| * HTTP/3 では複数のストリームを明示的に管理する必要があるため | |
| * SSL_DEFAULT_STREAM_MODE_NONE を設定 | |
| */ | |
| if (!SSL_set_default_stream_mode(conn->qconn, SSL_DEFAULT_STREAM_MODE_NONE)) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "failed to configure default stream mode"); | |
| goto err; | |
| } | |
| /* | |
| * HTTP/3 requires a couple of unidirectional management streams: a control | |
| * stream and some QPACK state management streams for each side of a | |
| * connection. These are the instances on our side (with us sending); the | |
| * server will also create its own equivalent unidirectional streams on its | |
| * side, which we handle subsequently as they come in (see SSL_accept_stream | |
| * in the event handling code below). | |
| */ | |
| /* | |
| * HTTP/3 に必要な単方向管理ストリームを作成: | |
| * - 制御ストリーム: HTTP/3 の設定やフレームを送信 | |
| * - QPACK エンコーダ: ヘッダー圧縮の動的テーブル更新 | |
| * - QPACK デコーダ: ヘッダー解凍の確認応答 | |
| * サーバー側も同様のストリームを作成し、SSL_accept_stream で受信する | |
| */ | |
| if ((s_ctl_send | |
| = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_CTRL_SEND)) == NULL) | |
| goto err; | |
| if ((s_qpenc_send | |
| = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_ENC_SEND)) == NULL) | |
| goto err; | |
| if ((s_qpdec_send | |
| = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_QPACK_DEC_SEND)) == NULL) | |
| goto err; | |
| /* デフォルト設定を使用 */ | |
| if (settings == NULL) { | |
| nghttp3_settings_default(&dsettings); | |
| settings = &dsettings; | |
| } | |
| if (callbacks != NULL) | |
| intl_callbacks = *callbacks; | |
| /* | |
| * We need to do some of our own processing when many of these events occur, | |
| * so we note the original callback functions and forward appropriately. | |
| */ | |
| /* | |
| * コールバックのラッピング | |
| * 内部処理を行った後にアプリケーションのコールバックを呼び出す | |
| */ | |
| conn->recv_data_cb = intl_callbacks.recv_data; | |
| conn->stream_close_cb = intl_callbacks.stream_close; | |
| conn->stop_sending_cb = intl_callbacks.stop_sending; | |
| conn->reset_stream_cb = intl_callbacks.reset_stream; | |
| conn->deferred_consume_cb = intl_callbacks.deferred_consume; | |
| intl_callbacks.recv_data = h3_conn_recv_data; | |
| intl_callbacks.stream_close = h3_conn_stream_close; | |
| intl_callbacks.stop_sending = h3_conn_stop_sending; | |
| intl_callbacks.reset_stream = h3_conn_reset_stream; | |
| intl_callbacks.deferred_consume = h3_conn_deferred_consume; | |
| /* Create the HTTP/3 client state. */ | |
| /* nghttp3 クライアント状態を作成 */ | |
| ec = nghttp3_conn_client_new(&conn->h3conn, &intl_callbacks, settings, | |
| NULL, conn); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot create nghttp3 connection: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| /* | |
| * Tell the HTTP/3 stack which stream IDs are used for our outgoing control | |
| * and QPACK streams. Note that we don't have to tell the HTTP/3 stack what | |
| * IDs are used for incoming streams as this is inferred automatically from | |
| * the stream type byte which starts every incoming unidirectional stream, | |
| * so it will autodetect the correct stream IDs for the incoming control and | |
| * QPACK streams initiated by the server. | |
| */ | |
| /* | |
| * 送信用ストリーム ID を nghttp3 にバインド | |
| * 受信側のストリーム ID はストリームタイプバイトから自動検出される | |
| */ | |
| ec = nghttp3_conn_bind_control_stream(conn->h3conn, s_ctl_send->id); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot bind nghttp3 control stream: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| ec = nghttp3_conn_bind_qpack_streams(conn->h3conn, | |
| s_qpenc_send->id, | |
| s_qpdec_send->id); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot bind nghttp3 QPACK streams: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| return conn; | |
| err: | |
| nghttp3_conn_del(conn->h3conn); | |
| h3_stream_free(s_ctl_send); | |
| h3_stream_free(s_qpenc_send); | |
| h3_stream_free(s_qpdec_send); | |
| lh_OSSL_DEMO_H3_STREAM_free(conn->streams); | |
| OPENSSL_free(conn); | |
| return NULL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_socket_bio - UDP ソケット BIO を作成 | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * 指定されたアドレスに接続された UDP ソケットを作成し、 | |
| * BIO でラップして返す。QUIC は UDP 上で動作する。 | |
| */ | |
| /* | |
| * Create BIO with UDP connected socket for one of the address we got from | |
| * resolver. | |
| */ | |
| static BIO * | |
| create_socket_bio(const BIO_ADDRINFO *bai) | |
| { | |
| int sock; | |
| BIO *bio; | |
| /* UDP ソケットを作成(QUIC 用) */ | |
| sock = BIO_socket(BIO_ADDRINFO_family(bai), SOCK_DGRAM, 0, 0); | |
| if (sock == -1) | |
| return NULL; | |
| /* サーバーに接続(UDP でも connect() でピアアドレスを設定可能) */ | |
| if (!BIO_connect(sock, BIO_ADDRINFO_address(bai), 0)) | |
| goto err; | |
| /* 非ブロッキングモードに設定 */ | |
| if (!BIO_socket_nbio(sock, 1)) | |
| goto err; | |
| /* データグラム BIO を作成(UDP 用) */ | |
| if ((bio = BIO_new(BIO_s_datagram())) == NULL) | |
| goto err; | |
| BIO_set_fd(bio, sock, BIO_CLOSE); | |
| return bio; | |
| err: | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_CONN_new_for_addr - アドレスから HTTP/3 接続を作成 | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * 指定されたアドレスに接続する HTTP/3 クライアント接続を作成する。 | |
| * QUIC 接続の確立から HTTP/3 の初期化までを一括で行う。 | |
| * | |
| * 【処理内容】 | |
| * 1. UDP ソケット BIO を作成 | |
| * 2. QUIC SSL オブジェクトを作成 | |
| * 3. ホスト名検証と SNI を設定 | |
| * 4. HTTP/3 接続を初期化 | |
| */ | |
| OSSL_DEMO_H3_CONN *OSSL_DEMO_H3_CONN_new_for_addr(SSL_CTX *ctx, const BIO_ADDRINFO *bai, | |
| const char *bare_hostname, | |
| const nghttp3_callbacks *callbacks, | |
| const nghttp3_settings *settings, | |
| void *user_data) | |
| { | |
| BIO *qconn_bio = NULL; | |
| SSL *qconn = NULL; | |
| OSSL_DEMO_H3_CONN *conn = NULL; | |
| /* UDP ソケット BIO を作成 */ | |
| if ((qconn_bio = create_socket_bio(bai)) == NULL) | |
| return NULL; | |
| /* QUIC クライアント SSL オブジェクトを作成 */ | |
| qconn = SSL_new(ctx); | |
| if (qconn == NULL) | |
| goto err; | |
| /* BIO を SSL に関連付け */ | |
| SSL_set_bio(qconn, qconn_bio, qconn_bio); | |
| qconn_bio = NULL; | |
| /* Set the hostname we will validate the X.509 certificate against. */ | |
| /* 証明書検証用のホスト名を設定 */ | |
| if (SSL_set1_host(qconn, bare_hostname) <= 0) | |
| goto err; | |
| /* Configure SNI */ | |
| /* SNI (Server Name Indication) を設定 */ | |
| if (!SSL_set_tlsext_host_name(qconn, bare_hostname)) | |
| goto err; | |
| /* HTTP/3 接続を初期化 */ | |
| conn = OSSL_DEMO_H3_CONN_new_for_conn(qconn, callbacks, | |
| settings, user_data); | |
| if (conn == NULL) | |
| goto err; | |
| return conn; | |
| err: | |
| SSL_free(qconn); | |
| BIO_free_all(qconn_bio); | |
| return NULL; | |
| } | |
| /* 接続に関連付けられたユーザーデータを取得 */ | |
| void *OSSL_DEMO_H3_CONN_get_user_data(const OSSL_DEMO_H3_CONN *conn) | |
| { | |
| return conn->user_data; | |
| } | |
| /* 基盤となる QUIC 接続 SSL オブジェクトを取得 */ | |
| SSL *OSSL_DEMO_H3_CONN_get0_connection(const OSSL_DEMO_H3_CONN *conn) | |
| { | |
| return conn->qconn; | |
| } | |
| /* | |
| * ============================================================================ | |
| * h3_conn_pump_stream - 単一ストリームのデータを HTTP/3 スタックに転送 | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * QUIC ストリームから受信したデータを nghttp3 に渡す。 | |
| * SSL_read_ex → nghttp3_conn_read_stream の流れでデータを転送。 | |
| */ | |
| /* Pumps received data to the HTTP/3 stack for a single stream. */ | |
| static void h3_conn_pump_stream(OSSL_DEMO_H3_STREAM *s, void *conn_) | |
| { | |
| int ec; | |
| OSSL_DEMO_H3_CONN *conn = conn_; | |
| size_t num_bytes, consumed; | |
| uint64_t aec; | |
| if (!conn->pump_res) | |
| /* | |
| * Handling of a previous stream in the iteration over all streams | |
| * failed, so just do nothing. | |
| */ | |
| return; | |
| for (;;) { | |
| if (s->s == NULL /* If we already did STOP_SENDING, ignore this stream. */ | |
| /* If this is a write-only stream, there is no read data to check. */ | |
| || SSL_get_stream_read_state(s->s) == SSL_STREAM_STATE_WRONG_DIR | |
| /* | |
| * If we already got a FIN for this stream, there is nothing more to | |
| * do for it. | |
| */ | |
| || s->done_recv_fin) | |
| break; | |
| /* | |
| * Pump data from OpenSSL QUIC to the HTTP/3 stack by calling SSL_peek | |
| * to get received data and passing it to nghttp3 using | |
| * nghttp3_conn_read_stream. Note that this function is confusingly | |
| * named and inputs data to the HTTP/3 stack. | |
| */ | |
| if (s->buf_cur == s->buf_total) { | |
| /* Need more data. */ | |
| ec = SSL_read_ex(s->s, s->buf, sizeof(s->buf), &num_bytes); | |
| if (ec <= 0) { | |
| num_bytes = 0; | |
| if (SSL_get_error(s->s, ec) == SSL_ERROR_ZERO_RETURN) { | |
| /* Stream concluded normally. Pass FIN to HTTP/3 stack. */ | |
| ec = nghttp3_conn_read_stream(conn->h3conn, s->id, NULL, 0, | |
| /*fin=*/1); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot pass FIN to nghttp3: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| s->done_recv_fin = 1; | |
| } else if (SSL_get_stream_read_state(s->s) | |
| == SSL_STREAM_STATE_RESET_REMOTE) { | |
| /* Stream was reset by peer. */ | |
| if (!SSL_get_stream_read_error_code(s->s, &aec)) | |
| goto err; | |
| ec = nghttp3_conn_close_stream(conn->h3conn, s->id, aec); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot mark stream as reset: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| s->done_recv_fin = 1; | |
| } else { | |
| /* Other error. */ | |
| goto err; | |
| } | |
| } | |
| s->buf_cur = 0; | |
| s->buf_total = num_bytes; | |
| } | |
| if (s->buf_cur == s->buf_total) | |
| break; | |
| /* | |
| * This function is confusingly named as it is is named from nghttp3's | |
| * 'perspective'; it is used to pass data *into* the HTTP/3 stack which | |
| * has been received from the network. | |
| */ | |
| assert(conn->consumed_app_data == 0); | |
| ec = nghttp3_conn_read_stream(conn->h3conn, s->id, s->buf + s->buf_cur, | |
| s->buf_total - s->buf_cur, /*fin=*/0); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "nghttp3 failed to process incoming data: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| /* | |
| * read_stream reports the data it consumes from us in two different | |
| * ways; the non-application data is returned as a number of bytes 'ec' | |
| * above, but the number of bytes of application data has to be recorded | |
| * by our callback. We sum the two to determine the total number of | |
| * bytes which nghttp3 consumed. | |
| */ | |
| consumed = ec + conn->consumed_app_data; | |
| assert(consumed <= s->buf_total - s->buf_cur); | |
| s->buf_cur += consumed; | |
| conn->consumed_app_data = 0; | |
| } | |
| return; | |
| err: | |
| conn->pump_res = 0; | |
| } | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_CONN_handle_events - HTTP/3 イベント処理のメインループ | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * HTTP/3 接続のイベントを処理する。以下の3つの処理を行う: | |
| * | |
| * 1. 新しい受信ストリームの処理 (SSL_accept_stream) | |
| * - サーバーが開始した単方向ストリームを受け入れる | |
| * | |
| * 2. 送信データの転送 (nghttp3 → QUIC) | |
| * - nghttp3_conn_writev_stream でデータを取得 | |
| * - SSL_write_ex2 でネットワークに送信 | |
| * | |
| * 3. 受信データの転送 (QUIC → nghttp3) | |
| * - SSL_read_ex でデータを受信 | |
| * - nghttp3_conn_read_stream で nghttp3 に渡す | |
| * | |
| * 【戻り値】 | |
| * - 1: 成功 | |
| * - 0: エラー | |
| */ | |
| int OSSL_DEMO_H3_CONN_handle_events(OSSL_DEMO_H3_CONN *conn) | |
| { | |
| int ec, fin; | |
| size_t i, num_vecs, written, total_written, total_len; | |
| int64_t stream_id; | |
| uint64_t flags; | |
| nghttp3_vec vecs[8] = {0}; | |
| OSSL_DEMO_H3_STREAM key, *s; | |
| SSL *snew; | |
| if (conn == NULL) | |
| return 0; | |
| /* | |
| * We handle events by doing three things: | |
| * | |
| * 1. Handle new incoming streams | |
| * 2. Pump outgoing data from the HTTP/3 stack to the QUIC engine | |
| * 3. Pump incoming data from the QUIC engine to the HTTP/3 stack | |
| */ | |
| /* 1. Check for new incoming streams */ | |
| /* | |
| * ステップ 1: 新しい受信ストリームをチェック | |
| * サーバーが開始した制御/QPACK ストリームを受け入れる | |
| */ | |
| for (;;) { | |
| if ((snew = SSL_accept_stream(conn->qconn, SSL_ACCEPT_STREAM_NO_BLOCK)) == NULL) | |
| break; | |
| /* | |
| * Each new incoming stream gets wrapped into an OSSL_DEMO_H3_STREAM object and | |
| * added into our stream ID map. | |
| */ | |
| /* 新しいストリームをラップしてマップに追加 */ | |
| if (h3_conn_accept_stream(conn, snew) == NULL) { | |
| SSL_free(snew); | |
| return 0; | |
| } | |
| } | |
| /* 2. Pump outgoing data from HTTP/3 engine to QUIC. */ | |
| /* | |
| * ステップ 2: nghttp3 から QUIC へ送信データを転送 | |
| * nghttp3_conn_writev_stream → SSL_write_ex2 | |
| */ | |
| for (;;) { | |
| /* | |
| * Get a number of send vectors from the HTTP/3 engine. | |
| * | |
| * Note that this function is confusingly named as it is named from | |
| * nghttp3's 'perspective': this outputs pointers to data which nghttp3 | |
| * wants to *write* to the network. | |
| */ | |
| /* | |
| * nghttp3 から送信データを取得 | |
| * 注意: 関数名は nghttp3 の視点から命名されており、 | |
| * nghttp3 が「書き込みたい」データを出力する | |
| */ | |
| ec = nghttp3_conn_writev_stream(conn->h3conn, &stream_id, &fin, | |
| vecs, ARRAY_LEN(vecs)); | |
| if (ec < 0) | |
| return 0; | |
| if (ec == 0) | |
| break; | |
| /* | |
| * we let SSL_write_ex2(3) to conclude the stream for us (send FIN) | |
| * after all data are written. | |
| */ | |
| flags = (fin == 0) ? 0 : SSL_WRITE_FLAG_CONCLUDE; | |
| /* For each of the vectors returned, pass it to OpenSSL QUIC. */ | |
| key.id = stream_id; | |
| if ((s = lh_OSSL_DEMO_H3_STREAM_retrieve(conn->streams, &key)) == NULL) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "no stream for ID %zd", stream_id); | |
| return 0; | |
| } | |
| num_vecs = ec; | |
| total_len = nghttp3_vec_len(vecs, num_vecs); | |
| total_written = 0; | |
| for (i = 0; i < num_vecs; ++i) { | |
| if (vecs[i].len == 0) | |
| continue; | |
| if (s->s == NULL) { | |
| /* Already did STOP_SENDING and threw away stream, ignore */ | |
| written = vecs[i].len; | |
| } else if (!SSL_write_ex2(s->s, vecs[i].base, vecs[i].len, flags, &written)) { | |
| if (SSL_get_error(s->s, 0) == SSL_ERROR_WANT_WRITE) { | |
| /* | |
| * We have filled our send buffer so tell nghttp3 to stop | |
| * generating more data; we have to do this explicitly. | |
| */ | |
| written = 0; | |
| nghttp3_conn_block_stream(conn->h3conn, stream_id); | |
| } else { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "writing HTTP/3 data to network failed"); | |
| return 0; | |
| } | |
| } else { | |
| /* | |
| * Tell nghttp3 it can resume generating more data in case we | |
| * previously called block_stream. | |
| */ | |
| nghttp3_conn_unblock_stream(conn->h3conn, stream_id); | |
| } | |
| total_written += written; | |
| if (written > 0) { | |
| /* | |
| * Tell nghttp3 we have consumed the data it output when we | |
| * called writev_stream, otherwise subsequent calls to | |
| * writev_stream will output the same data. | |
| */ | |
| ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, written); | |
| if (ec < 0) | |
| return 0; | |
| /* | |
| * Tell nghttp3 it can free the buffered data because we will | |
| * not need it again. In our case we can always do this right | |
| * away because we copy the data into our QUIC send buffers | |
| * rather than simply storing a reference to it. | |
| */ | |
| ec = nghttp3_conn_add_ack_offset(conn->h3conn, stream_id, written); | |
| if (ec < 0) | |
| return 0; | |
| } | |
| } | |
| if (fin && total_written == total_len) { | |
| if (total_len == 0) { | |
| /* | |
| * As a special case, if nghttp3 requested to write a | |
| * zero-length stream with a FIN, we have to tell it we did this | |
| * by calling add_write_offset(0). | |
| */ | |
| ec = nghttp3_conn_add_write_offset(conn->h3conn, stream_id, 0); | |
| if (ec < 0) | |
| return 0; | |
| } | |
| } | |
| } | |
| /* 3. Pump incoming data from QUIC to HTTP/3 engine. */ | |
| /* | |
| * ステップ 3: QUIC から nghttp3 へ受信データを転送 | |
| * 全ストリームに対して h3_conn_pump_stream を実行 | |
| */ | |
| conn->pump_res = 1; /* cleared in below call if an error occurs */ | |
| lh_OSSL_DEMO_H3_STREAM_doall_arg(conn->streams, h3_conn_pump_stream, conn); | |
| if (!conn->pump_res) | |
| return 0; | |
| return 1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * OSSL_DEMO_H3_CONN_submit_request - HTTP/3 リクエストを送信 | |
| * ============================================================================ | |
| * | |
| * 【機能】 | |
| * HTTP/3 リクエストを送信する。新しい双方向ストリームを作成し、 | |
| * リクエストヘッダーとオプションでボディを送信する。 | |
| * | |
| * 【引数】 | |
| * - conn: HTTP/3 接続 | |
| * - nva: ヘッダーの名前-値ペア配列(:method, :path, :scheme など) | |
| * - nvlen: ヘッダー数 | |
| * - dr: リクエストボディのデータリーダー(NULL 可) | |
| * - user_data: リクエストに関連付けるユーザーデータ | |
| * | |
| * 【戻り値】 | |
| * - 1: 成功 | |
| * - 0: エラー | |
| */ | |
| int OSSL_DEMO_H3_CONN_submit_request(OSSL_DEMO_H3_CONN *conn, | |
| const nghttp3_nv *nva, size_t nvlen, | |
| const nghttp3_data_reader *dr, | |
| void *user_data) | |
| { | |
| int ec; | |
| OSSL_DEMO_H3_STREAM *s_req = NULL; | |
| if (conn == NULL) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_PASSED_NULL_PARAMETER, | |
| "connection must be specified"); | |
| return 0; | |
| } | |
| /* Each HTTP/3 request is represented by a stream. */ | |
| /* HTTP/3 では各リクエストが1つの双方向ストリームで表現される */ | |
| if ((s_req = h3_conn_create_stream(conn, OSSL_DEMO_H3_STREAM_TYPE_REQ)) == NULL) | |
| goto err; | |
| s_req->user_data = user_data; | |
| /* nghttp3 にリクエストを送信 */ | |
| ec = nghttp3_conn_submit_request(conn->h3conn, s_req->id, nva, nvlen, | |
| dr, s_req); | |
| if (ec < 0) { | |
| ERR_raise_data(ERR_LIB_USER, ERR_R_INTERNAL_ERROR, | |
| "cannot submit HTTP/3 request: %s (%d)", | |
| nghttp3_strerror(ec), ec); | |
| goto err; | |
| } | |
| return 1; | |
| err: | |
| h3_conn_remove_stream(conn, s_req); | |
| return 0; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* POSIX機能を有効にする(clock_gettime, localtime_r用) */ | |
| #ifndef _WIN32 | |
| # define _POSIX_C_SOURCE 199309L | |
| #endif | |
| /* | |
| * Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-client-block-with-debug.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-client-block-with-debug.c - デバッグ出力付きブロッキングモードQUICクライアント | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * quic-client-block.c に最大限のデバッグ出力を追加したバージョン。 | |
| * 全てのAPI呼び出し、データ内容、エラー詳細を出力する。 | |
| */ | |
| #include <string.h> | |
| #include <time.h> | |
| #include <stdarg.h> | |
| /* Include the appropriate header file for SOCK_DGRAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <winsock2.h> | |
| # include <ws2tcpip.h> | |
| #else /* Linux/Unix */ | |
| # include <sys/socket.h> | |
| # include <netinet/in.h> | |
| # include <arpa/inet.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| /* ============================================================================ | |
| * デバッグユーティリティ | |
| * ============================================================================ */ | |
| /* | |
| * DEBUG_LOG() - タイムスタンプ付きデバッグログ出力マクロ | |
| * | |
| * フォーマット: [HH:MM:SS.mmm] [関数名] メッセージ | |
| */ | |
| #define DEBUG_LOG(fmt, ...) do { \ | |
| struct timespec ts; \ | |
| struct tm tm_info; \ | |
| clock_gettime(CLOCK_REALTIME, &ts); \ | |
| localtime_r(&ts.tv_sec, &tm_info); \ | |
| fprintf(stderr, "[%02d:%02d:%02d.%03ld] [%s] " fmt "\n", \ | |
| tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec, \ | |
| ts.tv_nsec / 1000000, __func__, ##__VA_ARGS__); \ | |
| fflush(stderr); \ | |
| } while(0) | |
| /* | |
| * DEBUG_LOG_ENTER() - 関数開始ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_ENTER() DEBUG_LOG(">>> %s() 開始", __func__) | |
| /* | |
| * DEBUG_LOG_EXIT() - 関数終了ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_EXIT() DEBUG_LOG("<<< %s() 終了", __func__) | |
| /* | |
| * DEBUG_LOG_EXIT_WITH_RESULT() - 結果付き関数終了ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_EXIT_WITH_RESULT(result) DEBUG_LOG("<<< %s() 終了 (result=%d)", __func__, (int)(result)) | |
| /* | |
| * debug_hexdump() - データの16進ダンプを出力 | |
| * | |
| * @param prefix 出力行の先頭に付けるプレフィックス | |
| * @param data ダンプするデータ | |
| * @param len データの長さ | |
| */ | |
| static void debug_hexdump(const char *prefix, const unsigned char *data, size_t len) | |
| { | |
| size_t i, j; | |
| fprintf(stderr, "%s (%zu bytes):\n", prefix, len); | |
| for (i = 0; i < len; i += 16) { | |
| /* オフセット */ | |
| fprintf(stderr, " %04zx: ", i); | |
| /* 16進表示 */ | |
| for (j = 0; j < 16; j++) { | |
| if (i + j < len) | |
| fprintf(stderr, "%02x ", data[i + j]); | |
| else | |
| fprintf(stderr, " "); | |
| if (j == 7) | |
| fprintf(stderr, " "); | |
| } | |
| /* ASCII表示 */ | |
| fprintf(stderr, " |"); | |
| for (j = 0; j < 16 && i + j < len; j++) { | |
| unsigned char c = data[i + j]; | |
| fprintf(stderr, "%c", (c >= 32 && c < 127) ? c : '.'); | |
| } | |
| fprintf(stderr, "|\n"); | |
| } | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_ssl_error() - OpenSSL エラースタックの詳細を出力 | |
| * | |
| * @param context エラーが発生したコンテキストの説明 | |
| */ | |
| static void debug_print_ssl_error(const char *context) | |
| { | |
| unsigned long err; | |
| char buf[256]; | |
| DEBUG_LOG("=== SSL エラー詳細 [%s] ===", context); | |
| while ((err = ERR_get_error()) != 0) { | |
| ERR_error_string_n(err, buf, sizeof(buf)); | |
| fprintf(stderr, " エラーコード: 0x%lx\n", err); | |
| fprintf(stderr, " ライブラリ: %s\n", ERR_lib_error_string(err)); | |
| fprintf(stderr, " 理由: %s\n", ERR_reason_error_string(err)); | |
| fprintf(stderr, " 詳細: %s\n", buf); | |
| fprintf(stderr, " ---\n"); | |
| } | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_ssl_state() - SSL接続の状態を出力 | |
| * | |
| * @param ssl SSLオブジェクト | |
| */ | |
| static void debug_print_ssl_state(const SSL *ssl) | |
| { | |
| if (ssl == NULL) { | |
| DEBUG_LOG("SSL オブジェクト: NULL"); | |
| return; | |
| } | |
| DEBUG_LOG("=== SSL 状態 ==="); | |
| fprintf(stderr, " 状態文字列: %s\n", SSL_state_string_long(ssl)); | |
| fprintf(stderr, " バージョン: %s\n", SSL_get_version(ssl)); | |
| /* ALPN */ | |
| const unsigned char *alpn_data; | |
| unsigned int alpn_len; | |
| SSL_get0_alpn_selected(ssl, &alpn_data, &alpn_len); | |
| if (alpn_data != NULL && alpn_len > 0) { | |
| fprintf(stderr, " 選択されたALPN: %.*s\n", alpn_len, alpn_data); | |
| } else { | |
| fprintf(stderr, " 選択されたALPN: (なし)\n"); | |
| } | |
| /* 暗号スイート */ | |
| const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); | |
| if (cipher != NULL) { | |
| fprintf(stderr, " 暗号スイート: %s\n", SSL_CIPHER_get_name(cipher)); | |
| fprintf(stderr, " 暗号強度: %d bits\n", SSL_CIPHER_get_bits(cipher, NULL)); | |
| } | |
| /* 証明書検証結果 */ | |
| long verify_result = SSL_get_verify_result(ssl); | |
| fprintf(stderr, " 証明書検証結果: %ld (%s)\n", verify_result, | |
| X509_verify_cert_error_string(verify_result)); | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_peer_certificate() - ピア証明書の情報を出力 | |
| * | |
| * @param ssl SSLオブジェクト | |
| */ | |
| static void debug_print_peer_certificate(const SSL *ssl) | |
| { | |
| X509 *cert; | |
| char *subject, *issuer; | |
| DEBUG_LOG("=== ピア証明書情報 ==="); | |
| cert = SSL_get_peer_certificate(ssl); | |
| if (cert == NULL) { | |
| fprintf(stderr, " ピア証明書: (なし)\n"); | |
| fflush(stderr); | |
| return; | |
| } | |
| subject = X509_NAME_oneline(X509_get_subject_name(cert), NULL, 0); | |
| issuer = X509_NAME_oneline(X509_get_issuer_name(cert), NULL, 0); | |
| fprintf(stderr, " Subject: %s\n", subject ? subject : "(取得失敗)"); | |
| fprintf(stderr, " Issuer: %s\n", issuer ? issuer : "(取得失敗)"); | |
| OPENSSL_free(subject); | |
| OPENSSL_free(issuer); | |
| X509_free(cert); | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_bio_addr() - BIO_ADDRの内容を出力 | |
| * | |
| * @param prefix 出力の先頭に付けるプレフィックス | |
| * @param addr BIO_ADDRオブジェクト | |
| */ | |
| static void debug_print_bio_addr(const char *prefix, const BIO_ADDR *addr) | |
| { | |
| char *host = NULL; | |
| char *port = NULL; | |
| if (addr == NULL) { | |
| DEBUG_LOG("%s: (NULL)", prefix); | |
| return; | |
| } | |
| /* BIO_ADDR_hostname_string/service_string は動的に確保した文字列を返す */ | |
| host = BIO_ADDR_hostname_string(addr, 1); /* 1 = numeric */ | |
| port = BIO_ADDR_service_string(addr, 1); /* 1 = numeric */ | |
| if (host != NULL && port != NULL) { | |
| DEBUG_LOG("%s: %s:%s", prefix, host, port); | |
| } else { | |
| DEBUG_LOG("%s: (アドレス取得失敗)", prefix); | |
| } | |
| OPENSSL_free(host); | |
| OPENSSL_free(port); | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_socket_bio() - サーバーに接続するBIOを作成(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| static BIO *create_socket_bio(const char *hostname, const char *port, | |
| int family, BIO_ADDR **peer_addr) | |
| { | |
| int sock = -1; | |
| BIO_ADDRINFO *res; | |
| const BIO_ADDRINFO *ai = NULL; | |
| BIO *bio; | |
| int attempt = 0; | |
| DEBUG_LOG_ENTER(); | |
| DEBUG_LOG("ホスト名: %s", hostname); | |
| DEBUG_LOG("ポート: %s", port); | |
| DEBUG_LOG("アドレスファミリ: %s", family == AF_INET6 ? "AF_INET6" : "AF_INET"); | |
| /* DNSルックアップ */ | |
| DEBUG_LOG("BIO_lookup_ex() を呼び出し中..."); | |
| if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0, | |
| &res)) { | |
| DEBUG_LOG("BIO_lookup_ex() 失敗!"); | |
| debug_print_ssl_error("BIO_lookup_ex"); | |
| DEBUG_LOG_EXIT_WITH_RESULT(0); | |
| return NULL; | |
| } | |
| DEBUG_LOG("BIO_lookup_ex() 成功"); | |
| /* 各アドレスに対してループ */ | |
| for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { | |
| attempt++; | |
| DEBUG_LOG("接続試行 #%d", attempt); | |
| /* UDPソケット作成 */ | |
| DEBUG_LOG("BIO_socket() を呼び出し中..."); | |
| sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); | |
| DEBUG_LOG("BIO_socket() 戻り値: %d", sock); | |
| if (sock == -1) { | |
| DEBUG_LOG("ソケット作成失敗、次のアドレスを試行"); | |
| continue; | |
| } | |
| /* ソケット接続 */ | |
| DEBUG_LOG("BIO_connect() を呼び出し中..."); | |
| if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { | |
| DEBUG_LOG("BIO_connect() 失敗"); | |
| debug_print_ssl_error("BIO_connect"); | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| DEBUG_LOG("BIO_connect() 成功"); | |
| /* 非ブロッキングモード設定 */ | |
| DEBUG_LOG("BIO_socket_nbio() を呼び出し中..."); | |
| if (!BIO_socket_nbio(sock, 1)) { | |
| DEBUG_LOG("BIO_socket_nbio() 失敗"); | |
| debug_print_ssl_error("BIO_socket_nbio"); | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| DEBUG_LOG("BIO_socket_nbio() 成功"); | |
| break; /* 成功 */ | |
| } | |
| /* ピアアドレスを複製 */ | |
| if (sock != -1) { | |
| DEBUG_LOG("BIO_ADDR_dup() を呼び出し中..."); | |
| *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); | |
| if (*peer_addr == NULL) { | |
| DEBUG_LOG("BIO_ADDR_dup() 失敗!"); | |
| BIO_closesocket(sock); | |
| BIO_ADDRINFO_free(res); | |
| DEBUG_LOG_EXIT_WITH_RESULT(0); | |
| return NULL; | |
| } | |
| debug_print_bio_addr("ピアアドレス", *peer_addr); | |
| } | |
| /* DNSルックアップ結果を解放 */ | |
| DEBUG_LOG("BIO_ADDRINFO_free() を呼び出し中..."); | |
| BIO_ADDRINFO_free(res); | |
| if (sock == -1) { | |
| DEBUG_LOG("全てのアドレスで接続失敗"); | |
| DEBUG_LOG_EXIT_WITH_RESULT(0); | |
| return NULL; | |
| } | |
| /* データグラムBIOを作成 */ | |
| DEBUG_LOG("BIO_new(BIO_s_datagram()) を呼び出し中..."); | |
| bio = BIO_new(BIO_s_datagram()); | |
| if (bio == NULL) { | |
| DEBUG_LOG("BIO_new() 失敗!"); | |
| debug_print_ssl_error("BIO_new"); | |
| BIO_closesocket(sock); | |
| DEBUG_LOG_EXIT_WITH_RESULT(0); | |
| return NULL; | |
| } | |
| DEBUG_LOG("BIO_new() 成功: bio=%p", (void *)bio); | |
| /* BIOにソケットを関連付け */ | |
| DEBUG_LOG("BIO_set_fd(bio, %d, BIO_CLOSE) を呼び出し中...", sock); | |
| BIO_set_fd(bio, sock, BIO_CLOSE); | |
| DEBUG_LOG("BIO_set_fd() 完了"); | |
| DEBUG_LOG_EXIT(); | |
| return bio; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - QUICクライアントのエントリポイント(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| int main(int argc, char *argv[]) | |
| { | |
| SSL_CTX *ctx = NULL; | |
| SSL *ssl = NULL; | |
| BIO *bio = NULL; | |
| int res = EXIT_FAILURE; | |
| int ret; | |
| int i; | |
| unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; | |
| const char *request_start = "GET / HTTP/1.0\r\nConnection: close\r\nHost: "; | |
| const char *request_end = "\r\n\r\n"; | |
| size_t written, readbytes; | |
| char buf[160]; | |
| BIO_ADDR *peer_addr = NULL; | |
| char *hostname, *port; | |
| int argnext = 1; | |
| int ipv6 = 0; | |
| unsigned long read_count = 0; | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("QUIC クライアント (デバッグモード) 起動"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("OpenSSL バージョン: %s", OpenSSL_version(OPENSSL_VERSION)); | |
| DEBUG_LOG("引数の数: %d", argc); | |
| for (i = 0; i < argc; i++) { | |
| DEBUG_LOG(" argv[%d] = \"%s\"", i, argv[i]); | |
| } | |
| /* コマンドライン引数解析 */ | |
| if (argc < 3) { | |
| printf("Usage: quic-client-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| if (!strcmp(argv[argnext], "-6")) { | |
| if (argc < 4) { | |
| printf("Usage: quic-client-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| ipv6 = 1; | |
| argnext++; | |
| DEBUG_LOG("IPv6モード有効"); | |
| } | |
| hostname = argv[argnext++]; | |
| port = argv[argnext]; | |
| DEBUG_LOG("----------------------------------------"); | |
| DEBUG_LOG("接続先:"); | |
| DEBUG_LOG(" ホスト名: %s", hostname); | |
| DEBUG_LOG(" ポート: %s", port); | |
| DEBUG_LOG(" IPバージョン: %s", ipv6 ? "IPv6" : "IPv4"); | |
| DEBUG_LOG("----------------------------------------"); | |
| /* | |
| * SSL_CTX作成 | |
| */ | |
| DEBUG_LOG("SSL_CTX_new(OSSL_QUIC_client_method()) を呼び出し中..."); | |
| ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
| if (ctx == NULL) { | |
| DEBUG_LOG("SSL_CTX_new() 失敗!"); | |
| debug_print_ssl_error("SSL_CTX_new"); | |
| printf("Failed to create the SSL_CTX\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_CTX_new() 成功: ctx=%p", (void *)ctx); | |
| /* サーバー証明書検証設定 */ | |
| DEBUG_LOG("SSL_CTX_set_verify(SSL_VERIFY_PEER) を呼び出し中..."); | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| DEBUG_LOG("証明書検証モード: SSL_VERIFY_PEER"); | |
| /* デフォルト検証パス設定 */ | |
| DEBUG_LOG("SSL_CTX_set_default_verify_paths() を呼び出し中..."); | |
| if (!SSL_CTX_set_default_verify_paths(ctx)) { | |
| DEBUG_LOG("SSL_CTX_set_default_verify_paths() 失敗!"); | |
| debug_print_ssl_error("SSL_CTX_set_default_verify_paths"); | |
| printf("Failed to set the default trusted certificate store\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_CTX_set_default_verify_paths() 成功"); | |
| /* | |
| * SSLオブジェクト作成 | |
| */ | |
| DEBUG_LOG("SSL_new(ctx) を呼び出し中..."); | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| DEBUG_LOG("SSL_new() 失敗!"); | |
| debug_print_ssl_error("SSL_new"); | |
| printf("Failed to create the SSL object\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_new() 成功: ssl=%p", (void *)ssl); | |
| /* | |
| * ソケットBIO作成 | |
| */ | |
| DEBUG_LOG("create_socket_bio() を呼び出し中..."); | |
| bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, &peer_addr); | |
| if (bio == NULL) { | |
| DEBUG_LOG("create_socket_bio() 失敗!"); | |
| printf("Failed to crete the BIO\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("create_socket_bio() 成功: bio=%p", (void *)bio); | |
| /* BIOをSSLに関連付け */ | |
| DEBUG_LOG("SSL_set_bio(ssl, bio, bio) を呼び出し中..."); | |
| SSL_set_bio(ssl, bio, bio); | |
| DEBUG_LOG("SSL_set_bio() 完了"); | |
| /* | |
| * SNI設定 | |
| */ | |
| DEBUG_LOG("SSL_set_tlsext_host_name(ssl, \"%s\") を呼び出し中...", hostname); | |
| if (!SSL_set_tlsext_host_name(ssl, hostname)) { | |
| DEBUG_LOG("SSL_set_tlsext_host_name() 失敗!"); | |
| debug_print_ssl_error("SSL_set_tlsext_host_name"); | |
| printf("Failed to set the SNI hostname\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_set_tlsext_host_name() 成功"); | |
| /* | |
| * 証明書検証用ホスト名設定 | |
| */ | |
| DEBUG_LOG("SSL_set1_host(ssl, \"%s\") を呼び出し中...", hostname); | |
| if (!SSL_set1_host(ssl, hostname)) { | |
| DEBUG_LOG("SSL_set1_host() 失敗!"); | |
| debug_print_ssl_error("SSL_set1_host"); | |
| printf("Failed to set the certificate verification hostname"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_set1_host() 成功"); | |
| /* | |
| * ALPN設定 | |
| */ | |
| DEBUG_LOG("SSL_set_alpn_protos() を呼び出し中..."); | |
| debug_hexdump("ALPNプロトコル", alpn, sizeof(alpn)); | |
| ret = SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)); | |
| DEBUG_LOG("SSL_set_alpn_protos() 戻り値: %d (0=成功)", ret); | |
| if (ret != 0) { | |
| DEBUG_LOG("SSL_set_alpn_protos() 失敗!"); | |
| debug_print_ssl_error("SSL_set_alpn_protos"); | |
| printf("Failed to set the ALPN for the connection\n"); | |
| goto end; | |
| } | |
| /* | |
| * ピアアドレス設定 | |
| */ | |
| DEBUG_LOG("SSL_set1_initial_peer_addr() を呼び出し中..."); | |
| debug_print_bio_addr("初期ピアアドレス", peer_addr); | |
| if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) { | |
| DEBUG_LOG("SSL_set1_initial_peer_addr() 失敗!"); | |
| debug_print_ssl_error("SSL_set1_initial_peer_addr"); | |
| printf("Failed to set the initial peer address\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_set1_initial_peer_addr() 成功"); | |
| /* | |
| * QUICハンドシェイク | |
| */ | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("QUICハンドシェイク開始"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("SSL_connect(ssl) を呼び出し中..."); | |
| ret = SSL_connect(ssl); | |
| DEBUG_LOG("SSL_connect() 戻り値: %d", ret); | |
| if (ret < 1) { | |
| DEBUG_LOG("SSL_connect() 失敗!"); | |
| debug_print_ssl_error("SSL_connect"); | |
| printf("Failed to connect to the server\n"); | |
| if (SSL_get_verify_result(ssl) != X509_V_OK) { | |
| DEBUG_LOG("証明書検証エラー: %s", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| printf("Verify error: %s\n", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| } | |
| goto end; | |
| } | |
| DEBUG_LOG("SSL_connect() 成功!"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("QUICハンドシェイク完了"); | |
| DEBUG_LOG("========================================"); | |
| /* 接続情報を出力 */ | |
| debug_print_ssl_state(ssl); | |
| debug_print_peer_certificate(ssl); | |
| /* | |
| * HTTPリクエスト送信 | |
| */ | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("HTTPリクエスト送信開始"); | |
| DEBUG_LOG("========================================"); | |
| /* リクエスト開始部分 */ | |
| DEBUG_LOG("[Write #1] SSL_write_ex() を呼び出し中..."); | |
| debug_hexdump("送信データ", (const unsigned char *)request_start, strlen(request_start)); | |
| if (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) { | |
| DEBUG_LOG("SSL_write_ex() 失敗!"); | |
| debug_print_ssl_error("SSL_write_ex"); | |
| printf("Failed to write start of HTTP request\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("[Write #1] SSL_write_ex() 成功: %zu バイト書き込み", written); | |
| /* ホスト名 */ | |
| DEBUG_LOG("[Write #2] SSL_write_ex() を呼び出し中..."); | |
| debug_hexdump("送信データ", (const unsigned char *)hostname, strlen(hostname)); | |
| if (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) { | |
| DEBUG_LOG("SSL_write_ex() 失敗!"); | |
| debug_print_ssl_error("SSL_write_ex"); | |
| printf("Failed to write hostname in HTTP request\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("[Write #2] SSL_write_ex() 成功: %zu バイト書き込み", written); | |
| /* リクエスト終了部分(SSL_WRITE_FLAG_CONCLUDE付き) */ | |
| DEBUG_LOG("[Write #3] SSL_write_ex2(SSL_WRITE_FLAG_CONCLUDE) を呼び出し中..."); | |
| debug_hexdump("送信データ", (const unsigned char *)request_end, strlen(request_end)); | |
| if (!SSL_write_ex2(ssl, request_end, strlen(request_end), | |
| SSL_WRITE_FLAG_CONCLUDE, &written)) { | |
| DEBUG_LOG("SSL_write_ex2() 失敗!"); | |
| debug_print_ssl_error("SSL_write_ex2"); | |
| printf("Failed to write end of HTTP request\n"); | |
| goto end; | |
| } | |
| DEBUG_LOG("[Write #3] SSL_write_ex2() 成功: %zu バイト書き込み (ストリームFIN送信)", written); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("HTTPリクエスト送信完了"); | |
| DEBUG_LOG("========================================"); | |
| /* | |
| * HTTPレスポンス受信 | |
| */ | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("HTTPレスポンス受信開始"); | |
| DEBUG_LOG("========================================"); | |
| while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) { | |
| read_count++; | |
| DEBUG_LOG("[Read #%lu] SSL_read_ex() 成功: %zu バイト受信", read_count, readbytes); | |
| debug_hexdump("受信データ", (const unsigned char *)buf, readbytes); | |
| fwrite(buf, 1, readbytes, stdout); | |
| } | |
| printf("\n"); | |
| DEBUG_LOG("SSL_read_ex() ループ終了 (合計 %lu 回読み取り)", read_count); | |
| /* | |
| * 読み取り終了の原因確認 | |
| */ | |
| DEBUG_LOG("SSL_get_error(ssl, 0) を呼び出し中..."); | |
| ret = SSL_get_error(ssl, 0); | |
| DEBUG_LOG("SSL_get_error() 戻り値: %d", ret); | |
| switch (ret) { | |
| case SSL_ERROR_ZERO_RETURN: | |
| DEBUG_LOG("SSL_ERROR_ZERO_RETURN: 正常終了(ピアがFINを送信)"); | |
| break; | |
| case SSL_ERROR_SSL: | |
| DEBUG_LOG("SSL_ERROR_SSL: ストリームエラー発生"); | |
| DEBUG_LOG("SSL_get_stream_read_state() を呼び出し中..."); | |
| ret = SSL_get_stream_read_state(ssl); | |
| DEBUG_LOG("SSL_get_stream_read_state() 戻り値: %d", ret); | |
| switch (ret) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| DEBUG_LOG("SSL_STREAM_STATE_RESET_REMOTE: リモートがストリームをリセット"); | |
| printf("Stream reset occurred\n"); | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| DEBUG_LOG("SSL_STREAM_STATE_CONN_CLOSED: 接続が閉じられた"); | |
| printf("Connection closed\n"); | |
| goto end; | |
| default: | |
| DEBUG_LOG("不明なストリーム状態: %d", ret); | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| break; | |
| default: | |
| DEBUG_LOG("予期しないエラー: %d", ret); | |
| debug_print_ssl_error("SSL_read_ex"); | |
| printf("Failed reading remaining data\n"); | |
| break; | |
| } | |
| /* | |
| * 接続シャットダウン | |
| */ | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("接続シャットダウン開始"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("SSL_shutdown() を呼び出し中..."); | |
| do { | |
| ret = SSL_shutdown(ssl); | |
| DEBUG_LOG("SSL_shutdown() 戻り値: %d", ret); | |
| if (ret < 0) { | |
| DEBUG_LOG("SSL_shutdown() エラー!"); | |
| debug_print_ssl_error("SSL_shutdown"); | |
| printf("Error shutting down: %d\n", ret); | |
| goto end; | |
| } | |
| } while (ret != 1); | |
| DEBUG_LOG("SSL_shutdown() 完了"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("接続シャットダウン完了"); | |
| DEBUG_LOG("========================================"); | |
| /* Success! */ | |
| res = EXIT_SUCCESS; | |
| end: | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("クリーンアップ開始"); | |
| DEBUG_LOG("========================================"); | |
| if (res == EXIT_FAILURE) { | |
| DEBUG_LOG("エラー発生、OpenSSLエラースタックを出力"); | |
| ERR_print_errors_fp(stderr); | |
| } | |
| DEBUG_LOG("SSL_free(ssl) を呼び出し中..."); | |
| SSL_free(ssl); | |
| DEBUG_LOG("SSL_CTX_free(ctx) を呼び出し中..."); | |
| SSL_CTX_free(ctx); | |
| DEBUG_LOG("BIO_ADDR_free(peer_addr) を呼び出し中..."); | |
| BIO_ADDR_free(peer_addr); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("プログラム終了 (exit code: %d)", res); | |
| DEBUG_LOG("========================================"); | |
| return res; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-client-block.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-client-block.c - ブロッキングモードQUICクライアントデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICを使用したシンプルなHTTPクライアントのデモ。 | |
| * ブロッキングモードで動作し、指定されたサーバーに接続して | |
| * HTTP/1.0 GETリクエストを送信し、レスポンスを表示する。 | |
| * | |
| * 【注意】 | |
| * HTTP/1.0 over QUICは非標準であり、実際のサーバーでは | |
| * サポートされていない。これはデモ目的のみ。 | |
| * 実用的なHTTP/3クライアントにはnghttp3等のライブラリが必要。 | |
| * | |
| * 【他のデモとの比較】 | |
| * - quic-client-block.c (このファイル): ブロッキングモード、最もシンプル | |
| * - quic-client-non-block.c: 非ブロッキングモード、select()使用 | |
| * - quic-multi-stream.c: 複数ストリームの並行処理 | |
| * - ossl-nghttp3-demo.c: HTTP/3プロトコル対応 | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * main() | |
| * │ | |
| * ├── コマンドライン引数解析 (-6オプション、hostname、port) | |
| * │ | |
| * ├── SSL_CTX作成 | |
| * │ ├── OSSL_QUIC_client_method() ... QUICクライアント用メソッド | |
| * │ ├── SSL_VERIFY_PEER ... サーバー証明書検証 | |
| * │ └── デフォルト検証パス設定 | |
| * │ | |
| * ├── SSL作成 | |
| * │ | |
| * ├── create_socket_bio() | |
| * │ ├── BIO_lookup_ex() ... DNSルックアップ | |
| * │ ├── BIO_socket() ... UDPソケット作成 | |
| * │ ├── BIO_connect() ... ソケット接続 | |
| * │ ├── BIO_socket_nbio() ... 非ブロッキング設定 | |
| * │ └── BIO_new(BIO_s_datagram()) ... データグラムBIO作成 | |
| * │ | |
| * ├── SSL_set_bio() ... BIOをSSLに関連付け | |
| * ├── SSL_set_tlsext_host_name() ... SNI設定 | |
| * ├── SSL_set1_host() ... 証明書検証用ホスト名 | |
| * ├── SSL_set_alpn_protos() ... ALPN設定 (http/1.0) | |
| * ├── SSL_set1_initial_peer_addr() ... ピアアドレス設定 | |
| * │ | |
| * ├── SSL_connect() ... QUICハンドシェイク | |
| * │ | |
| * ├── SSL_write_ex() x3 ... HTTPリクエスト送信 | |
| * │ └── SSL_WRITE_FLAG_CONCLUDE ... ストリーム終了フラグ | |
| * │ | |
| * ├── SSL_read_ex() ループ ... レスポンス受信 | |
| * │ | |
| * ├── SSL_get_error() ... エラー確認 | |
| * │ ├── SSL_ERROR_ZERO_RETURN ... 正常終了(FIN受信) | |
| * │ ├── SSL_ERROR_SSL ... エラー | |
| * │ │ ├── SSL_STREAM_STATE_RESET_REMOTE ... リセット | |
| * │ │ └── SSL_STREAM_STATE_CONN_CLOSED ... 接続クローズ | |
| * │ └── その他のエラー | |
| * │ | |
| * └── SSL_shutdown() ループ ... 接続終了 | |
| * | |
| * 【使用方法】 | |
| * ./quic-client-block [-6] hostname port | |
| * | |
| * オプション: | |
| * -6 IPv6を使用 | |
| * | |
| * 例: | |
| * ./quic-client-block localhost 4433 | |
| * ./quic-client-block -6 localhost 4433 | |
| * | |
| * 【ブロッキングモードの特徴】 | |
| * - SSL_connect(), SSL_read_ex(), SSL_write_ex() が完了まで待機 | |
| * - 単純で理解しやすいコード | |
| * - 複数の接続やストリームの同時処理には不向き | |
| * - GUIアプリケーションではUIがフリーズする可能性 | |
| * | |
| * 【関連ドキュメント】 | |
| * doc/man7/ossl-guide-quic-client-block.pod | |
| */ | |
| /* | |
| * NB: Changes to this file should also be reflected in | |
| * doc/man7/ossl-guide-quic-client-block.pod | |
| */ | |
| #include <string.h> | |
| /* Include the appropriate header file for SOCK_DGRAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <winsock2.h> | |
| #else /* Linux/Unix */ | |
| # include <sys/socket.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| /* | |
| * create_socket_bio() - サーバーに接続するBIOを作成 | |
| * | |
| * DNSルックアップを行い、UDPソケットを作成してサーバーに接続し、 | |
| * データグラムBIOでラップして返す。 | |
| * | |
| * 【処理フロー】 | |
| * 1. BIO_lookup_ex(): DNSルックアップ | |
| * - 複数のアドレスが返される可能性がある(IPv4/IPv6) | |
| * | |
| * 2. 各アドレスに対してループ: | |
| * a. BIO_socket(): UDPソケット作成 | |
| * b. BIO_connect(): ソケットをサーバーアドレスに接続 | |
| * ※ UDPのconnect()は実際には通信しない(アドレスを関連付けるだけ) | |
| * c. BIO_socket_nbio(): 非ブロッキングモードに設定 | |
| * ※ QUICスタックが内部でI/Oを管理するため必要 | |
| * | |
| * 3. BIO_new(BIO_s_datagram()): データグラムBIO作成 | |
| * - UDP通信用のBIOタイプ | |
| * | |
| * 4. BIO_set_fd(): ソケットをBIOに関連付け | |
| * - BIO_CLOSE: BIO解放時にソケットも自動クローズ | |
| * | |
| * 【なぜOpenSSLのソケット関数を使うのか】 | |
| * socket(), connect()などの標準関数も使用可能だが、OpenSSLの関数を | |
| * 使用すると: | |
| * - クロスプラットフォーム対応(Windows/Unix) | |
| * - エラーがOpenSSLエラースタックに記録される | |
| * | |
| * 【UDPのconnect()について】 | |
| * TCPと異なり、UDPのconnect()はハンドシェイクを行わない。 | |
| * ローカルソケットにリモートアドレスを関連付けるだけ。 | |
| * これにより: | |
| * - send()/recv()でアドレス指定が不要になる | |
| * - 他のアドレスからのパケットが自動的に拒否される | |
| * | |
| * @param hostname 接続先ホスト名 | |
| * @param port 接続先ポート | |
| * @param family アドレスファミリ (AF_INET または AF_INET6) | |
| * @param peer_addr [出力] サーバーのアドレス(QUICで使用) | |
| * @return 成功時はBIO、失敗時はNULL | |
| */ | |
| /* Helper function to create a BIO connected to the server */ | |
| static BIO *create_socket_bio(const char *hostname, const char *port, | |
| int family, BIO_ADDR **peer_addr) | |
| { | |
| int sock = -1; | |
| BIO_ADDRINFO *res; | |
| const BIO_ADDRINFO *ai = NULL; | |
| BIO *bio; | |
| /* | |
| * Lookup IP address info for the server. | |
| */ | |
| /* | |
| * DNSルックアップ。 | |
| * BIO_LOOKUP_CLIENT: クライアント用のアドレスを取得 | |
| * SOCK_DGRAM: UDPソケット用(QUICはUDP上で動作) | |
| */ | |
| if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0, | |
| &res)) | |
| return NULL; | |
| /* | |
| * Loop through all the possible addresses for the server and find one | |
| * we can connect to. | |
| */ | |
| /* | |
| * DNSが返した全アドレスをループして、接続可能なものを探す。 | |
| * デュアルスタック環境では複数のアドレス(IPv4/IPv6)が返される。 | |
| */ | |
| for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { | |
| /* | |
| * Create a UDP socket. We could equally use non-OpenSSL calls such | |
| * as "socket" here for this and the subsequent connect and close | |
| * functions. But for portability reasons and also so that we get | |
| * errors on the OpenSSL stack in the event of a failure we use | |
| * OpenSSL's versions of these functions. | |
| */ | |
| /* UDPソケットを作成 */ | |
| sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); | |
| if (sock == -1) | |
| continue; | |
| /* Connect the socket to the server's address */ | |
| /* ソケットをサーバーアドレスに接続(UDPなのでアドレス関連付けのみ)*/ | |
| if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| /* Set to nonblocking mode */ | |
| /* | |
| * 非ブロッキングモードに設定。 | |
| * QUICスタックが内部でI/Oタイミングを管理するため、 | |
| * ソケット自体は非ブロッキングである必要がある。 | |
| * ただし、SSLオブジェクトはデフォルトでブロッキングモード | |
| * として動作する(内部でポーリングを行う)。 | |
| */ | |
| if (!BIO_socket_nbio(sock, 1)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| break; /* 成功 */ | |
| } | |
| /* 接続成功時、ピアアドレスを複製して保存 */ | |
| if (sock != -1) { | |
| *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); | |
| if (*peer_addr == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| } | |
| /* Free the address information resources we allocated earlier */ | |
| /* DNSルックアップ結果を解放 */ | |
| BIO_ADDRINFO_free(res); | |
| /* If sock is -1 then we've been unable to connect to the server */ | |
| /* 全てのアドレスで失敗した場合 */ | |
| if (sock == -1) | |
| return NULL; | |
| /* Create a BIO to wrap the socket */ | |
| /* | |
| * データグラムBIOを作成。 | |
| * BIO_s_datagram()はUDP通信用のBIOタイプ。 | |
| * TCP用のBIO_s_socket()とは異なり、データグラム境界を保持する。 | |
| */ | |
| bio = BIO_new(BIO_s_datagram()); | |
| if (bio == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| /* | |
| * Associate the newly created BIO with the underlying socket. By | |
| * passing BIO_CLOSE here the socket will be automatically closed when | |
| * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which | |
| * case you must close the socket explicitly when it is no longer | |
| * needed. | |
| */ | |
| /* | |
| * BIOにソケットを関連付け。 | |
| * BIO_CLOSE: BIO_free()時にソケットも自動的にクローズ | |
| * BIO_NOCLOSE: ソケットは手動でクローズする必要がある | |
| */ | |
| BIO_set_fd(bio, sock, BIO_CLOSE); | |
| return bio; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - QUICクライアントのエントリポイント | |
| * ============================================================================ | |
| * | |
| * HTTP/1.0 GETリクエストをQUIC経由で送信し、レスポンスを表示する。 | |
| * ブロッキングモードで動作するため、各操作は完了まで待機する。 | |
| * | |
| * 【注意】 | |
| * HTTP/1.0 over QUICは非標準。実際のサーバーでは動作しない。 | |
| * デモ目的のみ。 | |
| */ | |
| /* | |
| * Simple application to send a basic HTTP/1.0 request to a server and | |
| * print the response on the screen. Note that HTTP/1.0 over QUIC is | |
| * non-standard and will not typically be supported by real world servers. This | |
| * is for demonstration purposes only. | |
| */ | |
| int main(int argc, char *argv[]) | |
| { | |
| SSL_CTX *ctx = NULL; /* SSL/TLSコンテキスト */ | |
| SSL *ssl = NULL; /* QUIC接続オブジェクト */ | |
| BIO *bio = NULL; /* データグラムBIO(UDPソケット)*/ | |
| int res = EXIT_FAILURE; /* 戻り値(デフォルトはエラー)*/ | |
| int ret; | |
| /* | |
| * ALPN (Application-Layer Protocol Negotiation) | |
| * QUICではALPNが必須。ここでは "http/1.0" を指定。 | |
| * フォーマット: 長さ(1バイト) + 文字列 | |
| */ | |
| unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; | |
| /* HTTP/1.0リクエストの構成要素 */ | |
| const char *request_start = "GET / HTTP/1.0\r\nConnection: close\r\nHost: "; | |
| const char *request_end = "\r\n\r\n"; | |
| size_t written, readbytes; | |
| char buf[160]; /* レスポンス読み取りバッファ */ | |
| BIO_ADDR *peer_addr = NULL; /* サーバーのアドレス */ | |
| char *hostname, *port; | |
| int argnext = 1; | |
| int ipv6 = 0; /* IPv6使用フラグ */ | |
| /* | |
| * ---------------------------------------- | |
| * コマンドライン引数の解析 | |
| * ---------------------------------------- | |
| */ | |
| if (argc < 3) { | |
| printf("Usage: quic-client-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| /* -6 オプション: IPv6を使用 */ | |
| if (!strcmp(argv[argnext], "-6")) { | |
| if (argc < 4) { | |
| printf("Usage: quic-client-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| ipv6 = 1; | |
| argnext++; | |
| } | |
| hostname = argv[argnext++]; | |
| port = argv[argnext]; | |
| /* | |
| * ---------------------------------------- | |
| * SSL_CTXの作成 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Create an SSL_CTX which we can use to create SSL objects from. We | |
| * want an SSL_CTX for creating clients so we use | |
| * OSSL_QUIC_client_method() here. | |
| */ | |
| /* | |
| * QUICクライアント用のSSL_CTXを作成。 | |
| * OSSL_QUIC_client_method()はQUICクライアント専用のメソッド。 | |
| * TCPのSSL_CTX_new(TLS_client_method())に相当。 | |
| */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
| if (ctx == NULL) { | |
| printf("Failed to create the SSL_CTX\n"); | |
| goto end; | |
| } | |
| /* | |
| * Configure the client to abort the handshake if certificate | |
| * verification fails. Virtually all clients should do this unless you | |
| * really know what you are doing. | |
| */ | |
| /* | |
| * サーバー証明書の検証を有効化。 | |
| * 検証失敗時にハンドシェイクを中断する。 | |
| * 本番環境では必須の設定。 | |
| */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| /* Use the default trusted certificate store */ | |
| /* | |
| * システムデフォルトのCA証明書パスを使用。 | |
| * /etc/ssl/certs 等のシステム証明書ストアを参照。 | |
| */ | |
| if (!SSL_CTX_set_default_verify_paths(ctx)) { | |
| printf("Failed to set the default trusted certificate store\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * SSLオブジェクトの作成 | |
| * ---------------------------------------- | |
| */ | |
| /* Create an SSL object to represent the TLS connection */ | |
| /* QUIC接続を表すSSLオブジェクトを作成 */ | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| printf("Failed to create the SSL object\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * ソケットBIOの作成と関連付け | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Create the underlying transport socket/BIO and associate it with the | |
| * connection. | |
| */ | |
| /* UDPソケットを作成してBIOでラップ */ | |
| bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, &peer_addr); | |
| if (bio == NULL) { | |
| printf("Failed to crete the BIO\n"); | |
| goto end; | |
| } | |
| /* | |
| * BIOをSSLオブジェクトに関連付け。 | |
| * 両方の引数に同じBIOを渡す(読み書き両方に使用)。 | |
| * この時点でBIOの所有権はSSLに移る。 | |
| */ | |
| SSL_set_bio(ssl, bio, bio); | |
| /* | |
| * ---------------------------------------- | |
| * SNI (Server Name Indication) の設定 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Tell the server during the handshake which hostname we are attempting | |
| * to connect to in case the server supports multiple hosts. | |
| */ | |
| /* | |
| * SNIを設定。ハンドシェイク時にサーバーにホスト名を通知。 | |
| * 1つのIPアドレスで複数のドメインをホストしている場合に必要。 | |
| */ | |
| if (!SSL_set_tlsext_host_name(ssl, hostname)) { | |
| printf("Failed to set the SNI hostname\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * 証明書検証用ホスト名の設定 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Ensure we check during certificate verification that the server has | |
| * supplied a certificate for the hostname that we were expecting. | |
| * Virtually all clients should do this unless you really know what you | |
| * are doing. | |
| */ | |
| /* | |
| * 証明書のホスト名検証を有効化。 | |
| * サーバー証明書のCN/SANが指定したホスト名と一致することを確認。 | |
| * SNIとは別の設定(SNIは通知、これは検証)。 | |
| */ | |
| if (!SSL_set1_host(ssl, hostname)) { | |
| printf("Failed to set the certificate verification hostname"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * ALPNの設定 | |
| * ---------------------------------------- | |
| */ | |
| /* SSL_set_alpn_protos returns 0 for success! */ | |
| /* | |
| * ALPN (Application-Layer Protocol Negotiation) を設定。 | |
| * QUICではALPNが必須。サーバーとアプリケーションプロトコルをネゴシエート。 | |
| * 注意: この関数は成功時に0を返す(他のSSL関数と逆)。 | |
| */ | |
| if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) { | |
| printf("Failed to set the ALPN for the connection\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * ピアアドレスの設定 | |
| * ---------------------------------------- | |
| */ | |
| /* Set the IP address of the remote peer */ | |
| /* | |
| * QUICの初期ピアアドレスを設定。 | |
| * QUICはコネクションマイグレーションをサポートするため、 | |
| * アドレスが変わる可能性がある。これは初期アドレス。 | |
| */ | |
| if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) { | |
| printf("Failed to set the initial peer address\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * QUICハンドシェイクの実行 | |
| * ---------------------------------------- | |
| */ | |
| /* Do the handshake with the server */ | |
| /* | |
| * QUICハンドシェイクを実行。 | |
| * ブロッキングモードなので、ハンドシェイク完了まで待機。 | |
| * 内部的にはQUIC Initial、Handshake、1-RTTパケットの交換が行われる。 | |
| */ | |
| if (SSL_connect(ssl) < 1) { | |
| printf("Failed to connect to the server\n"); | |
| /* | |
| * If the failure is due to a verification error we can get more | |
| * information about it from SSL_get_verify_result(). | |
| */ | |
| /* 証明書検証エラーの場合、詳細を表示 */ | |
| if (SSL_get_verify_result(ssl) != X509_V_OK) | |
| printf("Verify error: %s\n", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * HTTPリクエストの送信 | |
| * ---------------------------------------- | |
| */ | |
| /* Write an HTTP GET request to the peer */ | |
| /* | |
| * HTTP/1.0 GETリクエストを送信。 | |
| * 3回のSSL_write_ex()で送信: | |
| * 1. "GET / HTTP/1.0\r\nConnection: close\r\nHost: " | |
| * 2. hostname | |
| * 3. "\r\n\r\n" | |
| */ | |
| if (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) { | |
| printf("Failed to write start of HTTP request\n"); | |
| goto end; | |
| } | |
| if (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) { | |
| printf("Failed to write hostname in HTTP request\n"); | |
| goto end; | |
| } | |
| /* | |
| * SSL_write_ex2()でSSL_WRITE_FLAG_CONCLUDEを指定。 | |
| * これにより、データ送信と同時にストリームのFINを送信。 | |
| * サーバーに「これ以上データを送らない」ことを通知。 | |
| */ | |
| if (!SSL_write_ex2(ssl, request_end, strlen(request_end), | |
| SSL_WRITE_FLAG_CONCLUDE, &written)) { | |
| printf("Failed to write end of HTTP request\n"); | |
| goto end; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * HTTPレスポンスの受信 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Get up to sizeof(buf) bytes of the response. We keep reading until the | |
| * server closes the connection. | |
| */ | |
| /* | |
| * レスポンスを読み取り、stdoutに出力。 | |
| * サーバーがストリームを閉じる(FINを送信する)まで読み続ける。 | |
| */ | |
| while (SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) { | |
| /* | |
| * OpenSSL does not guarantee that the returned data is a string or | |
| * that it is NUL terminated so we use fwrite() to write the exact | |
| * number of bytes that we read. The data could be non-printable or | |
| * have NUL characters in the middle of it. For this simple example | |
| * we're going to print it to stdout anyway. | |
| */ | |
| /* | |
| * OpenSSLはNUL終端を保証しないので、fwrite()で正確なバイト数を出力。 | |
| * バイナリデータやNUL文字を含む可能性がある。 | |
| */ | |
| fwrite(buf, 1, readbytes, stdout); | |
| } | |
| /* In case the response didn't finish with a newline we add one now */ | |
| /* レスポンスが改行で終わらない場合のための改行 */ | |
| printf("\n"); | |
| /* | |
| * ---------------------------------------- | |
| * 読み取り終了の原因を確認 | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Check whether we finished the while loop above normally or as the | |
| * result of an error. The 0 argument to SSL_get_error() is the return | |
| * code we received from the SSL_read_ex() call. It must be 0 in order | |
| * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In | |
| * QUIC terms this means that the peer has sent FIN on the stream to | |
| * indicate that no further data will be sent. | |
| */ | |
| /* | |
| * SSL_read_ex()が失敗した理由を確認。 | |
| * SSL_get_error()に渡す0は、SSL_read_ex()の戻り値(失敗時は0)。 | |
| * | |
| * 【エラーコードの意味】 | |
| * SSL_ERROR_ZERO_RETURN: 正常終了。サーバーがFINを送信。 | |
| * SSL_ERROR_SSL: 何らかのエラー発生。詳細は別途確認が必要。 | |
| */ | |
| switch (SSL_get_error(ssl, 0)) { | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* Normal completion of the stream */ | |
| /* 正常終了: サーバーがストリームを閉じた */ | |
| break; | |
| case SSL_ERROR_SSL: | |
| /* | |
| * Some stream fatal error occurred. This could be because of a stream | |
| * reset - or some failure occurred on the underlying connection. | |
| */ | |
| /* | |
| * ストリームレベルのエラー。 | |
| * SSL_get_stream_read_state()で詳細を確認。 | |
| */ | |
| switch (SSL_get_stream_read_state(ssl)) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| printf("Stream reset occurred\n"); | |
| /* The stream has been reset but the connection is still healthy. */ | |
| /* | |
| * リモートがストリームをリセット(RESET_STREAM)。 | |
| * ストリームは終了だが、接続自体は健全な可能性がある。 | |
| */ | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| printf("Connection closed\n"); | |
| /* Connection is already closed. Skip SSL_shutdown() */ | |
| /* | |
| * 接続自体が閉じられた。 | |
| * SSL_shutdown()は不要なのでスキップ。 | |
| */ | |
| goto end; | |
| default: | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| break; | |
| default: | |
| /* Some other unexpected error occurred */ | |
| /* 予期しないエラー */ | |
| printf ("Failed reading remaining data\n"); | |
| break; | |
| } | |
| /* | |
| * ---------------------------------------- | |
| * 接続のシャットダウン | |
| * ---------------------------------------- | |
| */ | |
| /* | |
| * Repeatedly call SSL_shutdown() until the connection is fully | |
| * closed. | |
| */ | |
| /* | |
| * QUIC接続をクリーンに終了。 | |
| * SSL_shutdown()は複数回呼び出す必要がある場合がある: | |
| * - 戻り値0: シャットダウン進行中 | |
| * - 戻り値1: シャットダウン完了 | |
| * - 戻り値<0: エラー | |
| * | |
| * QUICでは、CONNECTION_CLOSEフレームの送受信が行われる。 | |
| */ | |
| do { | |
| ret = SSL_shutdown(ssl); | |
| if (ret < 0) { | |
| printf("Error shutting down: %d\n", ret); | |
| goto end; | |
| } | |
| } while (ret != 1); | |
| /* Success! */ | |
| res = EXIT_SUCCESS; | |
| /* | |
| * ---------------------------------------- | |
| * クリーンアップ | |
| * ---------------------------------------- | |
| */ | |
| end: | |
| /* | |
| * If something bad happened then we will dump the contents of the | |
| * OpenSSL error stack to stderr. There might be some useful diagnostic | |
| * information there. | |
| */ | |
| /* エラー発生時、OpenSSLエラースタックの内容を出力 */ | |
| if (res == EXIT_FAILURE) | |
| ERR_print_errors_fp(stderr); | |
| /* | |
| * Free the resources we allocated. We do not free the BIO object here | |
| * because ownership of it was immediately transferred to the SSL object | |
| * via SSL_set_bio(). The BIO will be freed when we free the SSL object. | |
| */ | |
| /* | |
| * リソースを解放。 | |
| * BIOはSSL_set_bio()でSSLに所有権が移ったため、 | |
| * SSL_free()時に自動的に解放される。 | |
| * 明示的にBIO_free()を呼ぶ必要はない。 | |
| */ | |
| SSL_free(ssl); /* SSLオブジェクトを解放(BIOも同時に解放)*/ | |
| SSL_CTX_free(ctx); /* SSL_CTXを解放 */ | |
| BIO_ADDR_free(peer_addr); /* ピアアドレスを解放 */ | |
| return res; /* 成功時EXIT_SUCCESS(0)、失敗時EXIT_FAILURE(1) */ | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2023-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-client-non-block.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-client-non-block.c - 非ブロッキングモードQUICクライアントデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICを使用した非ブロッキングHTTPクライアントのデモ。 | |
| * select()を使用してソケット活動を待機し、I/O操作が一時的に | |
| * 失敗した場合はリトライする。 | |
| * | |
| * 【quic-client-block.cとの違い】 | |
| * - quic-client-block.c: ブロッキングモード。操作完了まで待機。 | |
| * - このファイル: 非ブロッキングモード。select()で明示的に待機。 | |
| * | |
| * 【非ブロッキングモードの利点】 | |
| * - 複数の接続を1つのスレッドで同時に処理可能 | |
| * - GUIアプリケーションでUIがフリーズしない | |
| * - タイムアウトや他のイベントとの統合が容易 | |
| * - より細かいI/O制御が可能 | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * main() | |
| * │ | |
| * ├── SSL_CTX作成、SSL作成、BIO作成(quic-client-block.cと同様) | |
| * │ | |
| * ├── SSL_set_blocking_mode(ssl, 0) ... 非ブロッキングモード設定 | |
| * │ | |
| * ├── SSL_connect() ループ | |
| * │ │ | |
| * │ └── 失敗時 → handle_io_failure() | |
| * │ ├── SSL_ERROR_WANT_READ/WRITE → wait_for_activity() → リトライ | |
| * │ ├── SSL_ERROR_ZERO_RETURN → EOF | |
| * │ └── その他 → エラー | |
| * │ | |
| * ├── SSL_write_ex() ループ(リトライあり) | |
| * │ | |
| * ├── SSL_read_ex() ループ(リトライあり) | |
| * │ | |
| * └── SSL_shutdown() ループ(リトライあり) | |
| * | |
| * 【wait_for_activity()関数】 | |
| * select()を使用してソケットの読み書き可能状態を待機する。 | |
| * QUICスタックが必要とするI/O方向(読み取り/書き込み)と | |
| * タイムアウトを考慮。 | |
| * | |
| * SSL_net_read_desired() → 読み取りが必要か | |
| * SSL_net_write_desired() → 書き込みが必要か | |
| * SSL_get_event_timeout() → 次のタイムアウト | |
| * | |
| * 【handle_io_failure()関数】 | |
| * SSL操作が失敗した際のエラーハンドリング: | |
| * SSL_ERROR_WANT_READ/WRITE: 一時的な失敗、リトライ可能 | |
| * SSL_ERROR_ZERO_RETURN: EOF(正常終了) | |
| * SSL_ERROR_SYSCALL: システムエラー | |
| * SSL_ERROR_SSL: SSLレベルのエラー | |
| * | |
| * 【使用方法】 | |
| * ./quic-client-non-block [-6] hostname port | |
| * | |
| * 【関連ドキュメント】 | |
| * doc/man7/ossl-guide-quic-client-non-block.pod | |
| */ | |
| /* | |
| * NB: Changes to this file should also be reflected in | |
| * doc/man7/ossl-guide-quic-client-non-block.pod | |
| */ | |
| #include <string.h> | |
| /* Include the appropriate header file for SOCK_DGRAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <winsock2.h> | |
| #else /* Linux/Unix */ | |
| # include <sys/socket.h> | |
| # include <sys/select.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| /* | |
| * create_socket_bio() - サーバーに接続するBIOを作成 | |
| * | |
| * quic-client-block.cのcreate_socket_bio()と同一。 | |
| * DNSルックアップを行い、UDPソケットを作成してサーバーに接続し、 | |
| * データグラムBIOでラップして返す。 | |
| * | |
| * @param hostname 接続先ホスト名 | |
| * @param port 接続先ポート | |
| * @param family アドレスファミリ (AF_INET または AF_INET6) | |
| * @param peer_addr [出力] サーバーのアドレス(QUICで使用) | |
| * @return 成功時はBIO、失敗時はNULL | |
| */ | |
| /* Helper function to create a BIO connected to the server */ | |
| static BIO *create_socket_bio(const char *hostname, const char *port, | |
| int family, BIO_ADDR **peer_addr) | |
| { | |
| int sock = -1; | |
| BIO_ADDRINFO *res; | |
| const BIO_ADDRINFO *ai = NULL; | |
| BIO *bio; | |
| /* | |
| * Lookup IP address info for the server. | |
| */ | |
| /* DNSルックアップ */ | |
| if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0, | |
| &res)) | |
| return NULL; | |
| /* | |
| * Loop through all the possible addresses for the server and find one | |
| * we can connect to. | |
| */ | |
| /* 全てのアドレスを試行 */ | |
| for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { | |
| /* | |
| * Create a UDP socket. We could equally use non-OpenSSL calls such | |
| * as "socket" here for this and the subsequent connect and close | |
| * functions. But for portability reasons and also so that we get | |
| * errors on the OpenSSL stack in the event of a failure we use | |
| * OpenSSL's versions of these functions. | |
| */ | |
| /* UDPソケット作成 */ | |
| sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); | |
| if (sock == -1) | |
| continue; | |
| /* Connect the socket to the server's address */ | |
| /* ソケットをサーバーアドレスに接続(UDPなのでアドレス関連付けのみ)*/ | |
| if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| /* Set to nonblocking mode */ | |
| /* 非ブロッキングモードに設定(QUICスタックが必要とする)*/ | |
| if (!BIO_socket_nbio(sock, 1)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| break; /* 成功 */ | |
| } | |
| /* 接続成功時、ピアアドレスを複製 */ | |
| if (sock != -1) { | |
| *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); | |
| if (*peer_addr == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| } | |
| /* Free the address information resources we allocated earlier */ | |
| /* DNSルックアップ結果を解放 */ | |
| BIO_ADDRINFO_free(res); | |
| /* If sock is -1 then we've been unable to connect to the server */ | |
| if (sock == -1) | |
| return NULL; | |
| /* Create a BIO to wrap the socket */ | |
| /* データグラムBIOを作成 */ | |
| bio = BIO_new(BIO_s_datagram()); | |
| if (bio == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| /* | |
| * Associate the newly created BIO with the underlying socket. By | |
| * passing BIO_CLOSE here the socket will be automatically closed when | |
| * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which | |
| * case you must close the socket explicitly when it is no longer | |
| * needed. | |
| */ | |
| /* BIOにソケットを関連付け(BIO_CLOSE: BIO解放時にソケットも閉じる)*/ | |
| BIO_set_fd(bio, sock, BIO_CLOSE); | |
| return bio; | |
| } | |
| /* | |
| * wait_for_activity() - ソケット活動を待機 | |
| * | |
| * 非ブロッキングモードで重要な関数。select()を使用して、 | |
| * ソケットが読み書き可能になるまで、またはタイムアウトまで待機する。 | |
| * | |
| * 【処理フロー】 | |
| * 1. SSL_get_fd()でソケットFDを取得 | |
| * 2. SSL_net_write_desired()で書き込み必要性を確認 | |
| * 3. SSL_net_read_desired()で読み取り必要性を確認 | |
| * 4. SSL_get_event_timeout()でタイムアウトを取得 | |
| * 5. select()で待機 | |
| * | |
| * 【SSL_net_write_desired() / SSL_net_read_desired()】 | |
| * QUICスタックが次にどの方向のI/Oを必要としているかを返す。 | |
| * 非ブロッキングモードでは、これらを確認してselect()に | |
| * 適切なFDを設定する必要がある。 | |
| * | |
| * 【SSL_get_event_timeout()】 | |
| * QUICの内部タイマー(ACK遅延、PTO等)に基づくタイムアウトを返す。 | |
| * isinfinite=1の場合、タイムアウトなし。 | |
| * | |
| * 【GUIアプリケーションへの応用】 | |
| * コメントに記載の通り、100msごとにGUIを更新したい場合は | |
| * tvpを100msに制限し、タイムアウト時にGUI更新を行う。 | |
| * | |
| * @param ssl SSLオブジェクト | |
| */ | |
| static void wait_for_activity(SSL *ssl) | |
| { | |
| fd_set wfds, rfds; /* 書き込み/読み取り用fd_set */ | |
| int width, sock, isinfinite; | |
| struct timeval tv; | |
| struct timeval *tvp = NULL; /* タイムアウト(NULLなら無期限)*/ | |
| /* Get hold of the underlying file descriptor for the socket */ | |
| /* ソケットFDを取得 */ | |
| sock = SSL_get_fd(ssl); | |
| FD_ZERO(&wfds); | |
| FD_ZERO(&rfds); | |
| /* | |
| * Find out if we would like to write to the socket, or read from it (or | |
| * both) | |
| */ | |
| /* | |
| * QUICスタックが必要とするI/O方向を確認。 | |
| * write_desired: 送信バッファにデータがあり、送信が必要 | |
| * read_desired: 受信が必要(新規パケット待ち) | |
| */ | |
| if (SSL_net_write_desired(ssl)) | |
| FD_SET(sock, &wfds); | |
| if (SSL_net_read_desired(ssl)) | |
| FD_SET(sock, &rfds); | |
| width = sock + 1; /* select()の第1引数(最大FD+1)*/ | |
| /* | |
| * Find out when OpenSSL would next like to be called, regardless of | |
| * whether the state of the underlying socket has changed or not. | |
| */ | |
| /* | |
| * QUICタイマーによる次のタイムアウトを取得。 | |
| * ACK遅延、PTO(パケットロス検出)、アイドルタイムアウト等。 | |
| */ | |
| if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite) | |
| tvp = &tv; | |
| /* | |
| * Wait until the socket is writeable or readable. We use select here | |
| * for the sake of simplicity and portability, but you could equally use | |
| * poll/epoll or similar functions | |
| * | |
| * NOTE: For the purposes of this demonstration code this effectively | |
| * makes this demo block until it has something more useful to do. In a | |
| * real application you probably want to go and do other work here (e.g. | |
| * update a GUI, or service other connections). | |
| * | |
| * Let's say for example that you want to update the progress counter on | |
| * a GUI every 100ms. One way to do that would be to use the timeout in | |
| * the last parameter to "select" below. If the tvp value is greater | |
| * than 100ms then use 100ms instead. Then, when select returns, you | |
| * check if it did so because of activity on the file descriptors or | |
| * because of the timeout. If the 100ms GUI timeout has expired but the | |
| * tvp timeout has not then go and update the GUI and then restart the | |
| * "select" (with updated timeouts). | |
| */ | |
| /* | |
| * select()でソケット活動またはタイムアウトを待機。 | |
| * poll()やepoll()も使用可能。 | |
| * | |
| * 【注意】 | |
| * このデモではselect()でブロックするが、実際のアプリケーションでは | |
| * この間に他の作業(GUI更新、他の接続処理等)を行うことが多い。 | |
| */ | |
| select(width, &rfds, &wfds, NULL, tvp); | |
| } | |
| /* | |
| * handle_io_failure() - I/O失敗時のエラーハンドリング | |
| * | |
| * 非ブロッキングモードでSSL操作が失敗した際に呼び出される。 | |
| * エラーの種類に応じて、リトライ可能かどうかを判断する。 | |
| * | |
| * 【戻り値】 | |
| * 1: リトライ可能(wait_for_activity()後に再試行) | |
| * 0: EOF(正常終了) | |
| * -1: 致命的エラー(リトライ不可) | |
| * | |
| * 【エラーコードの意味】 | |
| * SSL_ERROR_WANT_READ: データ受信が必要。後でリトライ。 | |
| * SSL_ERROR_WANT_WRITE: データ送信が必要。後でリトライ。 | |
| * SSL_ERROR_ZERO_RETURN: EOF。サーバーがFINを送信。 | |
| * SSL_ERROR_SYSCALL: システムコールエラー。 | |
| * SSL_ERROR_SSL: SSLレベルのエラー。 | |
| * | |
| * 【非ブロッキングモードでのパターン】 | |
| * | |
| * while (!SSL_xxx(ssl, ...)) { | |
| * if (handle_io_failure(ssl, ret) == 1) | |
| * continue; // リトライ | |
| * // エラー処理 | |
| * } | |
| * | |
| * @param ssl SSLオブジェクト | |
| * @param res SSL操作の戻り値 | |
| * @return 1:リトライ, 0:EOF, -1:エラー | |
| */ | |
| static int handle_io_failure(SSL *ssl, int res) | |
| { | |
| switch (SSL_get_error(ssl, res)) { | |
| case SSL_ERROR_WANT_READ: | |
| case SSL_ERROR_WANT_WRITE: | |
| /* Temporary failure. Wait until we can read/write and try again */ | |
| /* | |
| * 一時的な失敗。ソケットが読み書き可能になるまで待機して | |
| * リトライする。非ブロッキングモードでは頻繁に発生する。 | |
| */ | |
| wait_for_activity(ssl); | |
| return 1; /* リトライ可能 */ | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* EOF */ | |
| /* EOF: サーバーがストリームを閉じた(FIN送信)*/ | |
| return 0; | |
| case SSL_ERROR_SYSCALL: | |
| /* システムコールエラー */ | |
| return -1; | |
| case SSL_ERROR_SSL: | |
| /* | |
| * Some stream fatal error occurred. This could be because of a | |
| * stream reset - or some failure occurred on the underlying | |
| * connection. | |
| */ | |
| /* | |
| * SSLレベルのエラー。ストリームリセットや接続エラーの可能性。 | |
| * SSL_get_stream_read_state()で詳細を確認。 | |
| */ | |
| switch (SSL_get_stream_read_state(ssl)) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| printf("Stream reset occurred\n"); | |
| /* | |
| * The stream has been reset but the connection is still | |
| * healthy. | |
| */ | |
| /* | |
| * リモートがストリームをリセット(RESET_STREAM)。 | |
| * ストリームは終了だが、接続自体は健全な可能性がある。 | |
| */ | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| printf("Connection closed\n"); | |
| /* Connection is already closed. */ | |
| /* 接続が既に閉じられている */ | |
| break; | |
| default: | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| /* | |
| * If the failure is due to a verification error we can get more | |
| * information about it from SSL_get_verify_result(). | |
| */ | |
| /* 証明書検証エラーの場合、詳細を表示 */ | |
| if (SSL_get_verify_result(ssl) != X509_V_OK) | |
| printf("Verify error: %s\n", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| return -1; | |
| default: | |
| /* その他の予期しないエラー */ | |
| return -1; | |
| } | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - 非ブロッキングQUICクライアントのエントリポイント | |
| * ============================================================================ | |
| * | |
| * quic-client-block.cとの主な違い: | |
| * 1. SSL_set_blocking_mode(ssl, 0) で非ブロッキングモードを設定 | |
| * 2. 各SSL操作をwhileループでリトライ | |
| * 3. handle_io_failure()でエラー種別を判定 | |
| * | |
| * 【注意】 | |
| * HTTP/1.0 over QUICは非標準。デモ目的のみ。 | |
| */ | |
| /* | |
| * Simple application to send a basic HTTP/1.0 request to a server and | |
| * print the response on the screen. Note that HTTP/1.0 over QUIC is | |
| * non-standard and will not typically be supported by real world servers. This | |
| * is for demonstration purposes only. | |
| */ | |
| int main(int argc, char *argv[]) | |
| { | |
| SSL_CTX *ctx = NULL; | |
| SSL *ssl = NULL; | |
| BIO *bio = NULL; | |
| int res = EXIT_FAILURE; | |
| int ret; | |
| unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; /* ALPN: http/1.0 */ | |
| const char *request_start = "GET / HTTP/1.0\r\nConnection: close\r\nHost: "; | |
| const char *request_end = "\r\n\r\n"; | |
| size_t written, readbytes = 0; | |
| char buf[160]; | |
| BIO_ADDR *peer_addr = NULL; | |
| int eof = 0; /* EOF検出フラグ */ | |
| char *hostname, *port; | |
| int ipv6 = 0; | |
| int argnext = 1; | |
| if (argc < 3) { | |
| printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| if (!strcmp(argv[argnext], "-6")) { | |
| if (argc < 4) { | |
| printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| ipv6 = 1; | |
| argnext++; | |
| } | |
| hostname = argv[argnext++]; | |
| port = argv[argnext]; | |
| /* | |
| * ========================================================================= | |
| * 【SSL_CTX作成】 | |
| * ========================================================================= | |
| * | |
| * SSL_CTXはSSLオブジェクトのファクトリ。複数のSSL接続で共有可能。 | |
| * OSSL_QUIC_client_method(): QUICクライアント用メソッド | |
| * | |
| * 注意: TLSクライアントでは TLS_client_method() を使用するが、 | |
| * QUICでは専用のメソッドが必要。 | |
| */ | |
| /* | |
| * Create an SSL_CTX which we can use to create SSL objects from. We | |
| * want an SSL_CTX for creating clients so we use | |
| * OSSL_QUIC_client_method() here. | |
| */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
| if (ctx == NULL) { | |
| printf("Failed to create the SSL_CTX\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【証明書検証設定】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_VERIFY_PEER: サーバー証明書を検証する | |
| * - 検証失敗時はハンドシェイクを中止 | |
| * - 本番環境では必須の設定 | |
| * | |
| * SSL_CTX_set_default_verify_paths(): | |
| * - システムのデフォルトCA証明書ストアを使用 | |
| * - Linux: /etc/ssl/certs/ など | |
| */ | |
| /* | |
| * Configure the client to abort the handshake if certificate | |
| * verification fails. Virtually all clients should do this unless you | |
| * really know what you are doing. | |
| */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| /* Use the default trusted certificate store */ | |
| /* システムのデフォルトCA証明書ストアを使用 */ | |
| if (!SSL_CTX_set_default_verify_paths(ctx)) { | |
| printf("Failed to set the default trusted certificate store\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【SSLオブジェクト作成】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_new(): SSL_CTXから個別の接続を表すSSLオブジェクトを作成 | |
| * 1つの接続につき1つのSSLオブジェクトが必要。 | |
| */ | |
| /* Create an SSL object to represent the TLS connection */ | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| printf("Failed to create the SSL object\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【BIO作成と関連付け】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * create_socket_bio(): UDPソケットを作成し、データグラムBIOでラップ | |
| * SSL_set_bio(): BIOをSSLオブジェクトに関連付け | |
| * | |
| * 注意: SSL_set_bio()後、BIOの所有権はSSLに移転。 | |
| * SSL_free()時に自動的にBIOも解放される。 | |
| */ | |
| /* | |
| * Create the underlying transport socket/BIO and associate it with the | |
| * connection. | |
| */ | |
| bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, | |
| &peer_addr); | |
| if (bio == NULL) { | |
| printf("Failed to crete the BIO\n"); | |
| goto end; | |
| } | |
| SSL_set_bio(ssl, bio, bio); /* 読み書き両方に同じBIOを使用 */ | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【SNI (Server Name Indication) 設定】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_set_tlsext_host_name(): TLS拡張でホスト名を送信 | |
| * - 1つのIPアドレスで複数のドメインをホストするサーバー向け | |
| * - サーバーが適切な証明書を選択できるようにする | |
| */ | |
| /* | |
| * Tell the server during the handshake which hostname we are attempting | |
| * to connect to in case the server supports multiple hosts. | |
| */ | |
| if (!SSL_set_tlsext_host_name(ssl, hostname)) { | |
| printf("Failed to set the SNI hostname\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【ホスト名検証設定】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_set1_host(): 証明書のCN/SANとホスト名を照合 | |
| * - サーバー証明書が期待するホスト名と一致することを確認 | |
| * - 中間者攻撃の防止に必須 | |
| */ | |
| /* | |
| * Ensure we check during certificate verification that the server has | |
| * supplied a certificate for the hostname that we were expecting. | |
| * Virtually all clients should do this unless you really know what you | |
| * are doing. | |
| */ | |
| if (!SSL_set1_host(ssl, hostname)) { | |
| printf("Failed to set the certificate verification hostname"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【ALPN (Application-Layer Protocol Negotiation) 設定】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * ALPNはTLSハンドシェイク中にアプリケーションプロトコルを交渉する。 | |
| * QUICでは必須。 | |
| * | |
| * alpn配列のフォーマット: { 長さ, プロトコル名... } | |
| * 例: { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' } = "http/1.0" | |
| * | |
| * 注意: SSL_set_alpn_protos()は成功時に0を返す(他のAPIと逆) | |
| */ | |
| /* SSL_set_alpn_protos returns 0 for success! */ | |
| if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) { | |
| printf("Failed to set the ALPN for the connection\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【ピアアドレス設定】QUIC固有 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_set1_initial_peer_addr(): QUICパケットの送信先アドレスを設定 | |
| * - UDPはコネクションレスなので、送信先を明示的に指定 | |
| * - TLS over TCPでは不要(TCPが接続を管理) | |
| */ | |
| /* Set the IP address of the remote peer */ | |
| if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) { | |
| printf("Failed to set the initial peer address\n"); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【重要】非ブロッキングモードの設定 | |
| * ========================================================================= | |
| * | |
| * QUICでは内部的にソケットは常に非ブロッキングだが、SSLオブジェクトの | |
| * デフォルト動作はブロッキング(操作完了まで待機)。 | |
| * | |
| * SSL_set_blocking_mode(ssl, 0) を呼ぶことで: | |
| * - SSL操作が即座にリターン(待機しない) | |
| * - SSL_ERROR_WANT_READ/WRITE が返る可能性 | |
| * - アプリケーションが自分でリトライループを実装する必要 | |
| * | |
| * これが quic-client-block.c との最大の違い! | |
| * ブロッキング版ではこの呼び出しがなく、各操作が完了まで待機する。 | |
| */ | |
| /* | |
| * The underlying socket is always nonblocking with QUIC, but the default | |
| * behaviour of the SSL object is still to block. We set it for nonblocking | |
| * mode in this demo. | |
| */ | |
| if (!SSL_set_blocking_mode(ssl, 0)) { | |
| printf("Failed to turn off blocking mode\n"); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【非ブロッキングハンドシェイク】 | |
| * ========================================================================= | |
| * | |
| * ブロッキング版: ret = SSL_connect(ssl); if (ret != 1) error; | |
| * 非ブロッキング版: whileループでリトライ | |
| * | |
| * 非ブロッキングモードでは SSL_connect() が即座にリターンするため: | |
| * - 成功(1)するまでwhileループで繰り返し呼び出す | |
| * - handle_io_failure()が1を返す限りリトライ継続 | |
| * - 0(EOF)や-1(エラー)が返ったら終了 | |
| */ | |
| /* Do the handshake with the server */ | |
| while ((ret = SSL_connect(ssl)) != 1) { | |
| if (handle_io_failure(ssl, ret) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| printf("Failed to connect to server\n"); | |
| goto end; /* Cannot retry: error リトライ不可のエラー */ | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【非ブロッキング書き込み】HTTPリクエストの送信 | |
| * ========================================================================= | |
| * | |
| * HTTPリクエストを3つの部分に分けて送信: | |
| * 1. request_start: "GET / HTTP/1.0\r\nConnection: close\r\nHost: " | |
| * 2. hostname: 接続先ホスト名 | |
| * 3. request_end: "\r\n\r\n" (SSL_WRITE_FLAG_CONCLUDE付き) | |
| * | |
| * 各SSL_write_ex()をwhileループでラップ: | |
| * - 非ブロッキングでは書き込みが即座に完了しない場合がある | |
| * - handle_io_failure()が1を返す限りリトライ | |
| * | |
| * 最後のSSL_write_ex2()でSSL_WRITE_FLAG_CONCLUDEを指定: | |
| * - QUICストリームにFIN(終了)フラグを送信 | |
| * - これ以上データを送らないことをサーバーに通知 | |
| */ | |
| /* Write an HTTP GET request to the peer */ | |
| while (!SSL_write_ex(ssl, request_start, strlen(request_start), &written)) { | |
| if (handle_io_failure(ssl, 0) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| printf("Failed to write start of HTTP request\n"); | |
| goto end; /* Cannot retry: error リトライ不可のエラー */ | |
| } | |
| while (!SSL_write_ex(ssl, hostname, strlen(hostname), &written)) { | |
| if (handle_io_failure(ssl, 0) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| printf("Failed to write hostname in HTTP request\n"); | |
| goto end; /* Cannot retry: error リトライ不可のエラー */ | |
| } | |
| /* SSL_WRITE_FLAG_CONCLUDE: ストリーム終了(FIN送信) */ | |
| while (!SSL_write_ex2(ssl, request_end, strlen(request_end), | |
| SSL_WRITE_FLAG_CONCLUDE, &written)) { | |
| if (handle_io_failure(ssl, 0) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| printf("Failed to write end of HTTP request\n"); | |
| goto end; /* Cannot retry: error リトライ不可のエラー */ | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【非ブロッキング読み込み】HTTPレスポンスの受信 | |
| * ========================================================================= | |
| * | |
| * 外側のdo-whileループ: EOFまでデータを読み続ける | |
| * 内側のwhileループ: SSL_read_ex()をリトライ | |
| * | |
| * handle_io_failure()の戻り値による分岐: | |
| * case 1: リトライ可能 → continue(内側ループ継続) | |
| * case 0: EOF検出 → eof=1をセットして終了 | |
| * case -1: エラー → 処理終了 | |
| * | |
| * 非ブロッキングでは複数回のリトライで1つのレスポンスを読む。 | |
| * ブロッキング版ではSSL_read_ex()が完了するまで内部で待機する。 | |
| */ | |
| do { | |
| /* | |
| * Get up to sizeof(buf) bytes of the response. We keep reading until | |
| * the server closes the connection. | |
| */ | |
| while (!eof && !SSL_read_ex(ssl, buf, sizeof(buf), &readbytes)) { | |
| switch (handle_io_failure(ssl, 0)) { | |
| case 1: | |
| continue; /* Retry リトライ可能 */ | |
| case 0: | |
| eof = 1; /* EOF検出: サーバーが接続を閉じた */ | |
| continue; | |
| case -1: | |
| default: | |
| printf("Failed reading remaining data\n"); | |
| goto end; /* Cannot retry: error リトライ不可のエラー */ | |
| } | |
| } | |
| /* | |
| * OpenSSL does not guarantee that the returned data is a string or | |
| * that it is NUL terminated so we use fwrite() to write the exact | |
| * number of bytes that we read. The data could be non-printable or | |
| * have NUL characters in the middle of it. For this simple example | |
| * we're going to print it to stdout anyway. | |
| */ | |
| /* 読み取ったバイト数だけ出力(NUL終端は保証されない) */ | |
| if (!eof) | |
| fwrite(buf, 1, readbytes, stdout); | |
| } while (!eof); | |
| /* In case the response didn't finish with a newline we add one now */ | |
| printf("\n"); | |
| /* | |
| * ========================================================================= | |
| * 【非ブロッキングシャットダウン】 | |
| * ========================================================================= | |
| * | |
| * SSL_shutdown()の戻り値: | |
| * - 1: 完全にシャットダウン完了(双方向close完了) | |
| * - 0: 送信側のみ完了(受信側close待ち) | |
| * - <0: エラーまたはリトライ必要 | |
| * | |
| * 非ブロッキングでは完了(1)するまでループでリトライ。 | |
| * ret < 0 の場合のみ handle_io_failure() でリトライ判定。 | |
| * ret == 0 の場合はそのまま次のSSL_shutdown()を呼ぶ。 | |
| */ | |
| /* | |
| * Repeatedly call SSL_shutdown() until the connection is fully | |
| * closed. | |
| */ | |
| while ((ret = SSL_shutdown(ssl)) != 1) { | |
| if (ret < 0 && handle_io_failure(ssl, ret) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| } | |
| /* Success! 成功 */ | |
| res = EXIT_SUCCESS; | |
| end: | |
| /* | |
| * ========================================================================= | |
| * 【クリーンアップ】 | |
| * ========================================================================= | |
| */ | |
| /* | |
| * If something bad happened then we will dump the contents of the | |
| * OpenSSL error stack to stderr. There might be some useful diagnostic | |
| * information there. | |
| */ | |
| /* エラー時はOpenSSLエラースタックを出力(デバッグ用) */ | |
| if (res == EXIT_FAILURE) | |
| ERR_print_errors_fp(stderr); | |
| /* | |
| * Free the resources we allocated. We do not free the BIO object here | |
| * because ownership of it was immediately transferred to the SSL object | |
| * via SSL_set_bio(). The BIO will be freed when we free the SSL object. | |
| */ | |
| /* | |
| * リソース解放 | |
| * 注意: BIOはSSL_set_bio()でSSLに所有権移転済み。 | |
| * SSL_free()時に自動的に解放されるため、個別解放不要。 | |
| */ | |
| SSL_free(ssl); | |
| SSL_CTX_free(ctx); | |
| BIO_ADDR_free(peer_addr); | |
| return res; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2023-2024 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-multi-stream.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-multi-stream.c - QUICマルチストリームクライアントデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICのマルチストリーム機能を示すデモプログラム。 | |
| * 1つのQUIC接続上で複数の独立したストリームを使用してデータを送受信する。 | |
| * | |
| * 【QUICストリームの種類】 | |
| * | |
| * 1. 双方向ストリーム (Bidirectional Stream) | |
| * - クライアント/サーバー両方がデータを送受信可能 | |
| * - SSL_new_stream(ssl, 0) で作成 | |
| * | |
| * 2. 単方向ストリーム (Unidirectional Stream) | |
| * - 作成側のみがデータを送信可能 | |
| * - SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI) で作成 | |
| * | |
| * 【ストリームの開始者】 | |
| * | |
| * - クライアント開始: SSL_new_stream() で作成 | |
| * - サーバー開始: SSL_accept_stream() で受け入れ | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * main() | |
| * │ | |
| * ├── SSL_CTX作成、SSL作成 | |
| * │ | |
| * ├── SSL_set_default_stream_mode(SSL_DEFAULT_STREAM_MODE_NONE) | |
| * │ └── デフォルトストリームを無効化(マルチストリーム使用時推奨) | |
| * │ | |
| * ├── SSL_connect() - QUICハンドシェイク | |
| * │ | |
| * ├── SSL_new_stream(ssl, 0) ────────────── stream1 (双方向) | |
| * ├── SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI) ── stream2 (単方向) | |
| * │ | |
| * ├── write_a_request(stream1, ...) ─────── リクエスト送信 | |
| * ├── write_a_request(stream2, ...) ─────── リクエスト送信 | |
| * │ | |
| * ├── SSL_read_ex(stream1, ...) ─────────── stream1からレスポンス読み取り | |
| * │ | |
| * ├── SSL_accept_stream(ssl, 0) ─────────── stream3 (サーバー開始) | |
| * │ | |
| * ├── SSL_read_ex(stream3, ...) ─────────── stream3からレスポンス読み取り | |
| * │ | |
| * └── SSL_shutdown(ssl) - 接続終了 | |
| * | |
| * 【マルチストリームの利点】 | |
| * | |
| * - Head-of-Line Blocking の回避 | |
| * TCPでは1つのパケットロスが全データをブロック | |
| * QUICでは影響を受けるストリームのみがブロック | |
| * | |
| * - 並列処理 | |
| * 複数のリクエスト/レスポンスを同時に処理可能 | |
| * | |
| * - 優先度制御 | |
| * ストリームごとに優先度を設定可能 | |
| * | |
| * 【使用方法】 | |
| * ./quic-multi-stream [-6] hostname port | |
| * | |
| * 【関連ドキュメント】 | |
| * doc/man7/ossl-guide-quic-multi-stream.pod | |
| * | |
| * 【注意】 | |
| * HTTP/1.0 over QUICは非標準。デモ目的のみ。 | |
| * 実際のHTTP/3では異なるプロトコル(nghttp3等)を使用する。 | |
| */ | |
| /* | |
| * NB: Changes to this file should also be reflected in | |
| * doc/man7/ossl-guide-quic-multi-stream.pod | |
| */ | |
| #include <string.h> | |
| /* Include the appropriate header file for SOCK_DGRAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <winsock2.h> | |
| #else /* Linux/Unix */ | |
| # include <sys/socket.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| /* | |
| * create_socket_bio() - サーバーに接続するBIOを作成 | |
| * | |
| * 他のQUICデモ(quic-client-block.c等)と同一の実装。 | |
| * DNSルックアップを行い、UDPソケットを作成してサーバーに接続し、 | |
| * データグラムBIOでラップして返す。 | |
| * | |
| * @param hostname 接続先ホスト名 | |
| * @param port 接続先ポート | |
| * @param family アドレスファミリ (AF_INET または AF_INET6) | |
| * @param peer_addr [出力] サーバーのアドレス(QUICで使用) | |
| * @return 成功時はBIO、失敗時はNULL | |
| */ | |
| /* Helper function to create a BIO connected to the server */ | |
| static BIO *create_socket_bio(const char *hostname, const char *port, | |
| int family, BIO_ADDR **peer_addr) | |
| { | |
| int sock = -1; | |
| BIO_ADDRINFO *res; | |
| const BIO_ADDRINFO *ai = NULL; | |
| BIO *bio; | |
| /* | |
| * Lookup IP address info for the server. | |
| */ | |
| /* DNSルックアップ(SOCK_DGRAM: UDP用) */ | |
| if (!BIO_lookup_ex(hostname, port, BIO_LOOKUP_CLIENT, family, SOCK_DGRAM, 0, | |
| &res)) | |
| return NULL; | |
| /* | |
| * Loop through all the possible addresses for the server and find one | |
| * we can connect to. | |
| */ | |
| /* 全てのアドレスを試行 */ | |
| for (ai = res; ai != NULL; ai = BIO_ADDRINFO_next(ai)) { | |
| /* | |
| * Create a UDP socket. We could equally use non-OpenSSL calls such | |
| * as "socket" here for this and the subsequent connect and close | |
| * functions. But for portability reasons and also so that we get | |
| * errors on the OpenSSL stack in the event of a failure we use | |
| * OpenSSL's versions of these functions. | |
| */ | |
| /* UDPソケット作成 */ | |
| sock = BIO_socket(BIO_ADDRINFO_family(ai), SOCK_DGRAM, 0, 0); | |
| if (sock == -1) | |
| continue; | |
| /* Connect the socket to the server's address */ | |
| /* ソケットをサーバーアドレスに接続(UDPなのでアドレス関連付けのみ)*/ | |
| if (!BIO_connect(sock, BIO_ADDRINFO_address(ai), 0)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| /* Set to nonblocking mode */ | |
| /* 非ブロッキングモードに設定(QUICスタックが必要とする) */ | |
| if (!BIO_socket_nbio(sock, 1)) { | |
| BIO_closesocket(sock); | |
| sock = -1; | |
| continue; | |
| } | |
| break; /* 成功 */ | |
| } | |
| /* 接続成功時、ピアアドレスを複製 */ | |
| if (sock != -1) { | |
| *peer_addr = BIO_ADDR_dup(BIO_ADDRINFO_address(ai)); | |
| if (*peer_addr == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| } | |
| /* Free the address information resources we allocated earlier */ | |
| /* DNSルックアップ結果を解放 */ | |
| BIO_ADDRINFO_free(res); | |
| /* If sock is -1 then we've been unable to connect to the server */ | |
| if (sock == -1) | |
| return NULL; | |
| /* Create a BIO to wrap the socket */ | |
| /* データグラムBIOを作成 */ | |
| bio = BIO_new(BIO_s_datagram()); | |
| if (bio == NULL) { | |
| BIO_closesocket(sock); | |
| return NULL; | |
| } | |
| /* | |
| * Associate the newly created BIO with the underlying socket. By | |
| * passing BIO_CLOSE here the socket will be automatically closed when | |
| * the BIO is freed. Alternatively you can use BIO_NOCLOSE, in which | |
| * case you must close the socket explicitly when it is no longer | |
| * needed. | |
| */ | |
| /* BIOにソケットを関連付け(BIO_CLOSE: BIO解放時にソケットも閉じる) */ | |
| BIO_set_fd(bio, sock, BIO_CLOSE); | |
| return bio; | |
| } | |
| /* | |
| * write_a_request() - ストリームにHTTPリクエストを書き込む | |
| * | |
| * 指定されたストリームにHTTP/1.0 GETリクエストを送信する。 | |
| * マルチストリームでは、各ストリームに対して個別にデータを送受信できる。 | |
| * | |
| * @param stream データを送信するストリーム(SSLオブジェクト) | |
| * @param request_start リクエストの開始部分("GET /path HTTP/1.0\r\n...") | |
| * @param hostname Hostヘッダに含めるホスト名 | |
| * @return 成功時1、失敗時0 | |
| */ | |
| static int write_a_request(SSL *stream, const char *request_start, | |
| const char *hostname) | |
| { | |
| const char *request_end = "\r\n\r\n"; | |
| size_t written; | |
| /* リクエスト開始部分を送信 */ | |
| if (!SSL_write_ex(stream, request_start, strlen(request_start), | |
| &written)) | |
| return 0; | |
| /* ホスト名を送信 */ | |
| if (!SSL_write_ex(stream, hostname, strlen(hostname), &written)) | |
| return 0; | |
| /* リクエスト終了(空行)を送信 */ | |
| if (!SSL_write_ex(stream, request_end, strlen(request_end), &written)) | |
| return 0; | |
| return 1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - マルチストリームQUICクライアントのエントリポイント | |
| * ============================================================================ | |
| * | |
| * このデモでは3つのストリームを使用: | |
| * stream1: クライアント開始、双方向(リクエスト送信&レスポンス受信) | |
| * stream2: クライアント開始、単方向(リクエスト送信のみ) | |
| * stream3: サーバー開始(SSL_accept_streamで受け入れ) | |
| * | |
| * 【注意】 | |
| * HTTP/1.0 over QUICは非標準。デモ目的のみ。 | |
| */ | |
| /* | |
| * Simple application to send basic HTTP/1.0 requests to a server and print the | |
| * response on the screen. Note that HTTP/1.0 over QUIC is not a real protocol | |
| * and will not be supported by real world servers. This is for demonstration | |
| * purposes only. | |
| */ | |
| int main(int argc, char *argv[]) | |
| { | |
| SSL_CTX *ctx = NULL; | |
| SSL *ssl = NULL; /* QUIC接続オブジェクト */ | |
| SSL *stream1 = NULL, *stream2 = NULL, *stream3 = NULL; /* 個別ストリーム */ | |
| BIO *bio = NULL; | |
| int res = EXIT_FAILURE; | |
| int ret; | |
| unsigned char alpn[] = { 8, 'h', 't', 't', 'p', '/', '1', '.', '0' }; /* ALPN: http/1.0 */ | |
| const char *request1_start = | |
| "GET /request1.html HTTP/1.0\r\nConnection: close\r\nHost: "; | |
| const char *request2_start = | |
| "GET /request2.html HTTP/1.0\r\nConnection: close\r\nHost: "; | |
| size_t readbytes; | |
| char buf[160]; | |
| BIO_ADDR *peer_addr = NULL; | |
| char *hostname, *port; | |
| int argnext = 1; | |
| int ipv6 = 0; | |
| if (argc < 3) { | |
| printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| if (!strcmp(argv[argnext], "-6")) { | |
| if (argc < 4) { | |
| printf("Usage: quic-client-non-block [-6] hostname port\n"); | |
| goto end; | |
| } | |
| ipv6 = 1; | |
| argnext++; | |
| } | |
| hostname = argv[argnext++]; | |
| port = argv[argnext]; | |
| /* | |
| * ========================================================================= | |
| * 【SSL_CTX / SSL オブジェクト作成】 | |
| * ========================================================================= | |
| */ | |
| /* | |
| * Create an SSL_CTX which we can use to create SSL objects from. We | |
| * want an SSL_CTX for creating clients so we use | |
| * OSSL_QUIC_client_method() here. | |
| */ | |
| /* QUICクライアント用SSL_CTXを作成 */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_client_method()); | |
| if (ctx == NULL) { | |
| printf("Failed to create the SSL_CTX\n"); | |
| goto end; | |
| } | |
| /* | |
| * Configure the client to abort the handshake if certificate | |
| * verification fails. Virtually all clients should do this unless you | |
| * really know what you are doing. | |
| */ | |
| /* サーバー証明書の検証を有効化 */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); | |
| /* Use the default trusted certificate store */ | |
| /* システムのデフォルトCA証明書ストアを使用 */ | |
| if (!SSL_CTX_set_default_verify_paths(ctx)) { | |
| printf("Failed to set the default trusted certificate store\n"); | |
| goto end; | |
| } | |
| /* Create an SSL object to represent the TLS connection */ | |
| /* QUIC接続を表すSSLオブジェクトを作成 */ | |
| ssl = SSL_new(ctx); | |
| if (ssl == NULL) { | |
| printf("Failed to create the SSL object\n"); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【重要】デフォルトストリームモードの無効化 | |
| * ========================================================================= | |
| * | |
| * SSL_DEFAULT_STREAM_MODE_NONE を設定すると: | |
| * - SSLオブジェクト自体でのread/writeが無効化される | |
| * - 明示的にストリームを作成する必要がある | |
| * - マルチストリーム使用時に推奨される設定 | |
| * | |
| * デフォルトストリームモードが有効な場合: | |
| * - SSL_read/write は暗黙的にデフォルトストリームを使用 | |
| * - シングルストリームの場合は便利だが、マルチストリームでは混乱の元 | |
| */ | |
| /* | |
| * We will use multiple streams so we will disable the default stream mode. | |
| * This is not a requirement for using multiple streams but is recommended. | |
| */ | |
| if (!SSL_set_default_stream_mode(ssl, SSL_DEFAULT_STREAM_MODE_NONE)) { | |
| printf("Failed to disable the default stream mode\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【BIO作成・SNI・ALPN設定】(他のQUICデモと同様) | |
| * ------------------------------------------------------------------------- | |
| */ | |
| /* | |
| * Create the underlying transport socket/BIO and associate it with the | |
| * connection. | |
| */ | |
| /* UDPソケットを作成しBIOでラップ */ | |
| bio = create_socket_bio(hostname, port, ipv6 ? AF_INET6 : AF_INET, &peer_addr); | |
| if (bio == NULL) { | |
| printf("Failed to crete the BIO\n"); | |
| goto end; | |
| } | |
| SSL_set_bio(ssl, bio, bio); | |
| /* | |
| * Tell the server during the handshake which hostname we are attempting | |
| * to connect to in case the server supports multiple hosts. | |
| */ | |
| /* SNI (Server Name Indication) を設定 */ | |
| if (!SSL_set_tlsext_host_name(ssl, hostname)) { | |
| printf("Failed to set the SNI hostname\n"); | |
| goto end; | |
| } | |
| /* | |
| * Ensure we check during certificate verification that the server has | |
| * supplied a certificate for the hostname that we were expecting. | |
| * Virtually all clients should do this unless you really know what you | |
| * are doing. | |
| */ | |
| /* 証明書のホスト名検証を設定 */ | |
| if (!SSL_set1_host(ssl, hostname)) { | |
| printf("Failed to set the certificate verification hostname"); | |
| goto end; | |
| } | |
| /* SSL_set_alpn_protos returns 0 for success! */ | |
| /* ALPN設定(注意: 成功時に0を返す) */ | |
| if (SSL_set_alpn_protos(ssl, alpn, sizeof(alpn)) != 0) { | |
| printf("Failed to set the ALPN for the connection\n"); | |
| goto end; | |
| } | |
| /* Set the IP address of the remote peer */ | |
| /* QUICパケットの送信先アドレスを設定 */ | |
| if (!SSL_set1_initial_peer_addr(ssl, peer_addr)) { | |
| printf("Failed to set the initial peer address\n"); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【QUICハンドシェイク】 | |
| * ========================================================================= | |
| */ | |
| /* Do the handshake with the server */ | |
| if (SSL_connect(ssl) < 1) { | |
| printf("Failed to connect to the server\n"); | |
| /* | |
| * If the failure is due to a verification error we can get more | |
| * information about it from SSL_get_verify_result(). | |
| */ | |
| if (SSL_get_verify_result(ssl) != X509_V_OK) | |
| printf("Verify error: %s\n", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【クライアント開始ストリームの作成】 | |
| * ========================================================================= | |
| * | |
| * SSL_new_stream() でクライアント側からストリームを作成: | |
| * | |
| * stream1 = SSL_new_stream(ssl, 0) | |
| * → 双方向ストリーム(bidirectional) | |
| * → クライアント・サーバー両方がデータを送受信可能 | |
| * | |
| * stream2 = SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI) | |
| * → 単方向ストリーム(unidirectional) | |
| * → クライアントのみが送信可能、サーバーは受信のみ | |
| * | |
| * 各ストリームは独立したSSLオブジェクトとして扱われ、 | |
| * 個別にSSL_read_ex/SSL_write_exでデータを送受信できる。 | |
| */ | |
| /* | |
| * We create two new client initiated streams. The first will be | |
| * bi-directional, and the second will be uni-directional. | |
| */ | |
| stream1 = SSL_new_stream(ssl, 0); /* 双方向ストリーム */ | |
| stream2 = SSL_new_stream(ssl, SSL_STREAM_FLAG_UNI); /* 単方向ストリーム */ | |
| if (stream1 == NULL || stream2 == NULL) { | |
| printf("Failed to create streams\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【各ストリームにリクエストを送信】 | |
| * ------------------------------------------------------------------------- | |
| */ | |
| /* Write an HTTP GET request on each of our streams to the peer */ | |
| /* stream1 (双方向) にリクエスト1を送信 */ | |
| if (!write_a_request(stream1, request1_start, hostname)) { | |
| printf("Failed to write HTTP request on stream 1\n"); | |
| goto end; | |
| } | |
| /* stream2 (単方向) にリクエスト2を送信 */ | |
| if (!write_a_request(stream2, request2_start, hostname)) { | |
| printf("Failed to write HTTP request on stream 2\n"); | |
| goto end; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【stream1 (双方向) からレスポンスを読み取り】 | |
| * ========================================================================= | |
| * | |
| * このデモでは簡単のため、1つのストリームを読み終えてから次のストリームを読む。 | |
| * 実際には、複数ストリームのI/Oをインターリーブしたり、 | |
| * 別スレッドで個別に管理することも可能。 | |
| */ | |
| /* | |
| * In this demo we read all the data from one stream before reading all the | |
| * data from the next stream for simplicity. In practice there is no need to | |
| * do this. We can interleave IO on the different streams if we wish, or | |
| * manage the streams entirely separately on different threads. | |
| */ | |
| printf("Stream 1 data:\n"); | |
| /* | |
| * Get up to sizeof(buf) bytes of the response from stream 1 (which is a | |
| * bidirectional stream). We keep reading until the server closes the | |
| * connection. | |
| */ | |
| /* stream1からデータを読み取り(サーバーがFIN送信まで) */ | |
| while (SSL_read_ex(stream1, buf, sizeof(buf), &readbytes)) { | |
| /* | |
| * OpenSSL does not guarantee that the returned data is a string or | |
| * that it is NUL terminated so we use fwrite() to write the exact | |
| * number of bytes that we read. The data could be non-printable or | |
| * have NUL characters in the middle of it. For this simple example | |
| * we're going to print it to stdout anyway. | |
| */ | |
| fwrite(buf, 1, readbytes, stdout); | |
| } | |
| /* In case the response didn't finish with a newline we add one now */ | |
| printf("\n"); | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【ストリーム終了状態の確認】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_ERROR_ZERO_RETURN: 正常終了(サーバーがFIN送信) | |
| * SSL_ERROR_SSL: ストリームエラー | |
| * - SSL_STREAM_STATE_RESET_REMOTE: サーバーがストリームをリセット | |
| * - SSL_STREAM_STATE_CONN_CLOSED: 接続が閉じられた | |
| */ | |
| /* | |
| * Check whether we finished the while loop above normally or as the | |
| * result of an error. The 0 argument to SSL_get_error() is the return | |
| * code we received from the SSL_read_ex() call. It must be 0 in order | |
| * to get here. Normal completion is indicated by SSL_ERROR_ZERO_RETURN. In | |
| * QUIC terms this means that the peer has sent FIN on the stream to | |
| * indicate that no further data will be sent. | |
| */ | |
| switch (SSL_get_error(stream1, 0)) { | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* Normal completion of the stream */ | |
| /* 正常終了: サーバーがストリームにFINを送信 */ | |
| break; | |
| case SSL_ERROR_SSL: | |
| /* | |
| * Some stream fatal error occurred. This could be because of a stream | |
| * reset - or some failure occurred on the underlying connection. | |
| */ | |
| /* ストリームエラー: 詳細を確認 */ | |
| switch (SSL_get_stream_read_state(stream1)) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| printf("Stream reset occurred\n"); | |
| /* The stream has been reset but the connection is still healthy. */ | |
| /* ストリームはリセットされたが、接続は健全 */ | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| printf("Connection closed\n"); | |
| /* Connection is already closed. Skip SSL_shutdown() */ | |
| /* 接続が閉じられた。SSL_shutdown()をスキップ */ | |
| goto end; | |
| default: | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| break; | |
| default: | |
| /* Some other unexpected error occurred */ | |
| printf ("Failed reading remaining data\n"); | |
| break; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【サーバー開始ストリームの受け入れ】 | |
| * ========================================================================= | |
| * | |
| * SSL_accept_stream(): サーバーが開始したストリームを受け入れる | |
| * | |
| * このデモでは、stream2(単方向)で送ったリクエストに対して、 | |
| * サーバーが新しいストリームを開始してレスポンスを返すと仮定。 | |
| * (これは実際のプロトコルとしては不自然だが、デモ目的) | |
| * | |
| * フラグ: | |
| * 0: ブロッキングモード(ストリームが来るまで待機) | |
| * SSL_ACCEPT_STREAM_NO_BLOCK: 非ブロッキングモード | |
| */ | |
| /* | |
| * In our hypothetical HTTP/1.0 over QUIC protocol that we are using we | |
| * assume that the server will respond with a server initiated stream | |
| * containing the data requested in our uni-directional stream. This doesn't | |
| * really make sense to do in a real protocol, but its just for | |
| * demonstration purposes. | |
| * | |
| * We're using blocking mode so this will block until a stream becomes | |
| * available. We could override this behaviour if we wanted to by setting | |
| * the SSL_ACCEPT_STREAM_NO_BLOCK flag in the second argument below. | |
| */ | |
| stream3 = SSL_accept_stream(ssl, 0); /* サーバー開始ストリームを受け入れ */ | |
| if (stream3 == NULL) { | |
| printf("Failed to accept a new stream\n"); | |
| goto end; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【stream3からレスポンスを読み取り】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * 注意: stream2は単方向(クライアント→サーバー)なので、 | |
| * stream2からの読み取りは不可。 | |
| */ | |
| printf("Stream 3 data:\n"); | |
| /* | |
| * Read the data from stream 3 like we did for stream 1 above. Note that | |
| * stream 2 was uni-directional so there is no data to be read from that | |
| * one. | |
| */ | |
| /* stream3からデータを読み取り */ | |
| while (SSL_read_ex(stream3, buf, sizeof(buf), &readbytes)) | |
| fwrite(buf, 1, readbytes, stdout); | |
| printf("\n"); | |
| /* Check for errors on the stream */ | |
| /* stream3の終了状態を確認(stream1と同様) */ | |
| switch (SSL_get_error(stream3, 0)) { | |
| case SSL_ERROR_ZERO_RETURN: | |
| /* Normal completion of the stream */ | |
| /* 正常終了 */ | |
| break; | |
| case SSL_ERROR_SSL: | |
| switch (SSL_get_stream_read_state(stream3)) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| printf("Stream reset occurred\n"); | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| printf("Connection closed\n"); | |
| goto end; | |
| default: | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| break; | |
| default: | |
| printf ("Failed reading remaining data\n"); | |
| break; | |
| } | |
| /* | |
| * ========================================================================= | |
| * 【接続のシャットダウン】 | |
| * ========================================================================= | |
| * | |
| * SSL_shutdown()は接続全体をシャットダウンする。 | |
| * 個別のストリームは自動的に閉じられる。 | |
| * | |
| * 注意: SSL_shutdown()は接続オブジェクト(ssl)に対して呼ぶ。 | |
| * 個別のストリーム(stream1, stream2, stream3)には呼ばない。 | |
| */ | |
| /* | |
| * Repeatedly call SSL_shutdown() until the connection is fully | |
| * closed. | |
| */ | |
| do { | |
| ret = SSL_shutdown(ssl); | |
| if (ret < 0) { | |
| printf("Error shutting down: %d\n", ret); | |
| goto end; | |
| } | |
| } while (ret != 1); | |
| /* Success! 成功 */ | |
| res = EXIT_SUCCESS; | |
| end: | |
| /* | |
| * ========================================================================= | |
| * 【クリーンアップ】 | |
| * ========================================================================= | |
| * | |
| * 各ストリーム(stream1, stream2, stream3)も個別にSSL_free()が必要。 | |
| * 接続オブジェクト(ssl)のSSL_free()とは別。 | |
| * | |
| * 注意: BIOはSSL_set_bio()でsslに所有権移転済みなので、 | |
| * SSL_free(ssl)時に自動的に解放される。 | |
| */ | |
| /* | |
| * If something bad happened then we will dump the contents of the | |
| * OpenSSL error stack to stderr. There might be some useful diagnostic | |
| * information there. | |
| */ | |
| /* エラー時はOpenSSLエラースタックを出力 */ | |
| if (res == EXIT_FAILURE) | |
| ERR_print_errors_fp(stderr); | |
| /* | |
| * Free the resources we allocated. We do not free the BIO object here | |
| * because ownership of it was immediately transferred to the SSL object | |
| * via SSL_set_bio(). The BIO will be freed when we free the SSL object. | |
| */ | |
| SSL_free(ssl); /* 接続オブジェクト(BIOも自動解放) */ | |
| SSL_free(stream1); /* 双方向ストリーム */ | |
| SSL_free(stream2); /* 単方向ストリーム(クライアント開始) */ | |
| SSL_free(stream3); /* サーバー開始ストリーム */ | |
| SSL_CTX_free(ctx); | |
| BIO_ADDR_free(peer_addr); | |
| return res; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* POSIX機能を有効にする(clock_gettime, localtime_r用) */ | |
| #ifndef _WIN32 | |
| # define _POSIX_C_SOURCE 199309L | |
| #endif | |
| /* | |
| * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-server-block-with-debug.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-server-block-with-debug.c - デバッグ出力付きブロッキングモードQUICサーバー | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * quic-server-block.c に最大限のデバッグ出力を追加したバージョン。 | |
| * 全てのAPI呼び出し、データ内容、エラー詳細を出力する。 | |
| */ | |
| #include <string.h> | |
| #include <time.h> | |
| #include <stdarg.h> | |
| /* Include the appropriate header file for SOCK_STREAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <winsock2.h> | |
| # include <ws2tcpip.h> | |
| #else /* Linux/Unix */ | |
| # include <err.h> | |
| # include <sys/socket.h> | |
| # include <sys/select.h> | |
| # include <netinet/in.h> | |
| # include <arpa/inet.h> | |
| # include <unistd.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| #include <openssl/quic.h> | |
| /* ============================================================================ | |
| * デバッグユーティリティ | |
| * ============================================================================ */ | |
| /* | |
| * DEBUG_LOG() - タイムスタンプ付きデバッグログ出力マクロ | |
| * | |
| * フォーマット: [HH:MM:SS.mmm] [関数名] メッセージ | |
| */ | |
| #define DEBUG_LOG(fmt, ...) do { \ | |
| struct timespec ts; \ | |
| struct tm tm_info; \ | |
| clock_gettime(CLOCK_REALTIME, &ts); \ | |
| localtime_r(&ts.tv_sec, &tm_info); \ | |
| fprintf(stderr, "[%02d:%02d:%02d.%03ld] [%s] " fmt "\n", \ | |
| tm_info.tm_hour, tm_info.tm_min, tm_info.tm_sec, \ | |
| ts.tv_nsec / 1000000, __func__, ##__VA_ARGS__); \ | |
| fflush(stderr); \ | |
| } while(0) | |
| /* | |
| * DEBUG_LOG_ENTER() - 関数開始ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_ENTER() DEBUG_LOG(">>> %s() 開始", __func__) | |
| /* | |
| * DEBUG_LOG_EXIT() - 関数終了ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_EXIT() DEBUG_LOG("<<< %s() 終了", __func__) | |
| /* | |
| * DEBUG_LOG_EXIT_WITH_RESULT() - 結果付き関数終了ログ(関数名を表示) | |
| */ | |
| #define DEBUG_LOG_EXIT_WITH_RESULT(result) DEBUG_LOG("<<< %s() 終了 (result=%d)", __func__, (int)(result)) | |
| /* | |
| * debug_hexdump() - データの16進ダンプを出力 | |
| * | |
| * @param prefix 出力行の先頭に付けるプレフィックス | |
| * @param data ダンプするデータ | |
| * @param len データの長さ | |
| */ | |
| static void debug_hexdump(const char *prefix, const unsigned char *data, size_t len) | |
| { | |
| size_t i, j; | |
| fprintf(stderr, "%s (%zu bytes):\n", prefix, len); | |
| for (i = 0; i < len; i += 16) { | |
| /* オフセット */ | |
| fprintf(stderr, " %04zx: ", i); | |
| /* 16進表示 */ | |
| for (j = 0; j < 16; j++) { | |
| if (i + j < len) | |
| fprintf(stderr, "%02x ", data[i + j]); | |
| else | |
| fprintf(stderr, " "); | |
| if (j == 7) | |
| fprintf(stderr, " "); | |
| } | |
| /* ASCII表示 */ | |
| fprintf(stderr, " |"); | |
| for (j = 0; j < 16 && i + j < len; j++) { | |
| unsigned char c = data[i + j]; | |
| fprintf(stderr, "%c", (c >= 32 && c < 127) ? c : '.'); | |
| } | |
| fprintf(stderr, "|\n"); | |
| } | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_ssl_error() - OpenSSL エラースタックの詳細を出力 | |
| * | |
| * @param context エラーが発生したコンテキストの説明 | |
| */ | |
| static void debug_print_ssl_error(const char *context) | |
| { | |
| unsigned long err; | |
| char buf[256]; | |
| DEBUG_LOG("=== SSL エラー詳細 [%s] ===", context); | |
| while ((err = ERR_get_error()) != 0) { | |
| ERR_error_string_n(err, buf, sizeof(buf)); | |
| fprintf(stderr, " エラーコード: 0x%lx\n", err); | |
| fprintf(stderr, " ライブラリ: %s\n", ERR_lib_error_string(err)); | |
| fprintf(stderr, " 関数: %s\n", ERR_func_error_string(err)); | |
| fprintf(stderr, " 理由: %s\n", ERR_reason_error_string(err)); | |
| fprintf(stderr, " 詳細: %s\n", buf); | |
| fprintf(stderr, " ---\n"); | |
| } | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_ssl_state() - SSL接続の状態を出力 | |
| * | |
| * @param ssl SSLオブジェクト | |
| */ | |
| static void debug_print_ssl_state(const SSL *ssl) | |
| { | |
| if (ssl == NULL) { | |
| DEBUG_LOG("SSL オブジェクト: NULL"); | |
| return; | |
| } | |
| DEBUG_LOG("=== SSL 状態 ==="); | |
| fprintf(stderr, " 状態文字列: %s\n", SSL_state_string_long(ssl)); | |
| fprintf(stderr, " バージョン: %s\n", SSL_get_version(ssl)); | |
| /* ALPN */ | |
| const unsigned char *alpn_data; | |
| unsigned int alpn_len; | |
| SSL_get0_alpn_selected(ssl, &alpn_data, &alpn_len); | |
| if (alpn_data != NULL && alpn_len > 0) { | |
| fprintf(stderr, " 選択されたALPN: %.*s\n", alpn_len, alpn_data); | |
| } else { | |
| fprintf(stderr, " 選択されたALPN: (なし)\n"); | |
| } | |
| /* 暗号スイート */ | |
| const SSL_CIPHER *cipher = SSL_get_current_cipher(ssl); | |
| if (cipher != NULL) { | |
| fprintf(stderr, " 暗号スイート: %s\n", SSL_CIPHER_get_name(cipher)); | |
| fprintf(stderr, " 暗号強度: %d bits\n", SSL_CIPHER_get_bits(cipher, NULL)); | |
| } | |
| fflush(stderr); | |
| } | |
| /* | |
| * debug_print_connection_info() - 接続情報を出力 | |
| * | |
| * @param ssl 接続のSSLオブジェクト | |
| */ | |
| static void debug_print_connection_info(const SSL *ssl) | |
| { | |
| BIO *bio; | |
| int fd; | |
| struct sockaddr_storage peer_addr; | |
| socklen_t peer_len = sizeof(peer_addr); | |
| char addr_str[INET6_ADDRSTRLEN]; | |
| uint16_t port = 0; | |
| DEBUG_LOG("=== 接続情報 ==="); | |
| if (ssl == NULL) { | |
| fprintf(stderr, " SSL: NULL\n"); | |
| fflush(stderr); | |
| return; | |
| } | |
| /* BIOからFDを取得してピアアドレスを取得 */ | |
| bio = SSL_get_rbio(ssl); | |
| if (bio != NULL && BIO_get_fd(bio, &fd) > 0) { | |
| if (getpeername(fd, (struct sockaddr *)&peer_addr, &peer_len) == 0) { | |
| if (peer_addr.ss_family == AF_INET) { | |
| struct sockaddr_in *s = (struct sockaddr_in *)&peer_addr; | |
| inet_ntop(AF_INET, &s->sin_addr, addr_str, sizeof(addr_str)); | |
| port = ntohs(s->sin_port); | |
| } else if (peer_addr.ss_family == AF_INET6) { | |
| struct sockaddr_in6 *s = (struct sockaddr_in6 *)&peer_addr; | |
| inet_ntop(AF_INET6, &s->sin6_addr, addr_str, sizeof(addr_str)); | |
| port = ntohs(s->sin6_port); | |
| } | |
| fprintf(stderr, " ピアアドレス: %s:%u\n", addr_str, port); | |
| } else { | |
| fprintf(stderr, " ピアアドレス: (取得失敗: %s)\n", strerror(errno)); | |
| } | |
| fprintf(stderr, " ファイルディスクリプタ: %d\n", fd); | |
| } else { | |
| fprintf(stderr, " BIO/FD: (取得失敗)\n"); | |
| } | |
| fflush(stderr); | |
| } | |
| #ifdef _WIN32 | |
| static const char *progname; | |
| static void vwarnx(const char *fmt, va_list ap) | |
| { | |
| if (progname != NULL) | |
| fprintf(stderr, "%s: ", progname); | |
| vfprintf(stderr, fmt, ap); | |
| putc('\n', stderr); | |
| } | |
| static void errx(int status, const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| exit(status); | |
| } | |
| static void warnx(const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| } | |
| #endif | |
| /* | |
| * ============================================================================ | |
| * ALPN (Application-Layer Protocol Negotiation) 設定 | |
| * ============================================================================ | |
| */ | |
| static const unsigned char alpn_ossltest[] = { | |
| 8, 'h', 't', 't', 'p', '/', '1', '.', '0', /* http/1.0 */ | |
| 10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p', /* hq-interop */ | |
| }; | |
| /* | |
| * select_alpn() - ALPN選択コールバック(デバッグ出力付き) | |
| */ | |
| static int select_alpn(SSL *ssl, const unsigned char **out, | |
| unsigned char *out_len, const unsigned char *in, | |
| unsigned int in_len, void *arg) | |
| { | |
| int result; | |
| DEBUG_LOG_ENTER(); | |
| DEBUG_LOG("SSLオブジェクト: %p", (void *)ssl); | |
| /* クライアントが提示したALPNリストをダンプ */ | |
| debug_hexdump("クライアントALPNリスト", in, in_len); | |
| /* サーバーがサポートするALPNリストをダンプ */ | |
| debug_hexdump("サーバーALPNリスト", alpn_ossltest, sizeof(alpn_ossltest)); | |
| /* ALPN交渉実行 */ | |
| DEBUG_LOG("SSL_select_next_proto() を呼び出し中..."); | |
| result = SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, | |
| sizeof(alpn_ossltest), in, in_len); | |
| DEBUG_LOG("SSL_select_next_proto() 戻り値: %d", result); | |
| if (result == OPENSSL_NPN_NEGOTIATED) { | |
| DEBUG_LOG("ALPN交渉成功!"); | |
| DEBUG_LOG("選択されたプロトコル: %.*s (長さ: %u)", *out_len, *out, *out_len); | |
| DEBUG_LOG_EXIT_WITH_RESULT(SSL_TLSEXT_ERR_OK); | |
| return SSL_TLSEXT_ERR_OK; | |
| } | |
| DEBUG_LOG("ALPN交渉失敗: 共通のプロトコルが見つかりません"); | |
| debug_print_ssl_error("ALPN交渉"); | |
| DEBUG_LOG_EXIT_WITH_RESULT(SSL_TLSEXT_ERR_ALERT_FATAL); | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_ctx() - SSL_CTXの作成(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) | |
| { | |
| SSL_CTX *ctx; | |
| int ret; | |
| DEBUG_LOG_ENTER(); | |
| DEBUG_LOG("証明書パス: %s", cert_path); | |
| DEBUG_LOG("秘密鍵パス: %s", key_path); | |
| /* QUICサーバー用SSL_CTXを作成 */ | |
| DEBUG_LOG("SSL_CTX_new(OSSL_QUIC_server_method()) を呼び出し中..."); | |
| ctx = SSL_CTX_new(OSSL_QUIC_server_method()); | |
| if (ctx == NULL) { | |
| DEBUG_LOG("SSL_CTX_new() 失敗!"); | |
| debug_print_ssl_error("SSL_CTX_new"); | |
| goto err; | |
| } | |
| DEBUG_LOG("SSL_CTX_new() 成功: ctx=%p", (void *)ctx); | |
| /* 証明書チェーンのロード */ | |
| DEBUG_LOG("SSL_CTX_use_certificate_chain_file(%s) を呼び出し中...", cert_path); | |
| ret = SSL_CTX_use_certificate_chain_file(ctx, cert_path); | |
| DEBUG_LOG("SSL_CTX_use_certificate_chain_file() 戻り値: %d", ret); | |
| if (ret <= 0) { | |
| DEBUG_LOG("証明書ロード失敗!"); | |
| debug_print_ssl_error("SSL_CTX_use_certificate_chain_file"); | |
| fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); | |
| goto err; | |
| } | |
| DEBUG_LOG("証明書ロード成功"); | |
| /* 秘密鍵のロード */ | |
| DEBUG_LOG("SSL_CTX_use_PrivateKey_file(%s, SSL_FILETYPE_PEM) を呼び出し中...", key_path); | |
| ret = SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM); | |
| DEBUG_LOG("SSL_CTX_use_PrivateKey_file() 戻り値: %d", ret); | |
| if (ret <= 0) { | |
| DEBUG_LOG("秘密鍵ロード失敗!"); | |
| debug_print_ssl_error("SSL_CTX_use_PrivateKey_file"); | |
| fprintf(stderr, "couldn't load key file: %s\n", key_path); | |
| goto err; | |
| } | |
| DEBUG_LOG("秘密鍵ロード成功"); | |
| /* 証明書と秘密鍵の整合性チェック */ | |
| DEBUG_LOG("SSL_CTX_check_private_key() を呼び出し中..."); | |
| ret = SSL_CTX_check_private_key(ctx); | |
| DEBUG_LOG("SSL_CTX_check_private_key() 戻り値: %d", ret); | |
| if (ret != 1) { | |
| DEBUG_LOG("証明書と秘密鍵が一致しません!"); | |
| debug_print_ssl_error("SSL_CTX_check_private_key"); | |
| } else { | |
| DEBUG_LOG("証明書と秘密鍵の整合性OK"); | |
| } | |
| /* クライアント証明書検証の設定 */ | |
| DEBUG_LOG("SSL_CTX_set_verify(SSL_VERIFY_NONE) を呼び出し中..."); | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); | |
| DEBUG_LOG("クライアント証明書検証: 無効 (SSL_VERIFY_NONE)"); | |
| /* ALPNコールバック設定 */ | |
| DEBUG_LOG("SSL_CTX_set_alpn_select_cb() を呼び出し中..."); | |
| SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); | |
| DEBUG_LOG("ALPNコールバック設定完了"); | |
| DEBUG_LOG_EXIT(); | |
| DEBUG_LOG("SSL_CTX作成成功: %p", (void *)ctx); | |
| return ctx; | |
| err: | |
| DEBUG_LOG("エラー発生、SSL_CTX_free() を呼び出し中..."); | |
| SSL_CTX_free(ctx); | |
| DEBUG_LOG_EXIT_WITH_RESULT(0); | |
| return NULL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_socket() - UDPソケットの作成(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| static int create_socket(uint16_t port) | |
| { | |
| int fd; | |
| struct sockaddr_in sa = {0}; | |
| int ret; | |
| char addr_str[INET_ADDRSTRLEN]; | |
| DEBUG_LOG_ENTER(); | |
| DEBUG_LOG("ポート: %u", port); | |
| /* UDPソケットを作成 */ | |
| DEBUG_LOG("socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) を呼び出し中..."); | |
| fd = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); | |
| DEBUG_LOG("socket() 戻り値: %d", fd); | |
| if (fd < 0) { | |
| DEBUG_LOG("ソケット作成失敗: %s", strerror(errno)); | |
| fprintf(stderr, "cannot create socket"); | |
| goto err; | |
| } | |
| DEBUG_LOG("ソケット作成成功: fd=%d", fd); | |
| /* アドレス設定 */ | |
| sa.sin_family = AF_INET; | |
| sa.sin_port = htons(port); | |
| sa.sin_addr.s_addr = INADDR_ANY; | |
| inet_ntop(AF_INET, &sa.sin_addr, addr_str, sizeof(addr_str)); | |
| DEBUG_LOG("バインドアドレス: %s:%u", addr_str, port); | |
| /* ソケットをポートにバインド */ | |
| DEBUG_LOG("bind() を呼び出し中..."); | |
| ret = bind(fd, (const struct sockaddr *)&sa, sizeof(sa)); | |
| DEBUG_LOG("bind() 戻り値: %d", ret); | |
| if (ret < 0) { | |
| DEBUG_LOG("bind失敗: %s", strerror(errno)); | |
| fprintf(stderr, "cannot bind to %u\n", port); | |
| BIO_closesocket(fd); | |
| goto err; | |
| } | |
| DEBUG_LOG("bind成功"); | |
| DEBUG_LOG_EXIT_WITH_RESULT(fd); | |
| return fd; | |
| err: | |
| DEBUG_LOG("エラー発生"); | |
| BIO_closesocket(fd); | |
| DEBUG_LOG_EXIT_WITH_RESULT(-1); | |
| return -1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * run_quic_server() - QUICサーバーのメインループ(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| static int run_quic_server(SSL_CTX *ctx, int fd) | |
| { | |
| int ok = 0; | |
| SSL *listener, *conn; | |
| unsigned char buf[8192]; | |
| size_t nread; | |
| size_t nwritten; | |
| int ret; | |
| unsigned long conn_count = 0; | |
| unsigned long read_count, write_count; | |
| DEBUG_LOG_ENTER(); | |
| DEBUG_LOG("SSL_CTX: %p", (void *)ctx); | |
| DEBUG_LOG("ソケットFD: %d", fd); | |
| /* リスナーの作成 */ | |
| DEBUG_LOG("SSL_new_listener(ctx, 0) を呼び出し中..."); | |
| listener = SSL_new_listener(ctx, 0); | |
| DEBUG_LOG("SSL_new_listener() 戻り値: %p", (void *)listener); | |
| if (listener == NULL) { | |
| DEBUG_LOG("リスナー作成失敗!"); | |
| debug_print_ssl_error("SSL_new_listener"); | |
| goto err; | |
| } | |
| DEBUG_LOG("リスナー作成成功"); | |
| /* リスナーにUDPソケットを関連付け */ | |
| DEBUG_LOG("SSL_set_fd(listener, %d) を呼び出し中...", fd); | |
| ret = SSL_set_fd(listener, fd); | |
| DEBUG_LOG("SSL_set_fd() 戻り値: %d", ret); | |
| if (!ret) { | |
| DEBUG_LOG("SSL_set_fd失敗!"); | |
| debug_print_ssl_error("SSL_set_fd"); | |
| goto err; | |
| } | |
| DEBUG_LOG("SSL_set_fd成功"); | |
| /* リッスン開始 */ | |
| DEBUG_LOG("SSL_listen(listener) を呼び出し中..."); | |
| ret = SSL_listen(listener); | |
| DEBUG_LOG("SSL_listen() 戻り値: %d", ret); | |
| if (!ret) { | |
| DEBUG_LOG("SSL_listen失敗!"); | |
| debug_print_ssl_error("SSL_listen"); | |
| goto err; | |
| } | |
| DEBUG_LOG("リッスン開始成功"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("接続受け入れループ開始"); | |
| DEBUG_LOG("========================================"); | |
| /* 接続受け入れループ */ | |
| for (;;) { | |
| conn_count++; | |
| read_count = 0; | |
| write_count = 0; | |
| DEBUG_LOG("----------------------------------------"); | |
| DEBUG_LOG("接続 #%lu を待機中...", conn_count); | |
| DEBUG_LOG("----------------------------------------"); | |
| /* エラースタックをクリア */ | |
| DEBUG_LOG("ERR_clear_error() を呼び出し中..."); | |
| ERR_clear_error(); | |
| /* 接続受け入れ */ | |
| printf("Waiting for connection\n"); | |
| DEBUG_LOG("SSL_accept_connection(listener, 0) を呼び出し中..."); | |
| conn = SSL_accept_connection(listener, 0); | |
| DEBUG_LOG("SSL_accept_connection() 戻り値: %p", (void *)conn); | |
| if (conn == NULL) { | |
| DEBUG_LOG("接続受け入れ失敗!"); | |
| debug_print_ssl_error("SSL_accept_connection"); | |
| fprintf(stderr, "error while accepting connection\n"); | |
| goto err; | |
| } | |
| printf("Accepted new connection\n"); | |
| DEBUG_LOG("新しい接続を受け入れました: conn=%p", (void *)conn); | |
| /* 接続情報を出力 */ | |
| debug_print_ssl_state(conn); | |
| debug_print_connection_info(conn); | |
| /* エコー処理ループ */ | |
| DEBUG_LOG("エコー処理ループ開始"); | |
| while (1) { | |
| read_count++; | |
| DEBUG_LOG("[Read #%lu] SSL_read_ex() を呼び出し中... (バッファサイズ: %zu)", | |
| read_count, sizeof(buf)); | |
| ret = SSL_read_ex(conn, buf, sizeof(buf), &nread); | |
| DEBUG_LOG("[Read #%lu] SSL_read_ex() 戻り値: %d, 読み取りバイト数: %zu", | |
| read_count, ret, nread); | |
| if (ret <= 0) { | |
| int ssl_error = SSL_get_error(conn, ret); | |
| DEBUG_LOG("[Read #%lu] SSL_get_error() = %d", read_count, ssl_error); | |
| switch (ssl_error) { | |
| case SSL_ERROR_ZERO_RETURN: | |
| DEBUG_LOG(" -> SSL_ERROR_ZERO_RETURN: ピアがストリームを閉じました"); | |
| break; | |
| case SSL_ERROR_WANT_READ: | |
| DEBUG_LOG(" -> SSL_ERROR_WANT_READ: 読み取り待ち"); | |
| break; | |
| case SSL_ERROR_WANT_WRITE: | |
| DEBUG_LOG(" -> SSL_ERROR_WANT_WRITE: 書き込み待ち"); | |
| break; | |
| case SSL_ERROR_SYSCALL: | |
| DEBUG_LOG(" -> SSL_ERROR_SYSCALL: システムコールエラー (errno=%d: %s)", | |
| errno, strerror(errno)); | |
| break; | |
| case SSL_ERROR_SSL: | |
| DEBUG_LOG(" -> SSL_ERROR_SSL: SSLプロトコルエラー"); | |
| debug_print_ssl_error("SSL_read_ex"); | |
| break; | |
| default: | |
| DEBUG_LOG(" -> 不明なエラー: %d", ssl_error); | |
| } | |
| break; | |
| } | |
| DEBUG_LOG("[Read #%lu] データ受信成功: %zu バイト", read_count, nread); | |
| debug_hexdump("受信データ", buf, nread); | |
| /* エコー(書き戻し) */ | |
| write_count++; | |
| DEBUG_LOG("[Write #%lu] SSL_write_ex() を呼び出し中... (書き込みサイズ: %zu)", | |
| write_count, nread); | |
| ret = SSL_write_ex(conn, buf, nread, &nwritten); | |
| DEBUG_LOG("[Write #%lu] SSL_write_ex() 戻り値: %d, 書き込みバイト数: %zu", | |
| write_count, ret, nwritten); | |
| if (ret <= 0 || nwritten != nread) { | |
| int ssl_error = SSL_get_error(conn, ret); | |
| DEBUG_LOG("[Write #%lu] 書き込みエラー!", write_count); | |
| DEBUG_LOG(" SSL_get_error() = %d", ssl_error); | |
| debug_print_ssl_error("SSL_write_ex"); | |
| fprintf(stderr, "Error echoing client input"); | |
| break; | |
| } | |
| DEBUG_LOG("[Write #%lu] エコー成功: %zu バイト", write_count, nwritten); | |
| } | |
| DEBUG_LOG("エコー処理ループ終了 (読み取り: %lu回, 書き込み: %lu回)", | |
| read_count, write_count); | |
| /* ストリーム終了 */ | |
| DEBUG_LOG("SSL_stream_conclude(conn, 0) を呼び出し中..."); | |
| ret = SSL_stream_conclude(conn, 0); | |
| DEBUG_LOG("SSL_stream_conclude() 戻り値: %d", ret); | |
| if (ret != 1) { | |
| DEBUG_LOG("SSL_stream_conclude失敗!"); | |
| debug_print_ssl_error("SSL_stream_conclude"); | |
| fprintf(stderr, "Unable to conclude stream\n"); | |
| SSL_free(conn); | |
| goto err; | |
| } | |
| DEBUG_LOG("ストリーム終了成功"); | |
| /* 接続シャットダウン */ | |
| DEBUG_LOG("SSL_shutdown() を呼び出し中..."); | |
| while (SSL_shutdown(conn) != 1) | |
| continue; | |
| DEBUG_LOG("SSL_shutdown() 完了"); | |
| /* 接続解放 */ | |
| DEBUG_LOG("SSL_free(conn) を呼び出し中..."); | |
| SSL_free(conn); | |
| DEBUG_LOG("接続解放完了"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("接続 #%lu 処理完了", conn_count); | |
| DEBUG_LOG("========================================\n"); | |
| } | |
| err: | |
| DEBUG_LOG("エラー発生、クリーンアップ中..."); | |
| DEBUG_LOG("SSL_free(listener) を呼び出し中..."); | |
| SSL_free(listener); | |
| DEBUG_LOG_EXIT_WITH_RESULT(ok); | |
| return ok; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - QUICサーバーのエントリポイント(デバッグ出力付き) | |
| * ============================================================================ | |
| */ | |
| int main(int argc, char *argv[]) | |
| { | |
| int res = EXIT_FAILURE; | |
| SSL_CTX *ctx = NULL; | |
| int fd; | |
| unsigned long port; | |
| int i; | |
| #ifdef _WIN32 | |
| static const char *progname; | |
| progname = argv[0]; | |
| #endif | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("QUIC サーバー (デバッグモード) 起動"); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("OpenSSL バージョン: %s", OpenSSL_version(OPENSSL_VERSION)); | |
| DEBUG_LOG("引数の数: %d", argc); | |
| for (i = 0; i < argc; i++) { | |
| DEBUG_LOG(" argv[%d] = \"%s\"", i, argv[i]); | |
| } | |
| /* 引数チェック */ | |
| if (argc != 4) { | |
| DEBUG_LOG("引数エラー: 4個の引数が必要です (現在: %d)", argc); | |
| errx(res, "usage: %s <port> <server.crt> <server.key>", argv[0]); | |
| } | |
| DEBUG_LOG("----------------------------------------"); | |
| DEBUG_LOG("設定:"); | |
| DEBUG_LOG(" ポート: %s", argv[1]); | |
| DEBUG_LOG(" 証明書: %s", argv[2]); | |
| DEBUG_LOG(" 秘密鍵: %s", argv[3]); | |
| DEBUG_LOG("----------------------------------------"); | |
| /* SSL_CTX作成 */ | |
| DEBUG_LOG("SSL_CTX を作成中..."); | |
| ctx = create_ctx(argv[2], argv[3]); | |
| if (ctx == NULL) { | |
| DEBUG_LOG("SSL_CTX 作成失敗!"); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create context"); | |
| } | |
| DEBUG_LOG("SSL_CTX 作成成功: %p", (void *)ctx); | |
| /* ポート番号解析 */ | |
| DEBUG_LOG("ポート番号を解析中: \"%s\"", argv[1]); | |
| port = strtoul(argv[1], NULL, 0); | |
| DEBUG_LOG("解析結果: %lu", port); | |
| if (port == 0 || port > UINT16_MAX) { | |
| DEBUG_LOG("ポート番号エラー: 0 < port <= %u である必要があります", UINT16_MAX); | |
| SSL_CTX_free(ctx); | |
| errx(res, "Failed to parse port number"); | |
| } | |
| DEBUG_LOG("ポート番号OK: %lu", port); | |
| /* UDPソケット作成 */ | |
| DEBUG_LOG("UDPソケットを作成中..."); | |
| fd = create_socket((uint16_t)port); | |
| if (fd < 0) { | |
| DEBUG_LOG("ソケット作成失敗!"); | |
| SSL_CTX_free(ctx); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create socket"); | |
| } | |
| DEBUG_LOG("ソケット作成成功: fd=%d", fd); | |
| DEBUG_LOG("========================================"); | |
| DEBUG_LOG("サーバー起動準備完了"); | |
| DEBUG_LOG(" リッスンポート: %lu", port); | |
| DEBUG_LOG(" ソケットFD: %d", fd); | |
| DEBUG_LOG("========================================\n"); | |
| /* QUICサーバーのメインループを実行 */ | |
| DEBUG_LOG("run_quic_server() を呼び出し中..."); | |
| if (!run_quic_server(ctx, fd)) { | |
| DEBUG_LOG("run_quic_server() がエラーで終了"); | |
| SSL_CTX_free(ctx); | |
| BIO_closesocket(fd); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Error in QUIC server loop"); | |
| } | |
| /* クリーンアップ(通常ここには到達しない) */ | |
| DEBUG_LOG("クリーンアップ中..."); | |
| DEBUG_LOG("SSL_CTX_free() を呼び出し中..."); | |
| SSL_CTX_free(ctx); | |
| DEBUG_LOG("BIO_closesocket() を呼び出し中..."); | |
| BIO_closesocket(fd); | |
| DEBUG_LOG("クリーンアップ完了"); | |
| res = EXIT_SUCCESS; | |
| DEBUG_LOG("プログラム終了 (exit code: %d)", res); | |
| return res; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-server-block.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-server-block.c - ブロッキングモードQUICサーバーデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICを使用したシンプルなエコーサーバーのデモ。 | |
| * クライアントから受信したデータをそのまま返す(エコー)。 | |
| * ブロッキングモードで動作し、各操作は完了するまで待機する。 | |
| * | |
| * 【アーキテクチャ図】 | |
| * | |
| * ┌─────────────────────────────────────────────────────────────┐ | |
| * │ QUICサーバー │ | |
| * │ │ | |
| * │ ┌─────────────┐ │ | |
| * │ │ SSL_CTX │ サーバー設定(証明書、秘密鍵、ALPN) │ | |
| * │ └──────┬──────┘ │ | |
| * │ │ │ | |
| * │ ▼ │ | |
| * │ ┌─────────────┐ │ | |
| * │ │ Listener │ SSL_new_listener() で作成 │ | |
| * │ │ (SSL) │ 接続要求を待ち受ける │ | |
| * │ └──────┬──────┘ │ | |
| * │ │ SSL_accept_connection() │ | |
| * │ ▼ │ | |
| * │ ┌─────────────┐ │ | |
| * │ │ Connection │ 個別のQUIC接続 │ | |
| * │ │ (SSL) │ SSL_read_ex/SSL_write_ex でデータ送受信 │ | |
| * │ └─────────────┘ │ | |
| * │ │ | |
| * └─────────────────────────────────────────────────────────────┘ | |
| * │ | |
| * │ UDP | |
| * ▼ | |
| * ┌───────────┐ | |
| * │ Client │ | |
| * └───────────┘ | |
| * | |
| * 【処理フロー】 | |
| * | |
| * main() | |
| * │ | |
| * ├── create_ctx() ─── SSL_CTX作成(証明書・秘密鍵ロード) | |
| * │ | |
| * ├── create_socket() ─── UDPソケット作成・bind | |
| * │ | |
| * └── run_quic_server() | |
| * │ | |
| * ├── SSL_new_listener() ─── リスナー作成 | |
| * ├── SSL_set_fd() ─── ソケット関連付け | |
| * ├── SSL_listen() ─── リッスン開始 | |
| * │ | |
| * └── for(;;) ─── 接続受け入れループ | |
| * │ | |
| * ├── SSL_accept_connection() ─── 新規接続受け入れ | |
| * │ | |
| * ├── SSL_read_ex() / SSL_write_ex() ─── エコー処理 | |
| * │ | |
| * ├── SSL_stream_conclude() ─── ストリーム終了 | |
| * │ | |
| * ├── SSL_shutdown() ─── 接続シャットダウン | |
| * │ | |
| * └── SSL_free() ─── 接続解放 | |
| * | |
| * 【QUICサーバーの主要API】 | |
| * | |
| * - SSL_new_listener(): QUICリスナーを作成 | |
| * - SSL_listen(): リッスン開始 | |
| * - SSL_accept_connection(): 新規接続を受け入れ | |
| * - SSL_stream_conclude(): ストリーム送信終了(FIN送信) | |
| * | |
| * 【使用方法】 | |
| * ./quic-server-block <port> <server.crt> <server.key> | |
| * | |
| * 【関連ドキュメント】 | |
| * doc/man7/ossl-guide-quic-server-block.pod | |
| */ | |
| /* | |
| * NB: Changes to this file should also be reflected in | |
| * doc/man7/ossl-guide-quic-server-block.pod | |
| */ | |
| #include <string.h> | |
| /* Include the appropriate header file for SOCK_STREAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <stdarg.h> | |
| # include <winsock2.h> | |
| #else /* Linux/Unix */ | |
| # include <err.h> | |
| # include <sys/socket.h> | |
| # include <sys/select.h> | |
| # include <netinet/in.h> | |
| # include <unistd.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| #include <openssl/quic.h> | |
| #ifdef _WIN32 | |
| static const char *progname; | |
| static void vwarnx(const char *fmt, va_list ap) | |
| { | |
| if (progname != NULL) | |
| fprintf(stderr, "%s: ", progname); | |
| vfprintf(stderr, fmt, ap); | |
| putc('\n', stderr); | |
| } | |
| static void errx(int status, const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| exit(status); | |
| } | |
| static void warnx(const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| } | |
| #endif | |
| /* | |
| * ============================================================================ | |
| * ALPN (Application-Layer Protocol Negotiation) 設定 | |
| * ============================================================================ | |
| * | |
| * サーバーがサポートするALPNプロトコルのリスト。 | |
| * フォーマット: { 長さ, プロトコル名... } の繰り返し | |
| * | |
| * このサーバーは以下のプロトコルを受け入れる: | |
| * - "http/1.0" (8バイト): HTTP/1.0 over QUIC(デモ用) | |
| * - "hq-interop" (10バイト): QUIC相互運用性テスト用プロトコル | |
| */ | |
| /* | |
| * ALPN strings for TLS handshake. Only 'http/1.0' and 'hq-interop' | |
| * are accepted. | |
| */ | |
| static const unsigned char alpn_ossltest[] = { | |
| 8, 'h', 't', 't', 'p', '/', '1', '.', '0', /* http/1.0 */ | |
| 10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p', /* hq-interop */ | |
| }; | |
| /* | |
| * select_alpn() - ALPN選択コールバック | |
| * | |
| * TLSハンドシェイク中に呼び出され、クライアントが提示したALPNリストと | |
| * サーバーがサポートするプロトコルを照合する。 | |
| * | |
| * @param ssl SSLオブジェクト | |
| * @param out [出力] 選択されたプロトコルへのポインタ | |
| * @param out_len [出力] 選択されたプロトコルの長さ | |
| * @param in クライアントが提示したALPNリスト | |
| * @param in_len クライアントALPNリストの長さ | |
| * @param arg ユーザーデータ(未使用) | |
| * @return SSL_TLSEXT_ERR_OK: 成功, SSL_TLSEXT_ERR_ALERT_FATAL: 失敗 | |
| */ | |
| /* | |
| * This callback validates and negotiates the desired ALPN on the server side. | |
| */ | |
| static int select_alpn(SSL *ssl, const unsigned char **out, | |
| unsigned char *out_len, const unsigned char *in, | |
| unsigned int in_len, void *arg) | |
| { | |
| /* | |
| * SSL_select_next_proto(): サーバーとクライアントのALPNリストを照合 | |
| * OPENSSL_NPN_NEGOTIATED: 共通のプロトコルが見つかった | |
| */ | |
| if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, | |
| sizeof(alpn_ossltest), in, | |
| in_len) == OPENSSL_NPN_NEGOTIATED) | |
| return SSL_TLSEXT_ERR_OK; /* 交渉成功 */ | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; /* 交渉失敗: ハンドシェイク中止 */ | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_ctx() - SSL_CTXの作成 | |
| * ============================================================================ | |
| * | |
| * QUICサーバー用のSSL_CTXを作成し、証明書と秘密鍵をロードする。 | |
| * | |
| * 【クライアントとの違い】 | |
| * - OSSL_QUIC_server_method() を使用(クライアントはOSSL_QUIC_client_method) | |
| * - サーバー証明書と秘密鍵のロードが必要 | |
| * - クライアント証明書の検証は無効(SSL_VERIFY_NONE) | |
| * - ALPNコールバックを設定してプロトコル交渉を処理 | |
| * | |
| * @param cert_path サーバー証明書チェーンファイルのパス(PEM形式) | |
| * @param key_path サーバー秘密鍵ファイルのパス(PEM形式) | |
| * @return 成功時はSSL_CTX、失敗時はNULL | |
| */ | |
| /* Create SSL_CTX. */ | |
| static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) | |
| { | |
| SSL_CTX *ctx; | |
| /* | |
| * An SSL_CTX holds shared configuration information for multiple | |
| * subsequent per-client connections. We specifically load a QUIC | |
| * server method here. | |
| */ | |
| /* QUICサーバー用SSL_CTXを作成 */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_server_method()); | |
| if (ctx == NULL) | |
| goto err; | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【サーバー証明書チェーンのロード】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * 証明書チェーンファイルには以下が含まれる(PEM形式): | |
| * 1. リーフ証明書(サーバー証明書)- 必ず最初 | |
| * 2. 中間CA証明書(あれば) | |
| * | |
| * 注意: 複数の公開鍵アルゴリズム(RSA、ECDSAなど)に対応する場合は、 | |
| * 各アルゴリズムごとに証明書と秘密鍵をロードできる。 | |
| */ | |
| /* | |
| * Load the server's certificate *chain* file (PEM format), which includes | |
| * not only the leaf (end-entity) server certificate, but also any | |
| * intermediate issuer-CA certificates. The leaf certificate must be the | |
| * first certificate in the file. | |
| * | |
| * In advanced use-cases this can be called multiple times, once per public | |
| * key algorithm for which the server has a corresponding certificate. | |
| * However, the corresponding private key (see below) must be loaded first, | |
| * *before* moving on to the next chain file. | |
| * | |
| * The requisite files "chain.pem" and "pkey.pem" can be generated by running | |
| * "make chain" in this directory. If the server will be executed from some | |
| * other directory, move or copy the files there. | |
| */ | |
| if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { | |
| fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); | |
| goto err; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【秘密鍵のロード】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * 証明書に対応する秘密鍵をロード。 | |
| * 秘密鍵と証明書の対応関係も自動的に検証される。 | |
| */ | |
| /* | |
| * Load the corresponding private key, this also checks that the private | |
| * key matches the just loaded end-entity certificate. It does not check | |
| * whether the certificate chain is valid, the certificates could be | |
| * expired, or may otherwise fail to form a chain that a client can validate. | |
| */ | |
| if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { | |
| fprintf(stderr, "couldn't load key file: %s\n", key_path); | |
| goto err; | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【クライアント証明書検証の設定】 | |
| * ------------------------------------------------------------------------- | |
| * | |
| * SSL_VERIFY_NONE: クライアント証明書を要求しない | |
| * - 一般的なWebサーバーではクライアント証明書は不要 | |
| * - 相互TLS認証が必要な場合はSSL_VERIFY_PEERを使用 | |
| */ | |
| /* | |
| * Clients rarely employ certificate-based authentication, and so we don't | |
| * require "mutual" TLS authentication (indeed there's no way to know | |
| * whether or how the client authenticated the server, so the term "mutual" | |
| * is potentially misleading). | |
| * | |
| * Since we're not soliciting or processing client certificates, we don't | |
| * need to configure a trusted-certificate store, so no call to | |
| * SSL_CTX_set_default_verify_paths() is needed. The server's own | |
| * certificate chain is assumed valid. | |
| */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); | |
| /* Setup ALPN negotiation callback to decide which ALPN is accepted. */ | |
| /* ALPN選択コールバックを設定 */ | |
| SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); | |
| return ctx; | |
| err: | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_socket() - UDPソケットの作成 | |
| * ============================================================================ | |
| * | |
| * 指定されたポートでリッスンするUDPソケットを作成する。 | |
| * | |
| * 【クライアントとの違い】 | |
| * - サーバー: bind()でポートにバインド、connect()は不要 | |
| * - クライアント: connect()でサーバーに接続 | |
| * | |
| * @param port リッスンするポート番号 | |
| * @return 成功時はソケットFD、失敗時は-1 | |
| */ | |
| /* Create UDP socket on the given port. */ | |
| static int create_socket(uint16_t port) | |
| { | |
| int fd; | |
| struct sockaddr_in sa = {0}; | |
| /* Retrieve the file descriptor for a new UDP socket */ | |
| /* UDPソケットを作成 */ | |
| if ((fd = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | |
| fprintf(stderr, "cannot create socket"); | |
| goto err; | |
| } | |
| /* IPv4、指定ポート、全インターフェースでリッスン */ | |
| sa.sin_family = AF_INET; | |
| sa.sin_port = htons(port); | |
| /* sa.sin_addr は 0 (INADDR_ANY) のまま = 全インターフェース */ | |
| /* Bind to the new UDP socket on localhost */ | |
| /* ソケットをポートにバインド */ | |
| if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { | |
| fprintf(stderr, "cannot bind to %u\n", port); | |
| BIO_closesocket(fd); | |
| goto err; | |
| } | |
| return fd; | |
| err: | |
| BIO_closesocket(fd); | |
| return -1; | |
| } | |
| /* | |
| * ============================================================================ | |
| * run_quic_server() - QUICサーバーのメインループ | |
| * ============================================================================ | |
| * | |
| * QUICリスナーを作成し、接続を受け入れてエコー処理を行う。 | |
| * | |
| * 【QUICサーバーの階層構造】 | |
| * | |
| * SSL_CTX (設定共有) | |
| * │ | |
| * ▼ | |
| * Listener (SSL_new_listener) | |
| * │ 接続要求を待ち受け | |
| * │ | |
| * ▼ SSL_accept_connection() | |
| * Connection (SSL) | |
| * │ 個別のQUIC接続 | |
| * │ SSL_read_ex/SSL_write_ex でデータ送受信 | |
| * ▼ | |
| * | |
| * @param ctx SSL_CTX(サーバー設定) | |
| * @param fd UDPソケットのファイルディスクリプタ | |
| * @return 成功時は1(実際には無限ループなので到達しない)、失敗時は0 | |
| */ | |
| /* | |
| * Main loop for server to accept QUIC connections. | |
| * Echo every request back to the client. | |
| */ | |
| static int run_quic_server(SSL_CTX *ctx, int fd) | |
| { | |
| int ok = 0; | |
| SSL *listener, *conn; /* listener: リスナー, conn: 個別接続 */ | |
| unsigned char buf[8192]; | |
| size_t nread; | |
| size_t nwritten; | |
| /* | |
| * ========================================================================= | |
| * 【リスナーの作成と初期化】 | |
| * ========================================================================= | |
| * | |
| * SSL_new_listener(): QUICリスナーを作成 | |
| * - 複数のクライアント接続を受け入れる基盤 | |
| * - デフォルトでブロッキングモード | |
| * - 子オブジェクト(接続)は親の設定を継承 | |
| */ | |
| /* | |
| * Create a new QUIC listener. Listeners, and other QUIC objects, default | |
| * to operating in blocking mode. The configured behaviour is inherited by | |
| * child objects. | |
| */ | |
| if ((listener = SSL_new_listener(ctx, 0)) == NULL) | |
| goto err; | |
| /* Provide the listener with our UDP socket. */ | |
| /* リスナーにUDPソケットを関連付け */ | |
| if (!SSL_set_fd(listener, fd)) | |
| goto err; | |
| /* Begin listening. */ | |
| /* リッスン開始 */ | |
| if (!SSL_listen(listener)) | |
| goto err; | |
| /* | |
| * ========================================================================= | |
| * 【接続受け入れループ】 | |
| * ========================================================================= | |
| * | |
| * 無限ループで接続を受け入れ、各接続に対してエコー処理を行う。 | |
| * エラーが発生した場合のみループを抜ける。 | |
| */ | |
| /* | |
| * Begin an infinite loop of listening for connections. We will only | |
| * exit this loop if we encounter an error. | |
| */ | |
| for (;;) { | |
| /* Pristine error stack for each new connection */ | |
| /* 新しい接続ごとにエラースタックをクリア */ | |
| ERR_clear_error(); | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【接続の受け入れ】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * SSL_accept_connection(): 新しいQUIC接続を受け入れ | |
| * - ブロッキングモードでは接続が来るまで待機 | |
| * - 返されるSSLオブジェクトは個別の接続を表す | |
| */ | |
| /* Block while waiting for a client connection */ | |
| printf("Waiting for connection\n"); | |
| conn = SSL_accept_connection(listener, 0); | |
| if (conn == NULL) { | |
| fprintf(stderr, "error while accepting connection\n"); | |
| goto err; | |
| } | |
| printf("Accepted new connection\n"); | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【エコー処理】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * クライアントからデータを読み取り、そのまま送り返す。 | |
| * クライアントがストリームを閉じるまで(FIN受信まで)継続。 | |
| */ | |
| /* Echo client input */ | |
| while (SSL_read_ex(conn, buf, sizeof(buf), &nread) > 0) { | |
| if (SSL_write_ex(conn, buf, nread, &nwritten) > 0 | |
| && nwritten == nread) | |
| continue; | |
| fprintf(stderr, "Error echoing client input"); | |
| break; | |
| } | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【ストリーム終了とシャットダウン】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * SSL_stream_conclude(): 送信側ストリームを終了(FIN送信) | |
| * - これ以上データを送らないことをクライアントに通知 | |
| * | |
| * SSL_shutdown(): QUIC接続全体をシャットダウン | |
| * - 完了するまで複数回呼び出す必要がある場合がある | |
| */ | |
| /* Signal the end of the stream. */ | |
| if (SSL_stream_conclude(conn, 0) != 1) { | |
| fprintf(stderr, "Unable to conclude stream\n"); | |
| SSL_free(conn); | |
| goto err; | |
| } | |
| /* | |
| * Shut down the connection. We may need to call this multiple times | |
| * to ensure the connection is shutdown completely. | |
| */ | |
| /* 接続のシャットダウン(完了まで繰り返し呼び出し) */ | |
| while (SSL_shutdown(conn) != 1) | |
| continue; | |
| /* 接続オブジェクトを解放して次の接続を待つ */ | |
| SSL_free(conn); | |
| } | |
| err: | |
| SSL_free(listener); | |
| return ok; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - QUICサーバーのエントリポイント | |
| * ============================================================================ | |
| * | |
| * コマンドライン引数を解析し、SSL_CTXとソケットを作成してサーバーを起動する。 | |
| * | |
| * 【使用方法】 | |
| * ./quic-server-block <port> <server.crt> <server.key> | |
| * | |
| * 【引数】 | |
| * port - リッスンするポート番号 | |
| * server.crt - サーバー証明書(PEM形式) | |
| * server.key - サーバー秘密鍵(PEM形式) | |
| */ | |
| /* Minimal QUIC HTTP/1.0 server. */ | |
| int main(int argc, char *argv[]) | |
| { | |
| int res = EXIT_FAILURE; | |
| SSL_CTX *ctx = NULL; | |
| int fd; | |
| unsigned long port; | |
| #ifdef _WIN32 | |
| static const char *progname; | |
| progname = argv[0]; | |
| #endif | |
| /* 引数チェック */ | |
| if (argc != 4) | |
| errx(res, "usage: %s <port> <server.crt> <server.key>", argv[0]); | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【初期化】 | |
| * ------------------------------------------------------------------------- | |
| */ | |
| /* Create SSL_CTX that supports QUIC. */ | |
| /* QUIC対応SSL_CTXを作成(証明書・秘密鍵ロード) */ | |
| if ((ctx = create_ctx(argv[2], argv[3])) == NULL) { | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create context"); | |
| } | |
| /* Parse port number from command line arguments. */ | |
| /* ポート番号を解析 */ | |
| port = strtoul(argv[1], NULL, 0); | |
| if (port == 0 || port > UINT16_MAX) { | |
| SSL_CTX_free(ctx); | |
| errx(res, "Failed to parse port number"); | |
| } | |
| /* Create and bind a UDP socket. */ | |
| /* UDPソケットを作成しポートにバインド */ | |
| if ((fd = create_socket((uint16_t)port)) < 0) { | |
| SSL_CTX_free(ctx); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create socket"); | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【サーバー起動】 | |
| * ------------------------------------------------------------------------- | |
| */ | |
| /* QUIC server connection acceptance loop. */ | |
| /* QUICサーバーのメインループを実行(無限ループ) */ | |
| if (!run_quic_server(ctx, fd)) { | |
| SSL_CTX_free(ctx); | |
| BIO_closesocket(fd); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Error in QUIC server loop"); | |
| } | |
| /* | |
| * ------------------------------------------------------------------------- | |
| * 【クリーンアップ】 | |
| * ------------------------------------------------------------------------- | |
| * 注意: run_quic_server()は通常無限ループなので、ここには到達しない。 | |
| * エラー時のみここに来る。 | |
| */ | |
| /* Free resources. */ | |
| SSL_CTX_free(ctx); | |
| BIO_closesocket(fd); | |
| res = EXIT_SUCCESS; | |
| return res; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* | |
| * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved. | |
| * | |
| * Licensed under the Apache License 2.0 (the "License"). You may not use | |
| * this file except in compliance with the License. You can obtain a copy | |
| * in the file LICENSE in the source distribution or at | |
| * https://www.openssl.org/source/license.html | |
| */ | |
| /* | |
| * ファイルパス: demos/guide/quic-server-non-block.c (OpenSSLリポジトリ内) | |
| * | |
| * 注意: このファイルの日本語コメントは Claude Code によって追加されました。 | |
| * オリジナルのソースコードは https://github.com/openssl/openssl にあります。 | |
| */ | |
| /* | |
| * ============================================================================ | |
| * quic-server-non-block.c - 非ブロッキングモードQUICサーバーデモ | |
| * ============================================================================ | |
| * | |
| * ※ 日本語コメントは Claude Code によって追加されました。 | |
| * | |
| * 【概要】 | |
| * OpenSSL QUICを使用した非ブロッキングエコーサーバーのデモ。 | |
| * select()を使用してソケット活動を待機し、I/O操作が一時的に | |
| * 失敗した場合はリトライする。 | |
| * | |
| * 【quic-server-block.cとの違い】 | |
| * - quic-server-block.c: ブロッキングモード。操作完了まで待機。 | |
| * - このファイル: 非ブロッキングモード。select()で明示的に待機。 | |
| * | |
| * 【非ブロッキングモードの利点】 | |
| * - 複数の接続を1つのスレッドで同時に処理可能 | |
| * - GUIアプリケーションでUIがフリーズしない | |
| * - タイムアウトや他のイベントとの統合が容易 | |
| * - より細かいI/O制御が可能 | |
| * | |
| * 【処理フロー図】 | |
| * | |
| * main() | |
| * │ | |
| * ├── create_ctx() ─── SSL_CTX作成(証明書・秘密鍵ロード) | |
| * │ | |
| * ├── create_socket() ─── UDPソケット作成・bind・非ブロッキング設定 | |
| * │ | |
| * └── run_quic_server() | |
| * │ | |
| * ├── SSL_new_listener() ─── リスナー作成 | |
| * ├── SSL_set_fd() ─── ソケット関連付け | |
| * ├── SSL_set_blocking_mode(0) ─── 非ブロッキングモード設定 ★ | |
| * ├── SSL_listen() ─── リッスン開始 | |
| * │ | |
| * └── for(;;) ─── 接続受け入れループ | |
| * │ | |
| * ├── SSL_accept_connection() ループ | |
| * │ └── 失敗時 → wait_for_activity() → リトライ | |
| * │ | |
| * ├── SSL_read_ex() ループ(全データ読み取りまで) | |
| * │ └── 失敗時 → handle_io_failure() → リトライ | |
| * │ | |
| * ├── SSL_write_ex2() ループ(SSL_WRITE_FLAG_CONCLUDE付き) | |
| * │ └── 失敗時 → handle_io_failure() → リトライ | |
| * │ | |
| * ├── SSL_shutdown() ループ | |
| * │ └── 失敗時 → handle_io_failure() → リトライ | |
| * │ | |
| * └── SSL_free() | |
| * | |
| * 【非ブロッキングサーバーの主要パターン】 | |
| * | |
| * 1. SSL_set_blocking_mode(listener, 0) でリスナーを非ブロッキングに | |
| * → 子オブジェクト(接続)も非ブロッキングを継承 | |
| * | |
| * 2. 各SSL操作をwhileループでラップ | |
| * → handle_io_failure()でリトライ判定 | |
| * | |
| * 3. wait_for_activity()でソケット活動を待機 | |
| * → SSL_net_read_desired(), SSL_net_write_desired()で方向確認 | |
| * → SSL_get_event_timeout()でタイムアウト取得 | |
| * | |
| * 【使用方法】 | |
| * ./quic-server-non-block <port> <server.crt> <server.key> | |
| * | |
| * 【関連ドキュメント】 | |
| * doc/man7/ossl-guide-quic-server-non-block.pod | |
| */ | |
| /* | |
| * NB: Changes to this file should also be reflected in | |
| * doc/man7/ossl-guide-quic-server-non-block.pod | |
| */ | |
| #include <string.h> | |
| /* Include the appropriate header file for SOCK_STREAM */ | |
| #ifdef _WIN32 /* Windows */ | |
| # include <stdarg.h> | |
| # include <winsock2.h> | |
| #else /* Linux/Unix */ | |
| # include <err.h> | |
| # include <sys/socket.h> | |
| # include <sys/select.h> | |
| # include <netinet/in.h> | |
| # include <unistd.h> | |
| #endif | |
| #include <openssl/bio.h> | |
| #include <openssl/ssl.h> | |
| #include <openssl/err.h> | |
| #include <openssl/quic.h> | |
| #ifdef _WIN32 | |
| static const char *progname; | |
| static void vwarnx(const char *fmt, va_list ap) | |
| { | |
| if (progname != NULL) | |
| fprintf(stderr, "%s: ", progname); | |
| vfprintf(stderr, fmt, ap); | |
| putc('\n', stderr); | |
| } | |
| static void errx(int status, const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| exit(status); | |
| } | |
| static void warnx(const char *fmt, ...) | |
| { | |
| va_list ap; | |
| va_start(ap, fmt); | |
| vwarnx(fmt, ap); | |
| va_end(ap); | |
| } | |
| #endif | |
| /* | |
| * ============================================================================ | |
| * ALPN (Application-Layer Protocol Negotiation) 設定 | |
| * ============================================================================ | |
| * | |
| * サーバーがサポートするALPNプロトコルのリスト。 | |
| * quic-server-block.cと同一。 | |
| */ | |
| /* | |
| * ALPN strings for TLS handshake. Only 'http/1.0' and 'hq-interop' | |
| * are accepted. | |
| */ | |
| static const unsigned char alpn_ossltest[] = { | |
| 8, 'h', 't', 't', 'p', '/', '1', '.', '0', /* http/1.0 */ | |
| 10, 'h', 'q', '-', 'i', 'n', 't', 'e', 'r', 'o', 'p', /* hq-interop */ | |
| }; | |
| /* | |
| * select_alpn() - ALPN選択コールバック | |
| * | |
| * quic-server-block.cと同一の実装。 | |
| */ | |
| /* | |
| * This callback validates and negotiates the desired ALPN on the server side. | |
| */ | |
| static int select_alpn(SSL *ssl, const unsigned char **out, | |
| unsigned char *out_len, const unsigned char *in, | |
| unsigned int in_len, void *arg) | |
| { | |
| if (SSL_select_next_proto((unsigned char **)out, out_len, alpn_ossltest, | |
| sizeof(alpn_ossltest), in, | |
| in_len) == OPENSSL_NPN_NEGOTIATED) | |
| return SSL_TLSEXT_ERR_OK; /* 交渉成功 */ | |
| return SSL_TLSEXT_ERR_ALERT_FATAL; /* 交渉失敗 */ | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_ctx() - SSL_CTXの作成 | |
| * ============================================================================ | |
| * | |
| * quic-server-block.cと同一の実装。 | |
| * 証明書・秘密鍵のロード、ALPN設定を行う。 | |
| */ | |
| /* Create SSL_CTX. */ | |
| static SSL_CTX *create_ctx(const char *cert_path, const char *key_path) | |
| { | |
| SSL_CTX *ctx; | |
| /* | |
| * An SSL_CTX holds shared configuration information for multiple | |
| * subsequent per-client connections. We specifically load a QUIC | |
| * server method here. | |
| */ | |
| /* QUICサーバー用SSL_CTXを作成 */ | |
| ctx = SSL_CTX_new(OSSL_QUIC_server_method()); | |
| if (ctx == NULL) | |
| goto err; | |
| /* | |
| * Load the server's certificate *chain* file (PEM format), which includes | |
| * not only the leaf (end-entity) server certificate, but also any | |
| * intermediate issuer-CA certificates. The leaf certificate must be the | |
| * first certificate in the file. | |
| * | |
| * In advanced use-cases this can be called multiple times, once per public | |
| * key algorithm for which the server has a corresponding certificate. | |
| * However, the corresponding private key (see below) must be loaded first, | |
| * *before* moving on to the next chain file. | |
| * | |
| * The requisite files "chain.pem" and "pkey.pem" can be generated by running | |
| * "make chain" in this directory. If the server will be executed from some | |
| * other directory, move or copy the files there. | |
| */ | |
| /* サーバー証明書チェーンをロード */ | |
| if (SSL_CTX_use_certificate_chain_file(ctx, cert_path) <= 0) { | |
| fprintf(stderr, "couldn't load certificate file: %s\n", cert_path); | |
| goto err; | |
| } | |
| /* | |
| * Load the corresponding private key, this also checks that the private | |
| * key matches the just loaded end-entity certificate. It does not check | |
| * whether the certificate chain is valid, the certificates could be | |
| * expired, or may otherwise fail to form a chain that a client can validate. | |
| */ | |
| /* 秘密鍵をロード */ | |
| if (SSL_CTX_use_PrivateKey_file(ctx, key_path, SSL_FILETYPE_PEM) <= 0) { | |
| fprintf(stderr, "couldn't load key file: %s\n", key_path); | |
| goto err; | |
| } | |
| /* | |
| * Clients rarely employ certificate-based authentication, and so we don't | |
| * require "mutual" TLS authentication (indeed there's no way to know | |
| * whether or how the client authenticated the server, so the term "mutual" | |
| * is potentially misleading). | |
| * | |
| * Since we're not soliciting or processing client certificates, we don't | |
| * need to configure a trusted-certificate store, so no call to | |
| * SSL_CTX_set_default_verify_paths() is needed. The server's own | |
| * certificate chain is assumed valid. | |
| */ | |
| /* クライアント証明書検証は無効 */ | |
| SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); | |
| /* Setup ALPN negotiation callback to decide which ALPN is accepted. */ | |
| /* ALPN選択コールバックを設定 */ | |
| SSL_CTX_set_alpn_select_cb(ctx, select_alpn, NULL); | |
| return ctx; | |
| err: | |
| SSL_CTX_free(ctx); | |
| return NULL; | |
| } | |
| /* | |
| * ============================================================================ | |
| * create_socket() - UDPソケットの作成(非ブロッキング版) | |
| * ============================================================================ | |
| * | |
| * 【quic-server-block.cとの違い】 | |
| * - BIO_socket_nbio()でソケットを非ブロッキングモードに設定 | |
| * | |
| * @param port リッスンするポート番号 | |
| * @return 成功時はソケットFD、失敗時は-1 | |
| */ | |
| /* Create UDP socket on the given port. */ | |
| static int create_socket(uint16_t port) | |
| { | |
| int fd; | |
| struct sockaddr_in sa = {0}; | |
| /* Retrieve the file descriptor for a new UDP socket */ | |
| /* UDPソケットを作成 */ | |
| if ((fd = (int)socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) { | |
| fprintf(stderr, "cannot create socket"); | |
| return -1; | |
| } | |
| /* IPv4、指定ポート、全インターフェースでリッスン */ | |
| sa.sin_family = AF_INET; | |
| sa.sin_port = htons(port); | |
| /* Bind to the new UDP socket on localhost */ | |
| /* ソケットをポートにバインド */ | |
| if (bind(fd, (const struct sockaddr *)&sa, sizeof(sa)) < 0) { | |
| fprintf(stderr, "cannot bind to %u\n", port); | |
| BIO_closesocket(fd); | |
| return -1; | |
| } | |
| /* | |
| * 【重要】ソケットを非ブロッキングモードに設定 | |
| * これがquic-server-block.cとの違い。 | |
| * 非ブロッキングソケットでは、I/O操作が即座に完了しない場合に | |
| * エラーを返す(ブロックしない)。 | |
| */ | |
| /* Set port to nonblocking mode */ | |
| if (BIO_socket_nbio(fd, 1) <= 0) { | |
| fprintf(stderr, "Unable to set port to nonblocking mode"); | |
| BIO_closesocket(fd); | |
| return -1; | |
| } | |
| return fd; | |
| } | |
| /* | |
| * ============================================================================ | |
| * wait_for_activity() - ソケット活動を待機 | |
| * ============================================================================ | |
| * | |
| * 非ブロッキングモードで重要な関数。select()を使用して、 | |
| * ソケットが読み書き可能になるまで、またはタイムアウトまで待機する。 | |
| * | |
| * 【処理フロー】 | |
| * 1. SSL_get_fd()でソケットFDを取得 | |
| * 2. SSL_net_write_desired()で書き込み必要性を確認 | |
| * 3. SSL_net_read_desired()で読み取り必要性を確認 | |
| * 4. SSL_get_event_timeout()でQUICタイムアウトを取得 | |
| * 5. select()で待機 | |
| * | |
| * quic-client-non-block.cのwait_for_activity()と同様の実装。 | |
| * | |
| * @param ssl SSLオブジェクト(リスナーまたは接続) | |
| */ | |
| /** | |
| * @brief Waits for activity on the SSL socket, either for reading or writing. | |
| * | |
| * This function monitors the underlying file descriptor of the given SSL | |
| * connection to determine when it is ready for reading or writing, or both. | |
| * It uses the select function to wait until the socket is either readable | |
| * or writable, depending on what the SSL connection requires. | |
| * | |
| * @param ssl A pointer to the SSL object representing the connection. | |
| * | |
| * @note This function blocks until there is activity on the socket. In a real | |
| * application, you might want to perform other tasks while waiting, such as | |
| * updating a GUI or handling other connections. | |
| * | |
| * @note This function uses select for simplicity and portability. Depending | |
| * on your application's requirements, you might consider using other | |
| * mechanisms like poll or epoll for handling multiple file descriptors. | |
| */ | |
| static void wait_for_activity(SSL *ssl) | |
| { | |
| int sock, isinfinite; | |
| fd_set read_fd, write_fd; | |
| struct timeval tv; | |
| struct timeval *tvp = NULL; /* タイムアウト(NULLなら無期限) */ | |
| /* Get hold of the underlying file descriptor for the socket */ | |
| /* ソケットFDを取得 */ | |
| if ((sock = SSL_get_fd(ssl)) == -1) { | |
| fprintf(stderr, "Unable to get file descriptor"); | |
| return; | |
| } | |
| /* Initialize the fd_set structure */ | |
| FD_ZERO(&read_fd); | |
| FD_ZERO(&write_fd); | |
| /* | |
| * Determine if we would like to write to the socket, read from it, or both. | |
| */ | |
| /* | |
| * QUICスタックが必要とするI/O方向を確認。 | |
| * SSL_net_write_desired(): 送信が必要か | |
| * SSL_net_read_desired(): 受信が必要か | |
| */ | |
| if (SSL_net_write_desired(ssl)) | |
| FD_SET(sock, &write_fd); | |
| if (SSL_net_read_desired(ssl)) | |
| FD_SET(sock, &read_fd); | |
| /* | |
| * Find out when OpenSSL would next like to be called, regardless of | |
| * whether the state of the underlying socket has changed or not. | |
| */ | |
| /* | |
| * QUICタイマーによる次のタイムアウトを取得。 | |
| * ACK遅延、PTO(パケットロス検出)、アイドルタイムアウト等。 | |
| */ | |
| if (SSL_get_event_timeout(ssl, &tv, &isinfinite) && !isinfinite) | |
| tvp = &tv; | |
| /* | |
| * Wait until the socket is writeable or readable. We use select here | |
| * for the sake of simplicity and portability, but you could equally use | |
| * poll/epoll or similar functions | |
| * | |
| * NOTE: For the purposes of this demonstration code this effectively | |
| * makes this demo block until it has something more useful to do. In a | |
| * real application you probably want to go and do other work here (e.g. | |
| * update a GUI, or service other connections). | |
| * | |
| * Let's say for example that you want to update the progress counter on | |
| * a GUI every 100ms. One way to do that would be to use the timeout in | |
| * the last parameter to "select" below. If the tvp value is greater | |
| * than 100ms then use 100ms instead. Then, when select returns, you | |
| * check if it did so because of activity on the file descriptors or | |
| * because of the timeout. If the 100ms GUI timeout has expired but the | |
| * tvp timeout has not then go and update the GUI and then restart the | |
| * "select" (with updated timeouts). | |
| */ | |
| /* select()でソケット活動またはタイムアウトを待機 */ | |
| select(sock + 1, &read_fd, &write_fd, NULL, tvp); | |
| } | |
| /* | |
| * ============================================================================ | |
| * handle_io_failure() - I/O失敗時のエラーハンドリング | |
| * ============================================================================ | |
| * | |
| * 非ブロッキングモードでSSL操作が失敗した際に呼び出される。 | |
| * エラーの種類に応じて、リトライ可能かどうかを判断する。 | |
| * | |
| * 【戻り値】 | |
| * 1: リトライ可能(wait_for_activity()後に再試行) | |
| * 0: EOF(正常終了) | |
| * -1: 致命的エラー(リトライ不可) | |
| * | |
| * 【エラーコードの意味】 | |
| * SSL_ERROR_WANT_READ/WRITE: 一時的な失敗、リトライ可能 | |
| * SSL_ERROR_ZERO_RETURN: EOF(正常終了) | |
| * SSL_ERROR_NONE: エラーなし(EOFとして扱う) | |
| * SSL_ERROR_SYSCALL: システムコールエラー | |
| * SSL_ERROR_SSL: SSLレベルのエラー | |
| * | |
| * quic-client-non-block.cのhandle_io_failure()と類似の実装。 | |
| */ | |
| /** | |
| * @brief Handles I/O failures on an SSL connection based on the result code. | |
| * | |
| * This function processes the result of an SSL I/O operation and handles | |
| * different types of errors that may occur during the operation. It takes | |
| * appropriate actions such as retrying the operation, reporting errors, or | |
| * returning specific status codes based on the error type. | |
| * | |
| * @param ssl A pointer to the SSL object representing the connection. | |
| * @param res The result code from the SSL I/O operation. | |
| * @return An integer indicating the outcome: | |
| * - 1: Temporary failure, the operation should be retried. | |
| * - 0: EOF, indicating the connection has been closed. | |
| * - -1: A fatal error occurred or the connection has been reset. | |
| * | |
| * @note This function may block if a temporary failure occurs and | |
| * wait_for_activity() is called. | |
| * | |
| * @note If the failure is due to an SSL verification error, additional | |
| * information will be logged to stderr. | |
| */ | |
| static int handle_io_failure(SSL *ssl, int res) | |
| { | |
| switch (SSL_get_error(ssl, res)) { | |
| case SSL_ERROR_WANT_READ: | |
| case SSL_ERROR_WANT_WRITE: | |
| /* Temporary failure. Wait until we can read/write and try again */ | |
| /* | |
| * 一時的な失敗。ソケットが読み書き可能になるまで待機して | |
| * リトライする。非ブロッキングモードでは頻繁に発生する。 | |
| */ | |
| wait_for_activity(ssl); | |
| return 1; /* リトライ可能 */ | |
| case SSL_ERROR_ZERO_RETURN: | |
| case SSL_ERROR_NONE: | |
| /* EOF */ | |
| /* EOF: 相手がストリームを閉じた */ | |
| return 0; | |
| case SSL_ERROR_SYSCALL: | |
| /* システムコールエラー */ | |
| return -1; | |
| case SSL_ERROR_SSL: | |
| /* | |
| * Some stream fatal error occurred. This could be because of a | |
| * stream reset - or some failure occurred on the underlying | |
| * connection. | |
| */ | |
| /* SSLレベルのエラー。詳細を確認 */ | |
| switch (SSL_get_stream_read_state(ssl)) { | |
| case SSL_STREAM_STATE_RESET_REMOTE: | |
| printf("Stream reset occurred\n"); | |
| /* | |
| * The stream has been reset but the connection is still | |
| * healthy. | |
| */ | |
| /* ストリームはリセットされたが、接続は健全 */ | |
| break; | |
| case SSL_STREAM_STATE_CONN_CLOSED: | |
| printf("Connection closed\n"); | |
| /* Connection is already closed. */ | |
| /* 接続が既に閉じられている */ | |
| break; | |
| default: | |
| printf("Unknown stream failure\n"); | |
| break; | |
| } | |
| /* | |
| * If the failure is due to a verification error we can get more | |
| * information about it from SSL_get_verify_result(). | |
| */ | |
| /* 証明書検証エラーの場合、詳細を表示 */ | |
| if (SSL_get_verify_result(ssl) != X509_V_OK) | |
| printf("Verify error: %s\n", | |
| X509_verify_cert_error_string(SSL_get_verify_result(ssl))); | |
| return -1; | |
| default: | |
| /* その他の予期しないエラー */ | |
| return -1; | |
| } | |
| } | |
| /* | |
| * ============================================================================ | |
| * run_quic_server() - 非ブロッキングQUICサーバーのメインループ | |
| * ============================================================================ | |
| * | |
| * 【quic-server-block.cとの主な違い】 | |
| * 1. SSL_set_blocking_mode(listener, 0) で非ブロッキングモード設定 | |
| * 2. SSL_accept_connection() をwhileループでリトライ | |
| * 3. SSL_read_ex() / SSL_write_ex2() をループでリトライ | |
| * 4. SSL_shutdown() をループでリトライ | |
| * 5. handle_io_failure() でエラー種別を判定 | |
| * | |
| * @param ctx SSL_CTX(サーバー設定) | |
| * @param fd 非ブロッキングUDPソケットのFD | |
| * @return 成功時はEXIT_SUCCESS、失敗時は-1 | |
| */ | |
| /* | |
| * Main loop for server to accept QUIC connections. | |
| * Echo every request back to the client. | |
| */ | |
| static int run_quic_server(SSL_CTX *ctx, int fd) | |
| { | |
| int ok = -1; | |
| int ret, eof; | |
| SSL *listener, *conn = NULL; /* listener: リスナー, conn: 個別接続 */ | |
| unsigned char buf[8192]; | |
| size_t nread, total_read, total_written; | |
| /* | |
| * ========================================================================= | |
| * 【リスナーの作成と非ブロッキング設定】 | |
| * ========================================================================= | |
| */ | |
| /* Create a new QUIC listener */ | |
| /* QUICリスナーを作成 */ | |
| if ((listener = SSL_new_listener(ctx, 0)) == NULL) | |
| goto err; | |
| /* Provide the listener with our UDP socket. */ | |
| /* リスナーにUDPソケットを関連付け */ | |
| if (!SSL_set_fd(listener, fd)) | |
| goto err; | |
| /* | |
| * 【重要】リスナーを非ブロッキングモードに設定 | |
| * これがquic-server-block.cとの最大の違い! | |
| * 子オブジェクト(接続)も非ブロッキングモードを継承する。 | |
| */ | |
| /* | |
| * Set the listener mode to non-blocking, which is inherited by | |
| * child objects. | |
| */ | |
| if (!SSL_set_blocking_mode(listener, 0)) | |
| goto err; | |
| /* | |
| * Begin listening. Note that is not usually needed as SSL_accept_connection | |
| * will implicitly start listening. It is only needed if a server wishes to | |
| * ensure it has started to accept incoming connections but does not wish to | |
| * actually call SSL_accept_connection yet. | |
| */ | |
| /* リッスン開始(SSL_accept_connectionが暗黙的に行うため通常は不要) */ | |
| if (!SSL_listen(listener)) | |
| goto err; | |
| /* | |
| * ========================================================================= | |
| * 【接続受け入れループ】 | |
| * ========================================================================= | |
| */ | |
| /* | |
| * Begin an infinite loop of listening for connections. We will only | |
| * exit this loop if we encounter an error. | |
| */ | |
| for (;;) { | |
| eof = 0; | |
| total_read = 0; | |
| total_written = 0; | |
| /* Pristine error stack for each new connection */ | |
| /* 新しい接続ごとにエラースタックをクリア */ | |
| ERR_clear_error(); | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【非ブロッキング接続受け入れ】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * SSL_accept_connection()は非ブロッキングモードでは即座にリターン。 | |
| * NULLが返る場合はwait_for_activity()で待機してリトライ。 | |
| */ | |
| /* Block while waiting for a client connection */ | |
| printf("Waiting for connection\n"); | |
| while ((conn = SSL_accept_connection(listener, 0)) == NULL) | |
| wait_for_activity(listener); /* リスナーで待機 */ | |
| printf("Accepted new connection\n"); | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【非ブロッキング読み取り】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * クライアントがストリームを閉じる(FIN送信)まで全データを読み取る。 | |
| * handle_io_failure()が1を返す限りリトライ。 | |
| */ | |
| /* Read from client until the client sends a end of stream packet */ | |
| while (!eof) { | |
| ret = SSL_read_ex(conn, buf + total_read, sizeof(buf) - total_read, | |
| &nread); | |
| total_read += nread; | |
| if (total_read >= 8192) { | |
| fprintf(stderr, "Could not fit all data into buffer\n"); | |
| goto err; | |
| } | |
| switch (handle_io_failure(conn, ret)) { | |
| case 1: | |
| continue; /* Retry リトライ可能 */ | |
| case 0: | |
| /* Reached end of stream */ | |
| /* EOF: SSL_has_pending()で未処理データを確認 */ | |
| if (!SSL_has_pending(conn)) | |
| eof = 1; | |
| break; | |
| default: | |
| fprintf(stderr, "Failed reading remaining data\n"); | |
| goto err; | |
| } | |
| } | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【非ブロッキング書き込み(エコー)】 | |
| * --------------------------------------------------------------------- | |
| * | |
| * SSL_WRITE_FLAG_CONCLUDE: 書き込みと同時にストリーム終了(FIN送信) | |
| */ | |
| /* Echo client input */ | |
| while (!SSL_write_ex2(conn, buf, | |
| total_read, | |
| SSL_WRITE_FLAG_CONCLUDE, &total_written)) { | |
| if (handle_io_failure(conn, 0) == 1) | |
| continue; /* リトライ可能 */ | |
| fprintf(stderr, "Failed to write data\n"); | |
| goto err; | |
| } | |
| if (total_read != total_written) | |
| fprintf(stderr, "Failed to echo data [read: %zu, written: %zu]\n", | |
| total_read, total_written); | |
| /* | |
| * --------------------------------------------------------------------- | |
| * 【非ブロッキングシャットダウン】 | |
| * --------------------------------------------------------------------- | |
| */ | |
| /* | |
| * Shut down the connection. We may need to call this multiple times | |
| * to ensure the connection is shutdown completely. | |
| */ | |
| while ((ret = SSL_shutdown(conn)) != 1) { | |
| if (ret < 0 && handle_io_failure(conn, ret) == 1) | |
| continue; /* Retry リトライ可能 */ | |
| } | |
| /* 接続オブジェクトを解放して次の接続を待つ */ | |
| SSL_free(conn); | |
| } | |
| ok = EXIT_SUCCESS; | |
| err: | |
| SSL_free(listener); | |
| return ok; | |
| } | |
| /* | |
| * ============================================================================ | |
| * main() - 非ブロッキングQUICサーバーのエントリポイント | |
| * ============================================================================ | |
| * | |
| * quic-server-block.cと同様だが、create_socket()で非ブロッキングソケットを作成。 | |
| * | |
| * 【使用方法】 | |
| * ./quic-server-non-block <port> <server.crt> <server.key> | |
| */ | |
| /* Minimal QUIC HTTP/1.0 server. */ | |
| int main(int argc, char *argv[]) | |
| { | |
| int res = EXIT_FAILURE; | |
| SSL_CTX *ctx = NULL; | |
| int fd; | |
| unsigned long port; | |
| #ifdef _WIN32 | |
| progname = argv[0]; | |
| #endif | |
| /* 引数チェック */ | |
| if (argc != 4) | |
| errx(res, "usage: %s <port> <server.crt> <server.key>", argv[0]); | |
| /* Create SSL_CTX that supports QUIC. */ | |
| /* QUIC対応SSL_CTXを作成 */ | |
| if ((ctx = create_ctx(argv[2], argv[3])) == NULL) { | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create context"); | |
| } | |
| /* Parse port number from command line arguments. */ | |
| /* ポート番号を解析 */ | |
| port = strtoul(argv[1], NULL, 0); | |
| if (port == 0 || port > UINT16_MAX) { | |
| SSL_CTX_free(ctx); | |
| errx(res, "Failed to parse port number"); | |
| } | |
| /* Create and bind a UDP socket. */ | |
| /* 非ブロッキングUDPソケットを作成しポートにバインド */ | |
| if ((fd = create_socket((uint16_t)port)) < 0) { | |
| SSL_CTX_free(ctx); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Failed to create socket"); | |
| } | |
| /* QUIC server connection acceptance loop. */ | |
| /* 非ブロッキングQUICサーバーのメインループを実行 */ | |
| if (run_quic_server(ctx, fd) < 0) { | |
| SSL_CTX_free(ctx); | |
| BIO_closesocket(fd); | |
| ERR_print_errors_fp(stderr); | |
| errx(res, "Error in QUIC server loop"); | |
| } | |
| /* Free resources. */ | |
| /* リソース解放(通常は無限ループなのでここには到達しない) */ | |
| SSL_CTX_free(ctx); | |
| BIO_closesocket(fd); | |
| res = EXIT_SUCCESS; | |
| return res; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment