feat: HTTP/2 passive fingerprinting with individual SETTINGS fields

Complete implementation of HTTP/2 passive fingerprinting per thesis §2.5.3:

mod-reqin-log (C module):
- Replace connection-level filter with ap_hook_process_connection (APR_HOOK_FIRST)
  to capture H2 preface before mod_http2 takes over the connection
- AP_MODE_SPECULATIVE read of 512 bytes from c->input_filters
- Parse SETTINGS, WINDOW_UPDATE, PRIORITY flags, pseudo-header order
- Output individual SETTINGS params as separate JSON fields (IDs 1-6, 8)
- Read H2 notes from c1 (master connection) for mod_http2 secondary conns
- Fix header_order_signature JSON length bug (26→strlen)

ClickHouse schema:
- Add 8 new columns to http_logs: h2_has_priority, h2_header_table_size,
  h2_enable_push, h2_max_concurrent_streams, h2_initial_window_size,
  h2_max_frame_size, h2_max_header_list_size, h2_enable_connect_protocol
- Use Int32/Int64 with DEFAULT -1 to distinguish absent vs zero
- Update mv_http_logs to extract individual fields via JSONHas/JSONExtractInt
- Migration 04_http2_fields.sql updated for existing deployments

Correlator:
- Accept both timestamp_ns and timestamp field names (backward compat)

Integration:
- Enable HTTP/2 in Apache: Protocols h2 http/1.1 in httpd-integration.conf

Validated end-to-end via Playwright: H2 curl traffic → mod-reqin-log →
correlator → ClickHouse with all 12 H2 columns populated correctly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-11 02:33:45 +02:00
parent bd81331411
commit 85d3b95b7b
25 changed files with 649 additions and 160 deletions

View File

