| /* 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 <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; | |
| } | |
| } | |
| } |