/
githubmirror
/
nghttp2
Обзор
Документация
Войти
/
githubmirror
/
nghttp2
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/shrpx_worker_process.cc
712 строк
21 KB
Tatsuhiro Tsujikawa
nghttpx: Fix build error with gcc-16
30 июн 2026, 13:35
30 июн 2026, 13:35
775e7a7
Код
Авторство
О чём код?
/* * nghttp2 - HTTP/2 C Library * * Copyright (c) 2015 Tatsuhiro Tsujikawa * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "shrpx_worker_process.h" #include <sys/types.h> #ifdef HAVE_UNISTD_H # include <unistd.h> #endif // defined(HAVE_UNISTD_H) #include <sys/resource.h> #include <sys/wait.h> #include <grp.h> #include <cinttypes> #include <cstdlib> #include "ssl_compat.h" #ifdef NGHTTP2_OPENSSL_IS_WOLFSSL # include <wolfssl/options.h> # include <wolfssl/openssl/rand.h> #else // !defined(NGHTTP2_OPENSSL_IS_WOLFSSL) # include <openssl/rand.h> #endif // !defined(NGHTTP2_OPENSSL_IS_WOLFSSL) #include <ev.h> #include <ares.h> #include "shrpx_config.h" #include "shrpx_connection_handler.h" #include "shrpx_log_config.h" #include "shrpx_worker.h" #include "shrpx_accept_handler.h" #include "shrpx_http2_upstream.h" #include "shrpx_http2_session.h" #include "shrpx_memcached_dispatcher.h" #include "shrpx_memcached_request.h" #include "shrpx_process.h" #include "shrpx_tls.h" #include "shrpx_log.h" #include "util.h" #include "app_helper.h" #include "template.h" #include "xsi_strerror.h" using namespace nghttp2; namespace shrpx { namespace { void drop_privileges( #ifdef HAVE_NEVERBLEED neverbleed_t *nb #endif // defined(HAVE_NEVERBLEED) ) { std::array<char, STRERROR_BUFSIZE> errbuf; auto config = get_config(); if (getuid() == 0 && config->uid != 0) { #ifdef HAVE_NEVERBLEED if (nb) { neverbleed_setuidgid(nb, config->user.data(), 1); } #endif // defined(HAVE_NEVERBLEED) if (initgroups(config->user.data(), #ifndef __APPLE__ config->gid #else // defined(__APPLE__) static_cast<int>(config->gid) #endif // defined(__APPLE__) ) != 0) { auto error = errno; Log{FATAL} << "Could not change supplementary groups: " << xsi_strerror(error, errbuf.data(), errbuf.size()); exit(EXIT_FAILURE); } if (setgid(config->gid) != 0) { auto error = errno; Log{FATAL} << "Could not change gid: " << xsi_strerror(error, errbuf.data(), errbuf.size()); exit(EXIT_FAILURE); } if (setuid(config->uid) != 0) { auto error = errno; Log{FATAL} << "Could not change uid: " << xsi_strerror(error, errbuf.data(), errbuf.size()); exit(EXIT_FAILURE); } if (setuid(0) != -1) { Log{FATAL} << "Still have root privileges?"; exit(EXIT_FAILURE); } } } } // namespace namespace { void graceful_shutdown(ConnectionHandler *conn_handler) { if (conn_handler->get_graceful_shutdown()) { return; } Log{NOTICE} << "Graceful shutdown signal received"; conn_handler->set_graceful_shutdown(true); conn_handler->graceful_shutdown_worker(); auto single_worker = conn_handler->get_single_worker(); if (single_worker) { auto worker_stat = single_worker->get_worker_stat(); if (worker_stat->num_connections == 0 && worker_stat->num_close_waits == 0) { ev_break(conn_handler->get_loop()); } return; } } } // namespace namespace { void reopen_log(ConnectionHandler *conn_handler) { Log{NOTICE} << "Reopening log files: worker process (thread main)"; auto config = get_config(); auto &loggingconf = config->logging; (void)reopen_log_files(loggingconf); redirect_stderr_to_errorlog(loggingconf); conn_handler->worker_reopen_log_files(); } } // namespace namespace { void ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) { auto conn_handler = static_cast<ConnectionHandler *>(w->data); std::array<uint8_t, 1024> buf; ssize_t nread; while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR) ; if (nread == -1) { auto error = errno; Log{ERROR} << "Failed to read data from ipc channel: errno=" << error; if (error == ECONNRESET) { Log{FATAL} << "IPC socket connection was reset. Perform immediate shutdown."; nghttp2_Exit(EXIT_FAILURE); } return; } if (nread == 0) { // IPC socket closed. Perform immediate shutdown. Log{FATAL} << "IPC socket is closed. Perform immediate shutdown."; nghttp2_Exit(EXIT_FAILURE); } for (size_t i = 0; i < as_unsigned(nread); ++i) { switch (buf[i]) { case SHRPX_IPC_GRACEFUL_SHUTDOWN: graceful_shutdown(conn_handler); break; case SHRPX_IPC_REOPEN_LOG: reopen_log(conn_handler); break; } } } } // namespace #ifdef ENABLE_HTTP3 namespace { void quic_ipc_readcb(struct ev_loop *loop, ev_io *w, int revents) { auto conn_handler = static_cast<ConnectionHandler *>(w->data); if (!conn_handler->quic_ipc_read()) { Log{ERROR} << "Failed to read data from QUIC IPC channel"; return; } } } // namespace #endif // defined(ENABLE_HTTP3) namespace { std::expected<void, Error> generate_ticket_key(TicketKey &ticket_key) { ticket_key.cipher = get_config()->tls.ticket.cipher; ticket_key.hmac = nghttp2::tls::sha256(); ticket_key.hmac_keylen = static_cast<size_t>(EVP_MD_size(ticket_key.hmac)); assert(static_cast<size_t>(EVP_CIPHER_key_length(ticket_key.cipher)) <= ticket_key.data.enc_key.size()); assert(ticket_key.hmac_keylen <= ticket_key.data.hmac_key.size()); if (log_enabled(INFO)) { Log{INFO} << "enc_keylen=" << EVP_CIPHER_key_length(ticket_key.cipher) << ", hmac_keylen=" << ticket_key.hmac_keylen; } if (RAND_bytes(reinterpret_cast<unsigned char *>(&ticket_key.data), sizeof(ticket_key.data)) == 0) { return std::unexpected{Error::CRYPTO}; } return {}; } } // namespace namespace { void renew_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) { auto conn_handler = static_cast<ConnectionHandler *>(w->data); const auto &old_ticket_keys = conn_handler->get_ticket_keys(); auto ticket_keys = std::make_shared<TicketKeys>(); Log{NOTICE} << "Renew new ticket keys"; // If old_ticket_keys is not empty, it should contain at least 2 // keys: one for encryption, and last one for the next encryption // key but decryption only. The keys in between are old keys and // decryption only. The next key is provided to ensure to mitigate // possible problem when one worker encrypt new key, but one worker, // which did not take the that key yet, and cannot decrypt it. // // We keep keys for get_config()->tls_session_timeout seconds. The // default is 12 hours. Thus the maximum ticket vector size is 12. if (old_ticket_keys) { auto &old_keys = old_ticket_keys->keys; auto &new_keys = ticket_keys->keys; assert(!old_keys.empty()); auto max_tickets = static_cast<size_t>(std::chrono::duration_cast<std::chrono::hours>( get_config()->tls.session_timeout) .count()); new_keys.resize(std::min(max_tickets, old_keys.size() + 1)); std::ranges::copy_n(std::ranges::begin(old_keys), as_signed(new_keys.size() - 1), std::ranges::begin(new_keys) + 1); } else { ticket_keys->keys.resize(1); } auto &new_key = ticket_keys->keys[0]; if (!generate_ticket_key(new_key)) { if (log_enabled(INFO)) { Log{INFO} << "failed to generate ticket key"; } conn_handler->set_ticket_keys(nullptr); conn_handler->set_ticket_keys_to_worker(nullptr); return; } if (log_enabled(INFO)) { Log{INFO} << "ticket keys generation done"; assert(ticket_keys->keys.size() >= 1); Log{INFO} << 0 << " enc+dec: " << util::format_hex(ticket_keys->keys[0].data.name); for (size_t i = 1; i < ticket_keys->keys.size(); ++i) { auto &key = ticket_keys->keys[i]; Log{INFO} << i << " dec: " << util::format_hex(key.data.name); } } conn_handler->set_ticket_keys(ticket_keys); conn_handler->set_ticket_keys_to_worker(ticket_keys); } } // namespace namespace { void memcached_get_ticket_key_cb(struct ev_loop *loop, ev_timer *w, int revents) { auto conn_handler = static_cast<ConnectionHandler *>(w->data); auto dispatcher = conn_handler->get_tls_ticket_key_memcached_dispatcher(); auto req = std::make_unique<MemcachedRequest>(); req->key = "nghttpx:tls-ticket-key"; req->op = MemcachedOp::GET; req->cb = [conn_handler, w](MemcachedRequest *req, MemcachedResult res) { switch (res.status_code) { case MemcachedStatusCode::NO_ERROR: break; case MemcachedStatusCode::EXT_NETWORK_ERROR: conn_handler->on_tls_ticket_key_network_error(w); return; default: conn_handler->on_tls_ticket_key_not_found(w); return; } // |version (4bytes)|len (2bytes)|key (variable length)|... // (len, key) pairs are repeated as necessary. auto &value = res.value; if (value.size() < 4) { Log{WARN} << "Memcached: tls ticket key value is too small: got " << value.size(); conn_handler->on_tls_ticket_key_not_found(w); return; } auto p = value.data(); auto version = util::get_uint32(p); // Currently supported version is 1. if (version != 1) { Log{WARN} << "Memcached: tls ticket key version: want 1, got " << version; conn_handler->on_tls_ticket_key_not_found(w); return; } auto end = p + value.size(); p += 4; auto &ticketconf = get_config()->tls.ticket; size_t expectedlen; size_t enc_keylen; size_t hmac_keylen; if (ticketconf.cipher == nghttp2::tls::aes_128_cbc()) { expectedlen = 48; enc_keylen = 16; hmac_keylen = 16; } else if (ticketconf.cipher == nghttp2::tls::aes_256_cbc()) { expectedlen = 80; enc_keylen = 32; hmac_keylen = 32; } else { return; } auto ticket_keys = std::make_shared<TicketKeys>(); for (; p != end;) { if (end - p < 2) { Log{WARN} << "Memcached: tls ticket key data is too small"; conn_handler->on_tls_ticket_key_not_found(w); return; } auto len = util::get_uint16(p); p += 2; if (len != expectedlen) { Log{WARN} << "Memcached: wrong tls ticket key size: want " << expectedlen << ", got " << len; conn_handler->on_tls_ticket_key_not_found(w); return; } if (p + len > end) { Log{WARN} << "Memcached: too short tls ticket key payload: want " << len << ", got " << (end - p); conn_handler->on_tls_ticket_key_not_found(w); return; } auto key = TicketKey(); key.cipher = ticketconf.cipher; key.hmac = nghttp2::tls::sha256(); key.hmac_keylen = hmac_keylen; p = std::ranges::copy_n(p, key.data.name.size(), std::ranges::begin(key.data.name)) .in; p = std::ranges::copy_n(p, as_signed(enc_keylen), std::ranges::begin(key.data.enc_key)) .in; p = std::ranges::copy_n(p, as_signed(hmac_keylen), std::ranges::begin(key.data.hmac_key)) .in; ticket_keys->keys.push_back(std::move(key)); } conn_handler->on_tls_ticket_key_get_success(ticket_keys, w); }; if (log_enabled(INFO)) { Log{INFO} << "Memcached: tls ticket key get request sent"; } (void)dispatcher->add_request(std::move(req)); } } // namespace #ifdef HAVE_NEVERBLEED namespace { void nb_child_cb(struct ev_loop *loop, ev_child *w, int revents) { log_chld(w->rpid, w->rstatus, "neverbleed process"); ev_child_stop(loop, w); Log{FATAL} << "neverbleed process exited; aborting now"; nghttp2_Exit(EXIT_FAILURE); } } // namespace #endif // defined(HAVE_NEVERBLEED) namespace { std::expected<void, Error> send_ready_event(int ready_ipc_fd) { std::array<char, STRERROR_BUFSIZE> errbuf; auto pid = getpid(); ssize_t nwrite; while ((nwrite = write(ready_ipc_fd, &pid, sizeof(pid))) == -1 && errno == EINTR) ; if (nwrite < 0) { auto error = errno; Log{ERROR} << "Writing PID to ready IPC channel failed: " << xsi_strerror(error, errbuf.data(), errbuf.size()); return std::unexpected{Error::SYSCALL}; } return {}; } } // namespace std::expected<void, Error> worker_process_event_loop(WorkerProcessConfig *wpconf) { int rv; std::array<char, STRERROR_BUFSIZE> errbuf; (void)errbuf; auto config = get_config(); if (auto rv = reopen_log_files(config->logging); !rv) { Log{FATAL} << "Failed to open log file"; return rv; } rv = ares_library_init(ARES_LIB_INIT_ALL); if (rv != 0) { Log{FATAL} << "ares_library_init failed: " << ares_strerror(rv); return std::unexpected{Error::DNS}; } auto loop = EV_DEFAULT; auto gen = util::make_mt19937(); #ifdef HAVE_NEVERBLEED std::array<char, NEVERBLEED_ERRBUF_SIZE> nb_errbuf; auto nb = std::make_unique<neverbleed_t>(); if (neverbleed_init(nb.get(), nb_errbuf.data()) != 0) { Log{FATAL} << "neverbleed_init failed: " << nb_errbuf.data(); return std::unexpected{Error::CRYPTO}; } Log{NOTICE} << "neverbleed process [" << nb->daemon_pid << "] spawned"; ev_child nb_childev; ev_child_init(&nb_childev, nb_child_cb, nb->daemon_pid, 0); nb_childev.data = nullptr; ev_child_start(loop, &nb_childev); #endif // defined(HAVE_NEVERBLEED) auto conn_handler = std::make_unique<ConnectionHandler>(loop, gen); #ifdef HAVE_NEVERBLEED conn_handler->set_neverbleed(nb.get()); #endif // defined(HAVE_NEVERBLEED) #ifdef ENABLE_HTTP3 conn_handler->set_quic_ipc_fd(wpconf->quic_ipc_fd); conn_handler->set_quic_lingering_worker_processes( wpconf->quic_lingering_worker_processes); #endif // defined(ENABLE_HTTP3) MemchunkPool mcpool; ev_timer renew_ticket_key_timer; if (tls::upstream_tls_enabled(config->conn)) { auto &ticketconf = config->tls.ticket; auto &memcachedconf = ticketconf.memcached; if (!memcachedconf.host.empty()) { SSL_CTX *ssl_ctx = nullptr; if (memcachedconf.tls) { ssl_ctx = conn_handler->create_tls_ticket_key_memcached_ssl_ctx(); } conn_handler->set_tls_ticket_key_memcached_dispatcher( std::make_unique<MemcachedDispatcher>(&ticketconf.memcached.addr, loop, ssl_ctx, memcachedconf.host, &mcpool, gen)); ev_timer_init(&renew_ticket_key_timer, memcached_get_ticket_key_cb, 0., 0.); renew_ticket_key_timer.data = conn_handler.get(); // Get first ticket keys. memcached_get_ticket_key_cb(loop, &renew_ticket_key_timer, 0); } else { bool auto_tls_ticket_key = true; if (!ticketconf.files.empty()) { if (!ticketconf.cipher_given) { Log{WARN} << "It is strongly recommended to specify " "--tls-ticket-key-cipher=aes-128-cbc (or " "tls-ticket-key-cipher=aes-128-cbc in configuration file) " "when --tls-ticket-key-file is used for the smooth " "transition when the default value of --tls-ticket-key-cipher " "becomes aes-256-cbc"; } auto ticket_keys = read_tls_ticket_key_file( ticketconf.files, ticketconf.cipher, nghttp2::tls::sha256()); if (!ticket_keys) { Log{WARN} << "Use internal session ticket key generator"; } else { conn_handler->set_ticket_keys(std::move(ticket_keys)); auto_tls_ticket_key = false; } } if (auto_tls_ticket_key) { // Generate new ticket key every 1hr. ev_timer_init(&renew_ticket_key_timer, renew_ticket_key_cb, 0., 1_h); renew_ticket_key_timer.data = conn_handler.get(); ev_timer_again(loop, &renew_ticket_key_timer); // Generate first session ticket key before running workers. renew_ticket_key_cb(loop, &renew_ticket_key_timer, 0); } } } #ifdef ENABLE_HTTP3 auto &quicconf = config->quic; std::unique_ptr<QUICKeyingMaterials> qkms; if (!quicconf.upstream.secret_file.empty()) { auto maybe_qkms = read_quic_secret_file(quicconf.upstream.secret_file); if (!maybe_qkms) { Log{WARN} << "Use QUIC keying materials generated internally"; } else { qkms = std::move(*maybe_qkms); } } if (!qkms) { qkms = std::make_unique<QUICKeyingMaterials>(); qkms->keying_materials.resize(1); auto &qkm = qkms->keying_materials.front(); if (RAND_bytes(qkm.reserved.data(), static_cast<nghttp2_ssl_rand_length_type>( qkm.reserved.size())) != 1) { Log{ERROR} << "Failed to generate QUIC secret reserved data"; return std::unexpected{Error::CRYPTO}; } if (RAND_bytes(qkm.secret.data(), static_cast<nghttp2_ssl_rand_length_type>( qkm.secret.size())) != 1) { Log{ERROR} << "Failed to generate QUIC secret"; return std::unexpected{Error::CRYPTO}; } if (RAND_bytes(qkm.salt.data(), static_cast<nghttp2_ssl_rand_length_type>( qkm.salt.size())) != 1) { Log{ERROR} << "Failed to generate QUIC salt"; return std::unexpected{Error::CRYPTO}; } } for (auto &qkm : qkms->keying_materials) { if (auto rv = qkm.init_ciphers(); !rv) { return rv; } } conn_handler->set_quic_keying_materials(std::move(qkms)); conn_handler->set_worker_ids(wpconf->worker_ids); conn_handler->set_quic_lingering_worker_processes( wpconf->quic_lingering_worker_processes); #endif // defined(ENABLE_HTTP3) if (config->single_thread) { if (auto rv = conn_handler->create_single_worker(); !rv) { return rv; } } else { #ifndef NOTHREADS sigset_t set; sigemptyset(&set); sigaddset(&set, SIGCHLD); rv = pthread_sigmask(SIG_BLOCK, &set, nullptr); if (rv != 0) { Log{ERROR} << "Blocking SIGCHLD failed: " << xsi_strerror(rv, errbuf.data(), errbuf.size()); return std::unexpected{Error::LIBC}; } #endif // !defined(NOTHREADS) if (auto rv = conn_handler->create_worker_thread(config->num_worker); !rv) { return rv; } #ifndef NOTHREADS rv = pthread_sigmask(SIG_UNBLOCK, &set, nullptr); if (rv != 0) { Log{ERROR} << "Unblocking SIGCHLD failed: " << xsi_strerror(rv, errbuf.data(), errbuf.size()); return std::unexpected{Error::LIBC}; } #endif // !defined(NOTHREADS) } // UNIX domain sockets are copied in AcceptHandler. No need to keep // the original. for (auto &addr : config->conn.listener.addrs) { if (addr.host_unix) { close(addr.fd); } } #if defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) conn_handler->unload_bpf_objects(); #endif // defined(ENABLE_HTTP3) && defined(HAVE_LIBBPF) drop_privileges( #ifdef HAVE_NEVERBLEED nb.get() #endif // defined(HAVE_NEVERBLEED) ); ev_io ipcev; ev_io_init(&ipcev, ipc_readcb, wpconf->ipc_fd, EV_READ); ipcev.data = conn_handler.get(); ev_io_start(loop, &ipcev); #ifdef ENABLE_HTTP3 ev_io quic_ipcev; ev_io_init(&quic_ipcev, quic_ipc_readcb, wpconf->quic_ipc_fd, EV_READ); quic_ipcev.data = conn_handler.get(); ev_io_start(loop, &quic_ipcev); #endif // defined(ENABLE_HTTP3) if (log_enabled(INFO)) { Log{INFO} << "Entering event loop"; } if (auto rv = send_ready_event(wpconf->ready_ipc_fd); !rv) { return rv; } ev_run(loop, 0); // Destroy SSL_CTX held in conn_handler before killing neverbleed // daemon. Otherwise priv_rsa_finish yields "write error" and // worker process aborts. conn_handler.reset(); #ifdef HAVE_NEVERBLEED assert(nb->daemon_pid > 0); rv = kill(nb->daemon_pid, SIGTERM); if (rv != 0) { auto error = errno; Log{ERROR} << "Could not send signal to neverbleed daemon: errno=" << error; } while ((rv = waitpid(nb->daemon_pid, nullptr, 0)) == -1 && errno == EINTR) ; if (rv == -1) { auto error = errno; Log{ERROR} << "Error occurred while we were waiting for the completion " "of neverbleed process: errno=" << error; } #endif // defined(HAVE_NEVERBLEED) ares_library_cleanup(); return {}; } } // namespace shrpx