Skip to content

mod_http3/src/h3_config.c

Functions

Name
apr_port_t get_server_port(const server_rec * s)
void * h3_create_server_config(apr_pool_t * p, server_rec * s)
void * h3_merge_server_config(apr_pool_t * p, void * base_conf, void * new_conf)
int mpm_query(int code)
Value of an MPM query, or -1 when the active MPM does not answer it.
const char * set_string(cmd_parms * cmd, const char * arg, const char * field)
const char * set_h3_cert_path(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_key_path(cmd_parms * cmd, void * , const char * arg)
const char * engine_list(apr_pool_t * pool)
int engine_known(const char * name)
const char * set_h3_quic_engine(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_port(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_max_concurrent_streams(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_max_connections(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_stream_buffer_size(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_max_request_body_size(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_max_response_body_size(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_handshake_timeout(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_idle_timeout(cmd_parms * cmd, void * , const char * arg)
const char * set_h3_alt_svc(cmd_parms * cmd, void * , int flag)
const char * set_h3_address_validation(cmd_parms * cmd, void * , int flag)
const char * set_h3_alt_svc_max_age(cmd_parms * cmd, void * , const char * arg)
int h3_post_config(apr_pool_t * p, apr_pool_t * plog, apr_pool_t * ptemp, server_rec * s)
void * h3_create_dir_config(apr_pool_t * p, char * dir)
void * h3_merge_dir_config(apr_pool_t * p, void * base, void * add)

Attributes

Name
const command_rec cmd_1
const command_rec cmd_2
const command_rec cmd_3
const command_rec cmd_4
const command_rec cmd_5
const command_rec cmd_6
const command_rec cmd_7
const command_rec cmd_8
const command_rec cmd_9
const command_rec cmd_10
const command_rec cmd_11
const command_rec cmd_12
const command_rec cmd_13
const command_rec cmd_14
const command_rec cmd_end
const command_rec[] h3_cmds

Functions Documentation

function get_server_port

apr_port_t get_server_port(
    const server_rec * s
)

Parameters:

  • s The server_rec.

Return: The TCP port, or 0 if undetermined.

Look up the TCP Listen port of a vhost by scanning its Port and Listen directive state.

function h3_create_server_config

void * h3_create_server_config(
    apr_pool_t * p,
    server_rec * s
)

Parameters:

  • p Pool used for the allocation.
  • s The vhost the config belongs to (unused).

Return: The new h3_server_conf.

ap_create_server_config callback: allocate a fresh zero-initialised h3_server_conf from p. All fields start as NULL/0.

function h3_merge_server_config

void * h3_merge_server_config(
    apr_pool_t * p,
    void * base_conf,
    void * new_conf
)

Parameters:

Return: The merged h3_server_conf.

ap_merge_server_config callback: produce a child vhost config that inherits each unset field from the parent. cert/key/h3_port use the new value if non-NULL/non-zero, else the base.

function mpm_query

static int mpm_query(
    int code
)

Value of an MPM query, or -1 when the active MPM does not answer it.

function set_string

static const char * set_string(
    cmd_parms * cmd,
    const char * arg,
    const char * field
)

function set_h3_cert_path

static const char * set_h3_cert_path(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_key_path

static const char * set_h3_key_path(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function engine_list

static const char * engine_list(
    apr_pool_t * pool
)

function engine_known

static int engine_known(
    const char * name
)

function set_h3_quic_engine

static const char * set_h3_quic_engine(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_port

static const char * set_h3_port(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_max_concurrent_streams

static const char * set_h3_max_concurrent_streams(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_max_connections

static const char * set_h3_max_connections(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_stream_buffer_size

static const char * set_h3_stream_buffer_size(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_max_request_body_size

static const char * set_h3_max_request_body_size(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_max_response_body_size

static const char * set_h3_max_response_body_size(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_handshake_timeout

static const char * set_h3_handshake_timeout(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_idle_timeout

static const char * set_h3_idle_timeout(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function set_h3_alt_svc

static const char * set_h3_alt_svc(
    cmd_parms * cmd,
    void * ,
    int flag
)

function set_h3_address_validation

static const char * set_h3_address_validation(
    cmd_parms * cmd,
    void * ,
    int flag
)

function set_h3_alt_svc_max_age

static const char * set_h3_alt_svc_max_age(
    cmd_parms * cmd,
    void * ,
    const char * arg
)

function h3_post_config

int h3_post_config(
    apr_pool_t * p,
    apr_pool_t * plog,
    apr_pool_t * ptemp,
    server_rec * s
)

Parameters:

  • p Config pool (unused).
  • plog Log pool (unused).
  • ptemp Temp pool (unused).
  • s The first server_rec in the configuration.

Return: OK, or HTTP_INTERNAL_SERVER_ERROR if no vhost has both H3CertificatePath and H3CertificateKeyPath set.

ap_post_config hook: resolve cert/key/h3_port for the listening vhost and log the resolved values. No-op in AP_SQ_MS_CREATE_PRE_CONFIG (pre-config phase). Returns OK if a fully-configured vhost is found, HTTP_INTERNAL_SERVER_ERROR otherwise.

function h3_create_dir_config

void * h3_create_dir_config(
    apr_pool_t * p,
    char * dir
)

Parameters:

  • p Pool for the allocation.
  • dir The directory path (unused).

Return: A 1-byte zero-initialised block.

ap_create_dir_config callback. mod_http3 has no per-directory state; returns a 1-byte allocation so httpd takes ownership of a non-NULL pointer.

function h3_merge_dir_config

void * h3_merge_dir_config(
    apr_pool_t * p,
    void * base,
    void * add
)

Parameters:

  • p Pool for any allocation (unused).
  • base Base per-dir config.
  • add Per-dir config being merged in (unused).

Return: The base per-dir config.

ap_merge_dir_config callback. Identity merge: per-directory config is a no-op for mod_http3, so the base is returned unchanged.

Attributes Documentation

variable cmd_1

const command_rec cmd_1 = AP_INIT_TAKE1("H3CertificatePath", set_h3_cert_path, NULL, RSRC_CONF, "Path to the SSL certificate file for HTTP/3");

variable cmd_2

const command_rec cmd_2 = AP_INIT_TAKE1("H3CertificateKeyPath", set_h3_key_path, NULL, RSRC_CONF, "Path to the SSL certificate key file for HTTP/3");

variable cmd_3

const command_rec cmd_3 = AP_INIT_TAKE1("H3Port", set_h3_port, NULL, RSRC_CONF, "UDP port to listen on for QUIC/HTTP-3 (default: same as main server)");

variable cmd_4

const command_rec cmd_4 = AP_INIT_TAKE1("H3MaxConcurrentStreams", set_h3_max_concurrent_streams, NULL, RSRC_CONF, "Maximum number of concurrent HTTP/3 streams per connection (default: 100)");

variable cmd_5

const command_rec cmd_5 = AP_INIT_TAKE1("H3MaxConnections", set_h3_max_connections, NULL, RSRC_CONF, "Maximum concurrent QUIC/HTTP/3 connections per child process (default: 256)");

variable cmd_6

const command_rec cmd_6 = AP_INIT_TAKE1("H3StreamBufferSize", set_h3_stream_buffer_size, NULL, RSRC_CONF, "Per-stream request and streaming-response buffer size in bytes (default: 65536)");

variable cmd_7

const command_rec cmd_7 = AP_INIT_TAKE1("H3MaxRequestBodySize", set_h3_max_request_body_size, NULL, RSRC_CONF, "Maximum HTTP/3 request body size in bytes, fully buffered in memory (default: 10485760)");

variable cmd_8

const command_rec cmd_8 = AP_INIT_FLAG("H3AltSvc", set_h3_alt_svc, NULL, RSRC_CONF, "Whether to advertise HTTP/3 support via an Alt-Svc response header, required for browser discovery (default: on)");

variable cmd_9

const command_rec cmd_9 = AP_INIT_TAKE1("H3AltSvcMaxAge", set_h3_alt_svc_max_age, NULL, RSRC_CONF, "Seconds a client may cache the Alt-Svc HTTP/3 advertisement for (default: 86400)");

variable cmd_10

const command_rec cmd_10 = AP_INIT_TAKE1("H3HandshakeTimeout", set_h3_handshake_timeout, NULL, RSRC_CONF, "Timeout in seconds for QUIC handshakes to complete (default: 10)");

variable cmd_11

const command_rec cmd_11 = AP_INIT_TAKE1("H3IdleTimeout", set_h3_idle_timeout, NULL, RSRC_CONF, "Idle timeout in seconds for QUIC connections (default: 300)");

variable cmd_12

const command_rec cmd_12 = AP_INIT_TAKE1("H3MaxResponseBodySize", set_h3_max_response_body_size, NULL, RSRC_CONF, "Maximum HTTP/3 response body size in bytes; an explicit limit enables bounded whole-response buffering (default: unlimited streaming)");

variable cmd_13

const command_rec cmd_13 = AP_INIT_FLAG("H3AddressValidation", set_h3_address_validation, NULL, RSRC_CONF, "Whether to validate client addresses with a QUIC Retry packet before accepting a connection (default: on)");

variable cmd_14

const command_rec cmd_14 = AP_INIT_TAKE1("H3QuicEngine", set_h3_quic_engine, NULL, RSRC_CONF, "QUIC engine to run, among those compiled in (default: openssl)");

variable cmd_end

const command_rec cmd_end = AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL);

variable h3_cmds

const command_rec[] h3_cmds = {cmd_1, cmd_2, cmd_3, cmd_4, cmd_5, cmd_6, cmd_7, cmd_8, cmd_9, cmd_10, cmd_11, cmd_12, cmd_13, cmd_14, cmd_end};

Source code

/*
 * Copyright (c) 2026 The mod_http3 Project Authors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <httpd.h>

#include <ap_mpm.h>
#include <http_config.h>
#include <http_core.h>
#include <http_log.h>
#include <http_main.h>

#include <apr_cstr.h>
#include <apr_pools.h>
#include <apr_strings.h>

#include <stdint.h>
#include <unistd.h>

#include "h3.h"
#include "h3_check.h"
#include "h3_config.h"
#include "mod_http3.h"
#include "quic.h"

apr_port_t get_server_port(const server_rec* s)
{
    for (const server_addr_rec* sar = s->addrs; sar != NULL; sar = sar->next)
    {
        if (sar->host_port != 0)
        {
            return sar->host_port;
        }
    }
    return 0;
}

void* h3_create_server_config(apr_pool_t* p, server_rec* /*s*/)
{
    return apr_pcalloc(p, sizeof(h3_server_conf));
}

void* h3_merge_server_config(apr_pool_t* p, void* base_conf, void* new_conf)
{
    h3_server_conf* merged = apr_pcalloc(p, sizeof(h3_server_conf));
    h3_server_conf* base = (h3_server_conf*)base_conf;
    h3_server_conf* new = (h3_server_conf*)new_conf;

    merged->h3_cert_path = new->h3_cert_path ? new->h3_cert_path : base->h3_cert_path;
    merged->h3_key_path = new->h3_key_path ? new->h3_key_path : base->h3_key_path;
    merged->h3_quic_engine = new->h3_quic_engine ? new->h3_quic_engine : base->h3_quic_engine;
    merged->h3_port = new->h3_port ? new->h3_port : base->h3_port;
    merged->h3_max_concurrent_streams = new->h3_max_concurrent_streams ? new->h3_max_concurrent_streams : base->h3_max_concurrent_streams;
    merged->h3_max_connections = new->h3_max_connections ? new->h3_max_connections : base->h3_max_connections;
    merged->h3_stream_buffer_size = new->h3_stream_buffer_size ? new->h3_stream_buffer_size : base->h3_stream_buffer_size;
    merged->h3_max_request_body_size = new->h3_max_request_body_size ? new->h3_max_request_body_size : base->h3_max_request_body_size;
    merged->h3_max_response_body_size = new->h3_max_response_body_size ? new->h3_max_response_body_size : base->h3_max_response_body_size;
    merged->h3_alt_svc = new->h3_alt_svc != H3_FLAG_UNSET ? new->h3_alt_svc : base->h3_alt_svc;
    merged->h3_address_validation = new->h3_address_validation != H3_FLAG_UNSET ? new->h3_address_validation : base->h3_address_validation;
    merged->h3_alt_svc_max_age = new->h3_alt_svc_max_age ? new->h3_alt_svc_max_age : base->h3_alt_svc_max_age;
    merged->h3_handshake_timeout = new->h3_handshake_timeout ? new->h3_handshake_timeout : base->h3_handshake_timeout;
    merged->h3_idle_timeout = new->h3_idle_timeout ? new->h3_idle_timeout : base->h3_idle_timeout;

    return merged;
}

static int mpm_query(int code)
{
    int value = 0;
    return ap_mpm_query(code, &value) == APR_SUCCESS ? value : -1;
}

static const char* set_string(cmd_parms* cmd, const char* arg, const char* field)
{
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    if (!conf)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, cmd->server, "mod_http3: server config missing in directive");
        return "mod_http3: internal error: no server config";
    }
    *(const char**)((char*)conf + (apr_size_t)field) = apr_pstrdup(cmd->pool, arg);
    return NULL;
}

static const char* set_h3_cert_path(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    return set_string(cmd, arg, (const char*)offsetof(h3_server_conf, h3_cert_path));
}

static const char* set_h3_key_path(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    return set_string(cmd, arg, (const char*)offsetof(h3_server_conf, h3_key_path));
}


static const char* engine_list(apr_pool_t* pool)
{
    const char* list = quic_engine_name_at(0);
    for (size_t i = 1; i < quic_engine_count(); i++)
    {
        list = apr_pstrcat(pool, list, ", ", quic_engine_name_at(i), NULL);
    }
    return list;
}

/* Checked here rather than only at post_config, which httpd -t never reaches. */
static int engine_known(const char* name)
{
    for (size_t i = 0; i < quic_engine_count(); i++)
    {
        if (apr_cstr_casecmp(name, quic_engine_name_at(i)) == 0)
        {
            return 1;
        }
    }
    return 0;
}

static const char* set_h3_quic_engine(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (arg && *arg && !engine_known(arg))
    {
        return apr_psprintf(cmd->pool, "H3QuicEngine %s: this build has no such engine (compiled: %s)", arg, engine_list(cmd->pool));
    }
    return set_string(cmd, arg, (const char*)offsetof(h3_server_conf, h3_quic_engine));
}

static const char* set_h3_port(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3Port: empty port number";
    }
    apr_int64_t port = 0;
    apr_status_t rv = apr_cstr_atoi64(&port, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3Port: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3Port: '%s' is out of representable range", arg);
    }
    if (port < 1 || port > 65535)
    {
        return apr_psprintf(cmd->pool, "H3Port: '%s' is out of allowed range (1-65535)", arg);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_port = (apr_port_t)port;
    return NULL;
}

static const char* set_h3_max_concurrent_streams(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3MaxConcurrentStreams: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3MaxConcurrentStreams: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3MaxConcurrentStreams: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > UINT16_MAX)
    {
        return apr_psprintf(cmd->pool, "H3MaxConcurrentStreams: '%s' is out of allowed range (1-%u)", arg, (unsigned)UINT16_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_max_concurrent_streams = (apr_uint32_t)val;
    return NULL;
}

static const char* set_h3_max_connections(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3MaxConnections: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3MaxConnections: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3MaxConnections: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_MAX_CONNECTIONS_MAX)
    {
        return apr_psprintf(cmd->pool, "H3MaxConnections: '%s' is out of allowed range (1-%u)", arg, (unsigned)H3_MAX_CONNECTIONS_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_max_connections = (apr_uint32_t)val;
    return NULL;
}

static const char* set_h3_stream_buffer_size(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3StreamBufferSize: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3StreamBufferSize: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3StreamBufferSize: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_STREAM_BUFFER_SIZE_MAX)
    {
        return apr_psprintf(cmd->pool, "H3StreamBufferSize: '%s' is out of allowed range (1-%lu)", arg, (unsigned long)H3_STREAM_BUFFER_SIZE_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_stream_buffer_size = (apr_size_t)val;
    return NULL;
}

static const char* set_h3_max_request_body_size(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3MaxRequestBodySize: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3MaxRequestBodySize: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3MaxRequestBodySize: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_MAX_REQUEST_BODY_SIZE_MAX)
    {
        return apr_psprintf(cmd->pool, "H3MaxRequestBodySize: '%s' is out of allowed range (1-%lu)", arg, (unsigned long)H3_MAX_REQUEST_BODY_SIZE_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_max_request_body_size = (apr_size_t)val;
    return NULL;
}

static const char* set_h3_max_response_body_size(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3MaxResponseBodySize: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3MaxResponseBodySize: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3MaxResponseBodySize: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_MAX_RESPONSE_BODY_SIZE_MAX)
    {
        return apr_psprintf(cmd->pool, "H3MaxResponseBodySize: '%s' is out of allowed range (1-%lu)", arg, (unsigned long)H3_MAX_RESPONSE_BODY_SIZE_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_max_response_body_size = (apr_size_t)val;
    return NULL;
}

static const char* set_h3_handshake_timeout(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3HandshakeTimeout: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3HandshakeTimeout: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3HandshakeTimeout: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_HANDSHAKE_TIMEOUT_MAX)
    {
        return apr_psprintf(cmd->pool, "H3HandshakeTimeout: '%s' is out of allowed range (1-%u)", arg, (unsigned)H3_HANDSHAKE_TIMEOUT_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_handshake_timeout = (apr_uint32_t)val;
    return NULL;
}

static const char* set_h3_idle_timeout(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3IdleTimeout: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3IdleTimeout: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3IdleTimeout: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_IDLE_TIMEOUT_MAX)
    {
        return apr_psprintf(cmd->pool, "H3IdleTimeout: '%s' is out of allowed range (1-%u)", arg, (unsigned)H3_IDLE_TIMEOUT_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_idle_timeout = (apr_uint32_t)val;
    return NULL;
}

static const char* set_h3_alt_svc(cmd_parms* cmd, void* /*dummy*/, int flag)
{
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_alt_svc = flag ? H3_FLAG_ON : H3_FLAG_OFF;
    return NULL;
}

static const char* set_h3_address_validation(cmd_parms* cmd, void* /*dummy*/, int flag)
{
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_address_validation = flag ? H3_FLAG_ON : H3_FLAG_OFF;
    return NULL;
}

static const char* set_h3_alt_svc_max_age(cmd_parms* cmd, void* /*dummy*/, const char* arg)
{
    if (!arg || !*arg)
    {
        return "H3AltSvcMaxAge: empty value";
    }
    apr_uint64_t val = 0;
    apr_status_t rv = apr_cstr_atoui64(&val, arg);
    if (rv == APR_EINVAL)
    {
        return apr_psprintf(cmd->pool, "H3AltSvcMaxAge: '%s' is not a number", arg);
    }
    if (rv == APR_ERANGE)
    {
        return apr_psprintf(cmd->pool, "H3AltSvcMaxAge: '%s' is out of representable range", arg);
    }
    if (val == 0 || val > H3_ALT_SVC_MAX_AGE_MAX)
    {
        return apr_psprintf(cmd->pool, "H3AltSvcMaxAge: '%s' is out of allowed range (1-%lu)", arg, (unsigned long)H3_ALT_SVC_MAX_AGE_MAX);
    }
    h3_server_conf* conf = ap_get_module_config(cmd->server->module_config, &http3_module);
    CHECK(conf);
    conf->h3_alt_svc_max_age = (apr_uint32_t)val;
    return NULL;
}

int h3_post_config(apr_pool_t* /*p*/, apr_pool_t* /*plog*/, apr_pool_t* ptemp, server_rec* s)
{
    CHECK(ptemp);
    CHECK(s);
    h3_server_conf* conf = NULL;

    if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_PRE_CONFIG)
    {
        return OK;
    }

    for (server_rec* vs = s; vs; vs = vs->next)
    {
        h3_server_conf* vc = ap_get_module_config(vs->module_config, &http3_module);
        if (vc->h3_cert_path && vc->h3_key_path)
        {
            vc->host_port = get_server_port(vs);
            if (vc->h3_port == 0)
            {
                vc->h3_port = vc->host_port;
            }
            if (vc->h3_max_concurrent_streams == 0)
            {
                vc->h3_max_concurrent_streams = H3_MAX_CONCURRENT_STREAMS_DEFAULT;
            }
            if (vc->h3_max_connections == 0)
            {
                vc->h3_max_connections = H3_MAX_CONNECTIONS_DEFAULT;
            }
            if (vc->h3_stream_buffer_size == 0)
            {
                vc->h3_stream_buffer_size = H3_STREAM_BUFFER_SIZE_DEFAULT;
            }
            if (vc->h3_max_request_body_size == 0)
            {
                vc->h3_max_request_body_size = H3_MAX_REQUEST_BODY_SIZE_DEFAULT;
            }
            if (vc->h3_max_response_body_size == 0)
            {
                vc->h3_max_response_body_size = H3_MAX_RESPONSE_BODY_SIZE_DEFAULT;
            }
            if (vc->h3_alt_svc == H3_FLAG_UNSET)
            {
                vc->h3_alt_svc = H3_FLAG_ON;
            }
            if (vc->h3_address_validation == H3_FLAG_UNSET)
            {
                vc->h3_address_validation = H3_FLAG_ON;
            }
            if (vc->h3_alt_svc_max_age == 0)
            {
                vc->h3_alt_svc_max_age = H3_ALT_SVC_MAX_AGE_DEFAULT;
            }
            if (vc->h3_handshake_timeout == 0)
            {
                vc->h3_handshake_timeout = H3_HANDSHAKE_TIMEOUT_DEFAULT;
            }
            if (vc->h3_idle_timeout == 0)
            {
                vc->h3_idle_timeout = H3_IDLE_TIMEOUT_DEFAULT;
            }
            conf = vc;
            break;
        }
    }

    CHECK(conf && conf->h3_cert_path && conf->h3_key_path, return HTTP_INTERNAL_SERVER_ERROR;);

    if (conf->h3_quic_engine && !quic_select(conf->h3_quic_engine))
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_http3: H3QuicEngine %s: this build has no such engine (compiled: %s)", conf->h3_quic_engine, engine_list(ptemp));
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    /* Validate cert and key files are readable */
    apr_file_t* f = NULL;
    if (apr_file_open(&f, conf->h3_cert_path, APR_READ, APR_OS_DEFAULT, ptemp) != APR_SUCCESS)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_http3: H3CertificatePath not readable: %s", conf->h3_cert_path);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    apr_file_close(f);
    f = NULL;

    if (apr_file_open(&f, conf->h3_key_path, APR_READ, APR_OS_DEFAULT, ptemp) != APR_SUCCESS)
    {
        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "mod_http3: H3CertificateKeyPath not readable: %s", conf->h3_key_path);
        return HTTP_INTERNAL_SERVER_ERROR;
    }
    apr_file_close(f);

    ap_log_error(APLOG_MARK, APLOG_INFO, 0, s, "h3_post_config: pid=%d cert=%s key=%s h3_port=%d mpm=%s threaded=%d forked=%d max_threads=%d", getpid(), conf->h3_cert_path, conf->h3_key_path, (int)conf->h3_port, ap_show_mpm(), mpm_query(AP_MPMQ_IS_THREADED), mpm_query(AP_MPMQ_IS_FORKED),
                 mpm_query(AP_MPMQ_MAX_THREADS));
    return OK;
}

void* h3_create_dir_config(apr_pool_t* p, char* /*dir*/)
{
    CHECK(p);
    return apr_pcalloc(p, 1);
}

void* h3_merge_dir_config(apr_pool_t* /*p*/, void* base, void* /*add*/)
{
    CHECK(base);
    return base;
}

const command_rec cmd_1 = AP_INIT_TAKE1("H3CertificatePath", set_h3_cert_path, NULL, RSRC_CONF, "Path to the SSL certificate file for HTTP/3");
const command_rec cmd_2 = AP_INIT_TAKE1("H3CertificateKeyPath", set_h3_key_path, NULL, RSRC_CONF, "Path to the SSL certificate key file for HTTP/3");
const command_rec cmd_3 = AP_INIT_TAKE1("H3Port", set_h3_port, NULL, RSRC_CONF, "UDP port to listen on for QUIC/HTTP-3 (default: same as main server)");
const command_rec cmd_4 = AP_INIT_TAKE1("H3MaxConcurrentStreams", set_h3_max_concurrent_streams, NULL, RSRC_CONF, "Maximum number of concurrent HTTP/3 streams per connection (default: 100)");
const command_rec cmd_5 = AP_INIT_TAKE1("H3MaxConnections", set_h3_max_connections, NULL, RSRC_CONF, "Maximum concurrent QUIC/HTTP/3 connections per child process (default: 256)");
const command_rec cmd_6 = AP_INIT_TAKE1("H3StreamBufferSize", set_h3_stream_buffer_size, NULL, RSRC_CONF, "Per-stream request and streaming-response buffer size in bytes (default: 65536)");
const command_rec cmd_7 = AP_INIT_TAKE1("H3MaxRequestBodySize", set_h3_max_request_body_size, NULL, RSRC_CONF, "Maximum HTTP/3 request body size in bytes, fully buffered in memory (default: 10485760)");
const command_rec cmd_8 = AP_INIT_FLAG("H3AltSvc", set_h3_alt_svc, NULL, RSRC_CONF, "Whether to advertise HTTP/3 support via an Alt-Svc response header, required for browser discovery (default: on)");
const command_rec cmd_9 = AP_INIT_TAKE1("H3AltSvcMaxAge", set_h3_alt_svc_max_age, NULL, RSRC_CONF, "Seconds a client may cache the Alt-Svc HTTP/3 advertisement for (default: 86400)");
const command_rec cmd_10 = AP_INIT_TAKE1("H3HandshakeTimeout", set_h3_handshake_timeout, NULL, RSRC_CONF, "Timeout in seconds for QUIC handshakes to complete (default: 10)");
const command_rec cmd_11 = AP_INIT_TAKE1("H3IdleTimeout", set_h3_idle_timeout, NULL, RSRC_CONF, "Idle timeout in seconds for QUIC connections (default: 300)");
const command_rec cmd_12 = AP_INIT_TAKE1("H3MaxResponseBodySize", set_h3_max_response_body_size, NULL, RSRC_CONF, "Maximum HTTP/3 response body size in bytes; an explicit limit enables bounded whole-response buffering (default: unlimited streaming)");
const command_rec cmd_13 = AP_INIT_FLAG("H3AddressValidation", set_h3_address_validation, NULL, RSRC_CONF, "Whether to validate client addresses with a QUIC Retry packet before accepting a connection (default: on)");

const command_rec cmd_14 = AP_INIT_TAKE1("H3QuicEngine", set_h3_quic_engine, NULL, RSRC_CONF, "QUIC engine to run, among those compiled in (default: openssl)");

const command_rec cmd_end = AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL);
const command_rec h3_cmds[] = {cmd_1, cmd_2, cmd_3, cmd_4, cmd_5, cmd_6, cmd_7, cmd_8, cmd_9, cmd_10, cmd_11, cmd_12, cmd_13, cmd_14, cmd_end};