@ -130,15 +130,13 @@ static const char *cmd_set_log_level(cmd_parms *cmd, void *dummy, const char *ar
/* Forward declarations for hooks */
static int reqin_log_post_read_request(request_rec *r);
static int reqin_log_log_transaction(request_rec *r);
static void reqin_log_child_init(apr_pool_t *p, server_rec *s);
static int reqin_log_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s);
static void reqin_log_register_hooks(apr_pool_t *p);
/* Forward declarations for le filtre HTTP/2 */
static apr_status_t reqin_h2_filter(ap_filter_t *f, apr_bucket_brigade *bb,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes);
static void reqin_h2_add_filter(conn_rec *c, void *csd);
/* Forward declarations for la capture HTTP/2 */
static int reqin_h2_process_connection(conn_rec *c, void *csd);
/* Command table */
static const command_rec reqin_log_cmds[] = {
@ -934,12 +932,16 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
format_iso8601(&buf, r->request_time);
dynbuf_append(&buf, "\",", 2);
/* timestamp (nanoseconds since epoch, from request reception time) */
/* timestamp_ns (nanoseconds since epoch, via clock_gettime CLOCK_REALTIME) */
{
apr_uint64_t ns = ((apr_uint64_t)r->request_time) * APR_UINT64_C(1000);
struct timespec ts_now;
apr_uint64_t ns;
char ts_buf[32];
clock_gettime(CLOCK_REALTIME, &ts_now);
ns = (apr_uint64_t)ts_now.tv_sec * APR_UINT64_C(1000000000)
+ (apr_uint64_t)ts_now.tv_nsec;
snprintf(ts_buf, sizeof(ts_buf), "%" APR_UINT64_T_FMT, ns);
dynbuf_append(&buf, "\"timestamp\":", 12);
dynbuf_append(&buf, "\"timestamp_ns\":", 15);
dynbuf_append(&buf, ts_buf, -1);
dynbuf_append(&buf, ",", 1);
}
@ -989,8 +991,8 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
append_json_string(&buf, path);
dynbuf_append(&buf, "\",", 2);
/* query */
dynbuf_append(&buf, "\"query\":\"", 9);
/* query_string */
dynbuf_append(&buf, "\"query_string\":\"", 16);
append_json_string(&buf, query);
dynbuf_append(&buf, "\",", 2);
@ -1013,11 +1015,15 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
}
/* client_headers - ordered list of all header names as received from the client,
* preserving original order and case */
* preserving original order and case.
* headers_raw - all headers concatenated "Name: Value\r\n" preserving order.
* header_order_signature - FNV-1a 64-bit hash of the ordered header names. */
{
const apr_array_header_t *arr = apr_table_elts(r->headers_in);
const apr_table_entry_t *elts = (const apr_table_entry_t *)arr->elts;
int first = 1;
apr_uint64_t fnv_hash = APR_UINT64_C(14695981039346656037);
char hash_buf[24];
dynbuf_append(&buf, ",\"client_headers\":[", 19);
for (int i = 0; i < arr->nelts; i++) {
@ -1029,9 +1035,35 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
append_json_string(&buf, elts[i].key);
dynbuf_append(&buf, "\"", 1);
first = 0;
/* FNV-1a sur chaque octet du nom de header */
for (const char *p = elts[i].key; *p; p++) {
fnv_hash ^= (apr_uint64_t)(unsigned char)*p;
fnv_hash *= APR_UINT64_C(1099511628211);
}
/* Séparateur entre noms */
fnv_hash ^= (apr_uint64_t)'\n';
fnv_hash *= APR_UINT64_C(1099511628211);
}
}
dynbuf_append(&buf, "]", 1);
/* headers_raw — en-têtes bruts dans leur ordre d'émission */
dynbuf_append(&buf, ",\"headers_raw\":\"", 16);
for (int i = 0; i < arr->nelts; i++) {
if (elts[i].key != NULL) {
append_json_string(&buf, elts[i].key);
dynbuf_append(&buf, ": ", 2);
append_json_string(&buf, elts[i].val ? elts[i].val : "");
dynbuf_append(&buf, "\\r\\n", 4);
}
}
dynbuf_append(&buf, "\"", 1);
/* header_order_signature — FNV-1a 64-bit hash de l'ordre des noms */
snprintf(hash_buf, sizeof(hash_buf), "%" APR_UINT64_T_FMT, fnv_hash);
dynbuf_append(&buf, ",\"header_order_signature\":\"", (apr_size_t)-1);
dynbuf_append(&buf, hash_buf, -1);
dynbuf_append(&buf, "\"", 1);
}
/* Check buffer size before adding headers to prevent memory exhaustion */
@ -1096,14 +1128,20 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
}
}
/* Champs HTTP/2 passif depuis les notes de connexion (vides si HTTP/1.x) */
/* Champs HTTP/2 passif depuis les notes de connexion (vides si HTTP/1.x).
* Pour les connexions HTTP/2, mod_http2 crée des connexions secondaires (c2)
* par stream. Le preface H2 est stocké dans les notes de la connexion
* primaire (c1), accessible via r->connection->master. */
{
const char *h2_fp = apr_table_get(r->connection->notes, H2_NOTE_FINGERPRINT);
const char *h2_set = apr_table_get(r->connection->notes, H2_NOTE_SETTINGS);
const char *h2_wu = apr_table_get(r->connection->notes, H2_NOTE_WUPDATE);
const char *h2_ps = apr_table_get(r->connection->notes, H2_NOTE_PSEUDO_ORDER);
conn_rec *c1 = r->connection->master ? r->connection->master : r->connection;
const char *h2_fp = apr_table_get(c1->notes, H2_NOTE_FINGERPRINT);
const char *h2_set = apr_table_get(c1->notes, H2_NOTE_SETTINGS);
const char *h2_wu = apr_table_get(c1->notes, H2_NOTE_WUPDATE);
const char *h2_ps = apr_table_get(c1->notes, H2_NOTE_PSEUDO_ORDER);
const char *h2_pri = apr_table_get(c1->notes, H2_NOTE_HAS_PRIORITY);
if (h2_set && h2_set[0] != '\0') {
/* Champs composites (rétrocompatibilité + fingerprint matching) */
dynbuf_append(&buf, ",\"h2_fingerprint\":\"", (apr_size_t)-1);
append_json_string(&buf, h2_fp ? h2_fp : "");
dynbuf_append(&buf, "\",\"h2_settings_fp\":\"", (apr_size_t)-1);
@ -1113,11 +1151,35 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
dynbuf_append(&buf, ",\"h2_pseudo_order\":\"", (apr_size_t)-1);
append_json_string(&buf, h2_ps ? h2_ps : "");
dynbuf_append(&buf, "\"", 1);
dynbuf_append(&buf, ",\"h2_has_priority\":", (apr_size_t)-1);
dynbuf_append(&buf, (h2_pri && h2_pri[0] == '1') ? "1" : "0", 1);
/* Champs SETTINGS individuels (RFC 9113 §6.5.2).
* Émis uniquement si le client a envoyé le paramètre
* (-1 / absent = non émis → le champ JSON est absent). */
static const struct { const char *note; const char *json; } sfields[] = {
{H2_NOTE_SET_HEADER_TABLE_SIZE, ",\"h2_header_table_size\":"},
{H2_NOTE_SET_ENABLE_PUSH, ",\"h2_enable_push\":"},
{H2_NOTE_SET_MAX_CONCURRENT_STREAMS, ",\"h2_max_concurrent_streams\":"},
{H2_NOTE_SET_INITIAL_WINDOW_SIZE, ",\"h2_initial_window_size\":"},
{H2_NOTE_SET_MAX_FRAME_SIZE, ",\"h2_max_frame_size\":"},
{H2_NOTE_SET_MAX_HEADER_LIST_SIZE, ",\"h2_max_header_list_size\":"},
{H2_NOTE_SET_ENABLE_CONNECT, ",\"h2_enable_connect_protocol\":"},
};
int si;
for (si = 0; si < (int)(sizeof(sfields) / sizeof(sfields[0])); si++) {
const char *v = apr_table_get(c1->notes, sfields[si].note);
if (v) {
dynbuf_append(&buf, sfields[si].json, (apr_size_t)-1);
dynbuf_append(&buf, v, (apr_size_t)-1);
}
}
}
}
dynbuf_append(&buf, "}\n", 2);
/* Ne pas fermer le JSON ici — les champs de réponse (status_code,
* response_size, duration_ms) seront ajoutés par le hook log_transaction
* qui s'exécute après le traitement complet de la requête. */
if (buf.len > MAX_JSON_SIZE) {
apr_time_t now = apr_time_now();
apr_time_t error_interval = apr_time_from_sec(cfg->error_report_interval);
@ -1137,7 +1199,11 @@ static void log_request(request_rec *r, reqin_log_config_t *cfg, reqin_log_child
return;
}
write_to_socket(buf.data, buf.len, s, cfg, state);
/* Stocker le JSON partiel dans les notes de la requête pour log_transaction */
{
char *partial = apr_pstrmemdup(r->pool, buf.data, buf.len);
apr_table_setn(r->notes, "reqin_partial_json", partial);
}
}
/* ====== Fingerprinting HTTP/2 passif ====== */
@ -1307,6 +1373,13 @@ static void h2_parse_preface(conn_rec *c, const char *buf, apr_size_t len)
int has_priority = 0;
int settings_pos_out = 0;
/* Valeurs individuelles des paramètres SETTINGS (RFC 9113 §6.5.2).
* -1 signifie « absent du preface client » (distinction importante :
* un paramètre absent ≠ un paramètre à 0). */
int64_t setting_vals[9];
int i;
for (i = 0; i < 9; i++) setting_vals[i] = -1;
/* Vérification du magic HTTP/2 */
if (len < MAGIC_LEN || memcmp(buf, H2_MAGIC, MAGIC_LEN) != 0) return;
@ -1346,6 +1419,12 @@ static void h2_parse_preface(conn_rec *c, const char *buf, apr_size_t len)
settings_pos_out += snprintf(settings_buf + settings_pos_out,
(int)sizeof(settings_buf) - settings_pos_out,
"%u:%u", id, val);
/* Stocker la valeur individuelle (IDs 1-6 et 8) */
if (id >= 1 && id <= 6)
setting_vals[id] = (int64_t)val;
else if (id == 8)
setting_vals[8] = (int64_t)val;
}
} else if (type == 0x08u && stream_id == 0) {
@ -1412,6 +1491,26 @@ static void h2_parse_preface(conn_rec *c, const char *buf, apr_size_t len)
apr_table_setn(c->notes, H2_NOTE_SETTINGS, apr_pstrdup(c->pool, settings_buf));
apr_table_setn(c->notes, H2_NOTE_WUPDATE, apr_pstrdup(c->pool, wupdate_buf));
apr_table_setn(c->notes, H2_NOTE_PSEUDO_ORDER, apr_pstrdup(c->pool, pseudo_buf));
apr_table_setn(c->notes, H2_NOTE_HAS_PRIORITY, has_priority ? "1" : "0");
/* Stocker chaque paramètre SETTINGS individuel (absent = note absente) */
static const struct { int id; const char *note; } smap[] = {
{1, H2_NOTE_SET_HEADER_TABLE_SIZE},
{2, H2_NOTE_SET_ENABLE_PUSH},
{3, H2_NOTE_SET_MAX_CONCURRENT_STREAMS},
{4, H2_NOTE_SET_INITIAL_WINDOW_SIZE},
{5, H2_NOTE_SET_MAX_FRAME_SIZE},
{6, H2_NOTE_SET_MAX_HEADER_LIST_SIZE},
{8, H2_NOTE_SET_ENABLE_CONNECT},
};
for (i = 0; i < (int)(sizeof(smap) / sizeof(smap[0])); i++) {
int64_t v = setting_vals[smap[i].id];
if (v >= 0) {
char tmp[16];
snprintf(tmp, sizeof(tmp), "%u", (uint32_t)v);
apr_table_setn(c->notes, smap[i].note, apr_pstrdup(c->pool, tmp));
}
}
}
/**
@ -1419,9 +1518,13 @@ static void h2_parse_preface(conn_rec *c, const char *buf, apr_size_t len)
*
* S'injecte entre le filtre SSL (déchiffrement) et mod_http2 grâce à sa
* priorité AP_FTYPE_CONNECTION et à l'inscription via APR_HOOK_LAST.
* À la première invocation, effectue une lecture spéculative non-destructive
* (AP_MODE_SPECULATIVE) de H2_PEEK_SIZE octets, parse le preface HTTP/2,
* stocke les résultats dans c->notes, puis se retire de la chaîne.
*
* Stratégie : au lieu d'une lecture spéculative séparée (qui interfère avec
* le handshake SSL et le traitement mod_http2), ce filtre se greffe sur les
* lectures réelles. Il laisse passer les lectures spéculatives (utilisées par
* mod_http2 pour détecter le magic H2) sans intervenir, puis sur la première
* lecture non-spéculative, il inspecte les données déjà lues (via
* apr_brigade_flatten, qui copie sans consommer) pour parser le preface H2.
*
* @param f Filtre courant.
* @param bb Brigade cible pour la lecture réelle.
@ -1430,51 +1533,58 @@ static void h2_parse_preface(conn_rec *c, const char *buf, apr_size_t len)
* @param readbytes Nombre d'octets demandés.
* @return Statut APR de la lecture réelle.
*/
static apr_status_t reqin_h2_filter(ap_filter_t *f, apr_bucket_brigade *bb,
ap_input_mode_t mode, apr_read_type_e block,
apr_off_t readbytes)
{
conn_rec *c = f->c;
if (!apr_table_get(c->notes, H2_NOTE_PARSED)) {
/* Lecture spéculative : ne consomme pas les données du flux */
apr_bucket_brigade *peek = apr_brigade_create(c->pool, c->bucket_alloc);
apr_status_t rv = ap_get_brigade(f->next, peek,
AP_MODE_SPECULATIVE, APR_BLOCK_READ,
H2_PEEK_SIZE);
if (rv == APR_SUCCESS) {
char peek_buf[H2_PEEK_SIZE];
apr_size_t peek_len = sizeof(peek_buf);
if (apr_brigade_flatten(peek, peek_buf, &peek_len) == APR_SUCCESS
&& peek_len > 0) {
h2_parse_preface(c, peek_buf, peek_len);
}
}
apr_brigade_cleanup(peek);
apr_table_setn(c->notes, H2_NOTE_PARSED, "1");
}
/* Le filtre n'est nécessaire qu'une seule fois par connexion */
ap_remove_input_filter(f);
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
/**
* @brief Hook pre_connection — enregistre le filtre HTTP/2 sur chaque connexion.
* @brief Filtre d'entrée de connexion pour la capture passive du preface HTTP/2.
*
* Appelé à l'établissement de chaque connexion. Inscrit reqin_h2_filter dans
* la chaîne d'entrée avec APR_HOOK_LAST, ce qui garantit son positionnement
* après le filtre SSL (qui s'inscrit avec APR_HOOK_MIDDLE) et donc son accès
* au flux HTTP/2 en clair.
* S'injecte entre le filtre SSL (déchiffrement) et mod_http2 grâce à sa
* priorité AP_FTYPE_CONNECTION et à l'inscription via APR_HOOK_LAST.
*
* Stratégie : au lieu d'une lecture spéculative séparée (qui interfère avec
* le traitement mod_http2), ce filtre se greffe sur les lectures réelles.
* Il laisse passer les lectures spéculatives (utilisées par mod_http2 pour
* détecter le magic H2) sans intervenir, puis sur la première lecture
* non-spéculative, il inspecte les données déjà lues (via apr_brigade_flatten,
* qui copie sans consommer) pour parser le preface H2.
*
* @param f Filtre courant.
* @param bb Brigade cible pour la lecture réelle.
* @param mode Mode de lecture demandé (transmis à f->next).
* @param block Type de blocage (transmis à f->next).
* @param readbytes Nombre d'octets demandés.
* @return Statut APR de la lecture réelle.
*/
/**
* @brief Hook process_connection — capture passive du preface HTTP/2.
*
* S'exécute AVANT mod_http2 (APR_HOOK_FIRST) et effectue une lecture
* spéculative non-destructive de H2_PEEK_SIZE octets sur la connexion.
* Si le preface HTTP/2 (RFC 9113 §3.4) est détecté, parse les frames
* SETTINGS, WINDOW_UPDATE et le premier HEADERS, puis stocke les
* résultats dans c->notes. Retourne DECLINED pour laisser mod_http2
* (ou le handler HTTP/1.x) prendre le relais.
*
* @param c Connexion Apache.
* @param csd Socket descriptor (non utilisé).
* @return DECLINED — ne gère pas la connexion, laisse les hooks suivants.
*/
static void reqin_h2_add_filter(conn_rec *c, void *csd)
static int reqin_h2_process_connection(conn_rec *c, void *csd)
{
(void)csd;
ap_add_input_filter(H2_FILTER_NAME, NULL, NULL, c);
apr_bucket_brigade *bb = apr_brigade_create(c->pool, c->bucket_alloc);
apr_status_t rv = ap_get_brigade(c->input_filters, bb,
AP_MODE_SPECULATIVE, APR_BLOCK_READ,
H2_PEEK_SIZE);
if (rv == APR_SUCCESS) {
char buf[H2_PEEK_SIZE];
apr_size_t len = sizeof(buf);
if (apr_brigade_flatten(bb, buf, &len) == APR_SUCCESS && len >= 24) {
h2_parse_preface(c, buf, len);
}
}
apr_brigade_destroy(bb);
return DECLINED;
}
/* ====== Hooks Apache ====== */
@ -1506,6 +1616,73 @@ static int reqin_log_post_read_request(request_rec *r)
return DECLINED;
}
/**
* @brief Hook log_transaction — complète le JSON avec les champs de réponse et envoie.
*
* Récupère le JSON partiel stocké par post_read_request dans r->notes,
* ajoute status_code, response_size et duration_ms, puis envoie le JSON
* complet via le socket Unix.
*
* @param r request_rec — la requête traitée.
* @return DECLINED pour permettre aux autres modules de logger.
*/
static int reqin_log_log_transaction(request_rec *r)
{
reqin_log_server_conf_t *srv_conf;
reqin_log_config_t *cfg;
reqin_log_child_state_t *state;
const char *partial;
dynbuf_t buf;
char num_buf[32];
apr_time_t duration_us;
if (r->main != NULL || r->prev != NULL) {
return DECLINED;
}
srv_conf = get_server_conf(r->server);
if (srv_conf == NULL || srv_conf->config == NULL ||
!srv_conf->config->enabled || srv_conf->config->socket_path == NULL) {
return DECLINED;
}
partial = apr_table_get(r->notes, "reqin_partial_json");
if (partial == NULL) {
return DECLINED;
}
cfg = srv_conf->config;
state = &srv_conf->child_state;
dynbuf_init(&buf, r->pool, 4096);
dynbuf_append(&buf, partial, -1);
/* status_code */
snprintf(num_buf, sizeof(num_buf), "%d", r->status);
dynbuf_append(&buf, ",\"status_code\":", 15);
dynbuf_append(&buf, num_buf, -1);
/* response_size (bytes sent to client) */
snprintf(num_buf, sizeof(num_buf), "%" APR_INT64_T_FMT, (apr_int64_t)r->bytes_sent);
dynbuf_append(&buf, ",\"response_size\":", 17);
dynbuf_append(&buf, num_buf, -1);
/* duration_ms (request processing time in milliseconds) */
duration_us = apr_time_now() - r->request_time;
snprintf(num_buf, sizeof(num_buf), "%" APR_INT64_T_FMT, (apr_int64_t)(duration_us / 1000));
dynbuf_append(&buf, ",\"duration_ms\":", 15);
dynbuf_append(&buf, num_buf, -1);
/* Fermer le JSON */
dynbuf_append(&buf, "}\n", 2);
if (buf.len <= MAX_JSON_SIZE) {
write_to_socket(buf.data, buf.len, r->server, cfg, state);
}
return DECLINED;
}
/**
* @brief Hook child_init — initialise l'état du processus enfant et établit la connexion socket.
*
@ -1627,11 +1804,11 @@ static int reqin_log_post_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t
static void reqin_log_register_hooks(apr_pool_t *p)
{
(void)p;
/* Enregistrement du filtre de connexion HTTP/2 (avant les hooks de requête) */
ap_register_input_filter(H2_FILTER_NAME, reqin_h2_filter, NULL, AP_FTYPE_CONNECTION);
ap_hook_pre_connection(reqin_h2_add_filter, NULL, NULL, APR_HOOK_LAST);
/* Hook process_connection AVANT mod_http2 pour capturer le preface H2 */
ap_hook_process_connection(reqin_h2_process_connection, NULL, NULL, APR_HOOK_FIRST);
ap_hook_post_config(reqin_log_post_config, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_post_read_request(reqin_log_post_read_request, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_log_transaction(reqin_log_log_transaction, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_child_init(reqin_log_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}

View File

@ -36,14 +36,21 @@ extern module AP_MODULE_DECLARE_DATA reqin_log_module;
/* ====== Fingerprinting HTTP/2 passif ====== */
/* Nom du filtre d'entrée de connexion pour la capture du preface HTTP/2 */
#define H2_FILTER_NAME "REQIN_H2_PEEK"
/* Clés des notes de connexion stockant le fingerprint HTTP/2 parsé */
#define H2_NOTE_FINGERPRINT "reqin_h2_fp" /* Fingerprint Akamai complet */
#define H2_NOTE_SETTINGS "reqin_h2_set" /* Entrées SETTINGS brutes */
#define H2_NOTE_WUPDATE "reqin_h2_wu" /* Incrément WINDOW_UPDATE */
#define H2_NOTE_PSEUDO_ORDER "reqin_h2_ps" /* Ordre pseudo-headers */
#define H2_NOTE_HAS_PRIORITY "reqin_h2_pri" /* Flag PRIORITY présent */
#define H2_NOTE_PARSED "reqin_h2_done" /* Marqueur "déjà parsé" */
/* Clés des notes pour chaque paramètre SETTINGS individuel (RFC 9113 §6.5.2) */
#define H2_NOTE_SET_HEADER_TABLE_SIZE "reqin_h2_s1" /* ID 1 */
#define H2_NOTE_SET_ENABLE_PUSH "reqin_h2_s2" /* ID 2 */
#define H2_NOTE_SET_MAX_CONCURRENT_STREAMS "reqin_h2_s3" /* ID 3 */
#define H2_NOTE_SET_INITIAL_WINDOW_SIZE "reqin_h2_s4" /* ID 4 */
#define H2_NOTE_SET_MAX_FRAME_SIZE "reqin_h2_s5" /* ID 5 */
#define H2_NOTE_SET_MAX_HEADER_LIST_SIZE "reqin_h2_s6" /* ID 6 */
#define H2_NOTE_SET_ENABLE_CONNECT "reqin_h2_s8" /* ID 8 */
#endif /* MOD_REQIN_LOG_H */