mod_http3/src/h3_socket.c¶
Functions¶
| Name | |
|---|---|
| void | tune_buffers(apr_socket_t * sock, apr_size_t want, apr_port_t port, apr_pool_t * pool) |
| apr_status_t | h3_socket_open(apr_port_t port, apr_size_t buffer_size, apr_pool_t * pool, int * out_fd) |
| void | h3_socket_close(int fd) |
| apr_status_t | h3_wakeup_create(apr_pool_t * pool, h3_wakeup * w) |
| void | h3_wakeup_signal(h3_wakeup * w) |
| void | h3_wakeup_drain(h3_wakeup * w) |
Functions Documentation¶
function tune_buffers¶
static void tune_buffers(
apr_socket_t * sock,
apr_size_t want,
apr_port_t port,
apr_pool_t * pool
)
function h3_socket_open¶
apr_status_t h3_socket_open(
apr_port_t port,
apr_size_t buffer_size,
apr_pool_t * pool,
int * out_fd
)
Parameters:
- port Port to bind; 0 lets the OS pick.
- buffer_size Bytes requested for SO_RCVBUF and SO_SNDBUF; 0 keeps the OS default.
- pool Pool used for the underlying apr_socket_t lifetime.
- out_fd Out parameter: the resulting OS-level fd (suitable for SSL_set_fd on OpenSSL QUIC).
Return: APR_SUCCESS, APR_EAGAIN if the port is already bound, or another APR error code.
Open a non-blocking IPv6 dual-stack UDP socket and bind it to the given port. Sets SO_REUSEADDR; on non-Windows also tries SO_REUSEPORT so multiple children can share the port. Also asks for buffer_size on the send and receive buffers, which the OS may cap; a refused or capped buffer is logged, never fatal.
function h3_socket_close¶
Parameters:
- fd The OS-level fd to close.
Close a UDP socket previously returned by h3_socket_open. Safe to call with a negative fd (no-op).
function h3_wakeup_create¶
Parameters:
- pool Pool owning both sockets.
- w Out: the initialized pair; reset to reader_fd -1 on failure.
Return: APR_SUCCESS or an APR error code.
Create a wakeup pair on loopback. Both sockets are non-blocking.
function h3_wakeup_signal¶
Parameters:
- w The pair to signal; NULL or uncreated is ignored.
Wake the event thread. Safe from any thread; a blocked send is dropped.
function h3_wakeup_drain¶
Parameters:
- w The pair to drain; NULL or uncreated is ignored.
Discard everything queued on the reader, after poll reports it readable.
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 <apr_portable.h>
#include <string.h>
#include "h3_check.h"
#include "h3_os.h"
#include "h3_socket.h"
static void tune_buffers(apr_socket_t* sock, apr_size_t want, apr_port_t port, apr_pool_t* pool)
{
if (want == 0 || want > (apr_size_t)APR_INT32_MAX)
{
return;
}
apr_os_sock_t os_sock = -1;
int have_fd = apr_os_sock_get(&os_sock, sock) == APR_SUCCESS;
static const apr_int32_t opts[] = {APR_SO_RCVBUF, APR_SO_SNDBUF};
static const int sys_opts[] = {SO_RCVBUF, SO_SNDBUF};
static const char* const names[] = {"SO_RCVBUF", "SO_SNDBUF"};
for (int i = 0; i < 2; i++)
{
if (apr_socket_opt_set(sock, opts[i], (apr_int32_t)want) != APR_SUCCESS)
{
ap_log_perror(APLOG_MARK, APLOG_INFO, 0, pool, "h3_socket_open(%d): %s could not be set to %" APR_SIZE_T_FMT " bytes, keeping the OS default", (int)port, names[i], want);
continue;
}
int got = 0;
socklen_t got_len = sizeof(got);
if (have_fd && getsockopt(os_sock, SOL_SOCKET, sys_opts[i], (char*)&got, &got_len) == 0 && got > 0)
{
#if defined(__linux__)
got /= 2; /* Linux stores, and reports back, twice what was asked for. */
#endif
if ((apr_size_t)got < want)
{
ap_log_perror(APLOG_MARK, APLOG_INFO, 0, pool, "h3_socket_open(%d): %s granted %d bytes of the %" APR_SIZE_T_FMT " requested; raise the OS limit to grant more", (int)port, names[i], got, want);
}
}
}
}
apr_status_t h3_socket_open(apr_port_t port, apr_size_t buffer_size, apr_pool_t* pool, int* out_fd)
{
CHECK(pool);
CHECK(out_fd);
apr_socket_t* sock = NULL;
apr_status_t rv = apr_socket_create(&sock, APR_INET6, SOCK_DGRAM, APR_PROTO_UDP, pool);
if (rv != APR_SUCCESS)
{
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, "h3_socket_open: apr_socket_create failed");
return rv;
}
apr_socket_opt_set(sock, APR_IPV6_V6ONLY, 0);
tune_buffers(sock, buffer_size, port, pool);
apr_sockaddr_t* addr = NULL;
rv = apr_sockaddr_info_get(&addr, NULL, APR_INET6, port, 0, pool);
if (rv != APR_SUCCESS)
{
apr_socket_close(sock);
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, "h3_socket_open: apr_sockaddr_info_get failed");
return rv;
}
rv = apr_socket_bind(sock, addr);
if (rv != APR_SUCCESS)
{
apr_socket_close(sock);
if (h3_socket_os_is_eaddrinuse(rv))
{
ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, pool, "bind(%d) skipped (port already owned)", (int)port);
return APR_EAGAIN;
}
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, "bind(%d) failed", (int)port);
return rv;
}
rv = apr_socket_timeout_set(sock, 0);
if (rv != APR_SUCCESS)
{
apr_socket_close(sock);
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, "set nonblocking on %d failed", (int)port);
return rv;
}
apr_os_sock_t os_sock;
rv = apr_os_sock_get(&os_sock, sock);
if (rv != APR_SUCCESS)
{
apr_socket_close(sock);
ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool, "h3_socket_open: apr_os_sock_get failed");
return rv;
}
*out_fd = (int)os_sock;
return APR_SUCCESS;
}
void h3_socket_close(int fd)
{
if (fd >= 0)
{
h3_socket_os_close(fd);
}
}
apr_status_t h3_wakeup_create(apr_pool_t* pool, h3_wakeup* w)
{
CHECK(pool);
CHECK(w);
memset(w, 0, sizeof(*w));
w->reader_fd = -1;
apr_sockaddr_t* loopback = NULL;
apr_status_t rv = apr_sockaddr_info_get(&loopback, "127.0.0.1", APR_INET, 0, 0, pool);
if (rv != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_create(&w->reader, APR_INET, SOCK_DGRAM, APR_PROTO_UDP, pool)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_bind(w->reader, loopback)) != APR_SUCCESS)
{
goto fail;
}
apr_sockaddr_t* bound = NULL;
if ((rv = apr_socket_addr_get(&bound, APR_LOCAL, w->reader)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_create(&w->writer, APR_INET, SOCK_DGRAM, APR_PROTO_UDP, pool)) != APR_SUCCESS)
{
goto fail;
}
/* The writer needs a fresh address; the reader's had its port rewritten. */
apr_sockaddr_t* writer_bind = NULL;
if ((rv = apr_sockaddr_info_get(&writer_bind, "127.0.0.1", APR_INET, 0, 0, pool)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_bind(w->writer, writer_bind)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_connect(w->writer, bound)) != APR_SUCCESS)
{
goto fail;
}
/* Connect both ends, so only the writer can reach the reader. */
apr_sockaddr_t* writer_addr = NULL;
if ((rv = apr_socket_addr_get(&writer_addr, APR_LOCAL, w->writer)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_connect(w->reader, writer_addr)) != APR_SUCCESS)
{
goto fail;
}
if ((rv = apr_socket_timeout_set(w->reader, 0)) != APR_SUCCESS || (rv = apr_socket_timeout_set(w->writer, 0)) != APR_SUCCESS)
{
goto fail;
}
apr_os_sock_t os_sock;
if ((rv = apr_os_sock_get(&os_sock, w->reader)) != APR_SUCCESS)
{
goto fail;
}
w->reader_fd = os_sock;
return APR_SUCCESS;
fail:
/* The pool closes the sockets; restore the uncreated state. */
memset(w, 0, sizeof(*w));
w->reader_fd = -1;
return rv;
}
void h3_wakeup_signal(h3_wakeup* w)
{
if (!w || !w->writer)
{
return;
}
char byte = '1';
apr_size_t len = 1;
(void)apr_socket_send(w->writer, &byte, &len);
}
void h3_wakeup_drain(h3_wakeup* w)
{
if (!w || !w->reader)
{
return;
}
/* Bounded drain; leftovers cost one more poll wakeup at most. */
char buf[64];
for (int i = 0; i < 64; i++)
{
apr_size_t len = sizeof(buf);
if (apr_socket_recv(w->reader, buf, &len) != APR_SUCCESS || len == 0)
{
return;
}
}
}