blob: df2ecf3dfe47a13c830a25b211040cdba09494b4 [file]
/* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 <http_config.h>
#include <http_log.h>
#include <apr_pools.h>
#include <apr_thread_proc.h>
#include <apr_time.h>
#include "h3.h"
#include "h3_check.h"
#include "h3_config.h"
#include "h3_io.h"
#include "h3_os.h"
#include "h3_server.h"
#include "h3_socket.h"
#include "h3_threads.h"
#include "mod_http3.h"
static volatile int child_stopping = 0;
static apr_thread_t* port_acquire_thread = NULL;
struct port_acquire_args
{
apr_pool_t* pool;
server_rec* vhost;
h3_server_conf* conf;
};
static void* APR_THREAD_FUNC port_acquire_thread_fn(apr_thread_t* thread H3_UNUSED, void* data)
{
struct port_acquire_args* args = data;
/* Each attempt allocates; clear them, or retrying leaks for the life
* of the child. A successful attempt's socket stays in ptry. */
apr_pool_t* ptry = NULL;
if (apr_pool_create(&ptry, args->pool) != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, args->vhost, "port acquirer: could not create a pool");
return NULL;
}
while (!child_stopping)
{
int udp_fd = -1;
apr_status_t rv = h3_socket_open(args->conf->h3_port, args->conf->h3_socket_buffer_size, ptry, &udp_fd);
if (rv == APR_EAGAIN)
{
apr_pool_clear(ptry);
apr_sleep(apr_time_from_msec(H3_PORT_ACQUIRE_RETRY_MS));
continue;
}
if (rv != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, args->vhost, "port acquirer: h3_socket_open failed, giving up");
break;
}
if (child_stopping)
{
h3_socket_close(udp_fd);
break;
}
if (h3_io_listen_start(args->pool, args->vhost, args->conf, udp_fd) != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, args->vhost, "port acquirer: h3_io_listen_start failed");
h3_socket_close(udp_fd);
break;
}
ap_log_error(APLOG_MARK, APLOG_INFO, 0, args->vhost, "port acquirer: pid=%d acquired QUIC port after retrying", h3_getpid());
break;
}
return NULL;
}
static h3_server_conf* find_h3_server(server_rec* s, server_rec** out_server)
{
h3_server_conf* conf = NULL;
server_rec* current = s;
while (current)
{
h3_server_conf* tmp = ap_get_module_config(current->module_config, &http3_module);
if (tmp && tmp->h3_cert_path && tmp->h3_key_path && !conf)
{
conf = tmp;
conf->host_port = get_server_port(current);
*out_server = current;
break;
}
current = current->next;
}
return conf;
}
/* The threads must be gone before the child pool they run from is
* destroyed, and before exit() runs OpenSSL's cleanup under them. Most
* MPMs stop them through the child_stopping hook, but prefork skips that
* hook when a child exits from a signal handler. */
static apr_status_t child_pool_cleanup(void* data H3_UNUSED)
{
h3_c1_child_stopping(NULL, 0);
return APR_SUCCESS;
}
void h3_child_init(apr_pool_t* pchild, server_rec* s)
{
server_rec* vhost = NULL;
h3_server_conf* conf = find_h3_server(s, &vhost);
apr_pool_t* pool = NULL;
int udp_fd = -1;
apr_status_t rv;
if (!conf)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, "h3_child_init: no H3 cert/key configured, skipping");
return;
}
apr_pool_pre_cleanup_register(pchild, NULL, child_pool_cleanup);
if (h3_thread_safe_pool_create(pchild, "h3_child", &pool) != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: could not create the HTTP/3 pool");
return;
}
rv = h3_socket_open(conf->h3_port, conf->h3_socket_buffer_size, pool, &udp_fd);
if (rv == APR_EAGAIN)
{
struct port_acquire_args* args = apr_palloc(pool, sizeof(*args));
apr_threadattr_t* attr = NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, vhost, "h3_child_init: pid=%d port already owned, will keep retrying in background", h3_getpid());
args->pool = pool;
args->vhost = vhost;
args->conf = conf;
if (apr_threadattr_create(&attr, pool) != APR_SUCCESS || apr_thread_create(&port_acquire_thread, attr, port_acquire_thread_fn, args, pool) != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: failed to start port acquirer thread");
port_acquire_thread = NULL;
}
return;
}
if (rv != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: h3_socket_open failed");
return;
}
if (h3_io_listen_start(pool, vhost, conf, udp_fd) != APR_SUCCESS)
{
ap_log_error(APLOG_MARK, APLOG_ERR, 0, vhost, "h3_child_init: h3_io_listen_start failed");
h3_socket_close(udp_fd);
}
}
void h3_c1_child_stopping(apr_pool_t* p H3_UNUSED, int graceful)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "mod_http3: child stopping (graceful=%d)", graceful);
child_stopping = 1;
if (port_acquire_thread)
{
apr_status_t ignored;
apr_thread_join(&ignored, port_acquire_thread);
port_acquire_thread = NULL;
}
if (graceful)
{
h3_io_listen_drain(child_h3_io);
return;
}
h3_io_listen_stop(child_h3_io);
}
#if H3_DEVEL
void h3_c1_child_stopped(apr_pool_t* p H3_UNUSED, int graceful)
{
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL, "mod_http3: child stopped (graceful=%d)", graceful);
h3_io_listen_stop(child_h3_io);
}
#endif