diff options
Diffstat (limited to 'src/shrpx.cc')
-rw-r--r-- | src/shrpx.cc | 5329 |
1 files changed, 5329 insertions, 0 deletions
diff --git a/src/shrpx.cc b/src/shrpx.cc new file mode 100644 index 0000000..b42054c --- /dev/null +++ b/src/shrpx.cc @@ -0,0 +1,5329 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2012 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.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#ifdef HAVE_SYS_SOCKET_H +# include <sys/socket.h> +#endif // HAVE_SYS_SOCKET_H +#include <sys/un.h> +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif // HAVE_NETDB_H +#include <signal.h> +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif // HAVE_NETINET_IN_H +#include <netinet/tcp.h> +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif // HAVE_ARPA_INET_H +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif // HAVE_UNISTD_H +#include <getopt.h> +#ifdef HAVE_SYSLOG_H +# include <syslog.h> +#endif // HAVE_SYSLOG_H +#ifdef HAVE_LIMITS_H +# include <limits.h> +#endif // HAVE_LIMITS_H +#ifdef HAVE_SYS_TIME_H +# include <sys/time.h> +#endif // HAVE_SYS_TIME_H +#include <sys/resource.h> +#ifdef HAVE_LIBSYSTEMD +# include <systemd/sd-daemon.h> +#endif // HAVE_LIBSYSTEMD +#ifdef HAVE_LIBBPF +# include <bpf/libbpf.h> +#endif // HAVE_LIBBPF + +#include <cinttypes> +#include <limits> +#include <cstdlib> +#include <iostream> +#include <fstream> +#include <vector> +#include <initializer_list> +#include <random> + +#include <openssl/ssl.h> +#include <openssl/err.h> +#include <openssl/rand.h> +#include <ev.h> + +#include <nghttp2/nghttp2.h> + +#ifdef ENABLE_HTTP3 +# include <ngtcp2/ngtcp2.h> +# include <nghttp3/nghttp3.h> +#endif // ENABLE_HTTP3 + +#include "shrpx_config.h" +#include "shrpx_tls.h" +#include "shrpx_log_config.h" +#include "shrpx_worker.h" +#include "shrpx_http2_upstream.h" +#include "shrpx_http2_session.h" +#include "shrpx_worker_process.h" +#include "shrpx_process.h" +#include "shrpx_signal.h" +#include "shrpx_connection.h" +#include "shrpx_log.h" +#include "shrpx_http.h" +#include "util.h" +#include "app_helper.h" +#include "tls.h" +#include "template.h" +#include "allocator.h" +#include "ssl_compat.h" +#include "xsi_strerror.h" + +extern char **environ; + +using namespace nghttp2; + +namespace shrpx { + +// Deprecated: Environment variables to tell new binary the listening +// socket's file descriptors. They are not close-on-exec. +constexpr auto ENV_LISTENER4_FD = StringRef::from_lit("NGHTTPX_LISTENER4_FD"); +constexpr auto ENV_LISTENER6_FD = StringRef::from_lit("NGHTTPX_LISTENER6_FD"); + +// Deprecated: Environment variable to tell new binary the port number +// the current binary is listening to. +constexpr auto ENV_PORT = StringRef::from_lit("NGHTTPX_PORT"); + +// Deprecated: Environment variable to tell new binary the listening +// socket's file descriptor if frontend listens UNIX domain socket. +constexpr auto ENV_UNIX_FD = StringRef::from_lit("NGHTTP2_UNIX_FD"); +// Deprecated: Environment variable to tell new binary the UNIX domain +// socket path. +constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH"); + +// Prefix of environment variables to tell new binary the listening +// socket's file descriptor. They are not close-on-exec. For TCP +// socket, the value must be comma separated 2 parameters: tcp,<FD>. +// <FD> is file descriptor. For UNIX domain socket, the value must be +// comma separated 3 parameters: unix,<FD>,<PATH>. <FD> is file +// descriptor. <PATH> is a path to UNIX domain socket. +constexpr auto ENV_ACCEPT_PREFIX = StringRef::from_lit("NGHTTPX_ACCEPT_"); + +// This environment variable contains PID of the original main +// process, assuming that it created this main process as a result of +// SIGUSR2. The new main process is expected to send QUIT signal to +// the original main process to shut it down gracefully. +constexpr auto ENV_ORIG_PID = StringRef::from_lit("NGHTTPX_ORIG_PID"); + +// Prefix of environment variables to tell new binary the QUIC IPC +// file descriptor and CID prefix of the lingering worker process. +// The value must be comma separated parameters: +// <FD>,<CID_PREFIX_0>,<CID_PREFIX_1>,... <FD> is the file +// descriptor. <CID_PREFIX_I> is the I-th CID prefix in hex encoded +// string. +constexpr auto ENV_QUIC_WORKER_PROCESS_PREFIX = + StringRef::from_lit("NGHTTPX_QUIC_WORKER_PROCESS_"); + +#ifndef _KERNEL_FASTOPEN +# define _KERNEL_FASTOPEN +// conditional define for TCP_FASTOPEN mostly on ubuntu +# ifndef TCP_FASTOPEN +# define TCP_FASTOPEN 23 +# endif + +// conditional define for SOL_TCP mostly on ubuntu +# ifndef SOL_TCP +# define SOL_TCP 6 +# endif +#endif + +// This configuration is fixed at the first startup of the main +// process, and does not change after subsequent reloadings. +struct StartupConfig { + // This contains all options given in command-line. + std::vector<std::pair<StringRef, StringRef>> cmdcfgs; + // The current working directory where this process started. + char *cwd; + // The pointer to original argv (not sure why we have this?) + char **original_argv; + // The pointer to argv, this is a deep copy of original argv. + char **argv; + // The number of elements in argv. + int argc; +}; + +namespace { +StartupConfig suconfig; +} // namespace + +struct InheritedAddr { + // IP address if TCP socket. Otherwise, UNIX domain socket path. + StringRef host; + uint16_t port; + // true if UNIX domain socket path + bool host_unix; + int fd; + bool used; +}; + +namespace { +void signal_cb(struct ev_loop *loop, ev_signal *w, int revents); +} // namespace + +namespace { +void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents); +} // namespace + +struct WorkerProcess { + WorkerProcess(struct ev_loop *loop, pid_t worker_pid, int ipc_fd +#ifdef ENABLE_HTTP3 + , + int quic_ipc_fd, + const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> + &cid_prefixes +#endif // ENABLE_HTTP3 + ) + : loop(loop), + worker_pid(worker_pid), + ipc_fd(ipc_fd) +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd(quic_ipc_fd), + cid_prefixes(cid_prefixes) +#endif // ENABLE_HTTP3 + { + ev_child_init(&worker_process_childev, worker_process_child_cb, worker_pid, + 0); + worker_process_childev.data = this; + ev_child_start(loop, &worker_process_childev); + } + + ~WorkerProcess() { + ev_child_stop(loop, &worker_process_childev); + +#ifdef ENABLE_HTTP3 + if (quic_ipc_fd != -1) { + close(quic_ipc_fd); + } +#endif // ENABLE_HTTP3 + + if (ipc_fd != -1) { + shutdown(ipc_fd, SHUT_WR); + close(ipc_fd); + } + } + + ev_child worker_process_childev; + struct ev_loop *loop; + pid_t worker_pid; + int ipc_fd; + std::chrono::steady_clock::time_point termination_deadline; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd; + std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes; +#endif // ENABLE_HTTP3 +}; + +namespace { +void reload_config(); +} // namespace + +namespace { +std::deque<std::unique_ptr<WorkerProcess>> worker_processes; +} // namespace + +namespace { +ev_timer worker_process_grace_period_timer; +} // namespace + +namespace { +void worker_process_grace_period_timercb(struct ev_loop *loop, ev_timer *w, + int revents) { + auto now = std::chrono::steady_clock::now(); + auto next_repeat = std::chrono::steady_clock::duration::zero(); + + for (auto it = std::begin(worker_processes); + it != std::end(worker_processes);) { + auto &wp = *it; + if (wp->termination_deadline.time_since_epoch().count() == 0) { + ++it; + + continue; + } + + auto d = wp->termination_deadline - now; + if (d.count() > 0) { + if (next_repeat == std::chrono::steady_clock::duration::zero() || + d < next_repeat) { + next_repeat = d; + } + + ++it; + + continue; + } + + LOG(NOTICE) << "Deleting worker process pid=" << wp->worker_pid + << " because its grace shutdown period is over"; + + it = worker_processes.erase(it); + } + + if (next_repeat.count() > 0) { + w->repeat = util::ev_tstamp_from(next_repeat); + ev_timer_again(loop, w); + + return; + } + + ev_timer_stop(loop, w); +} +} // namespace + +namespace { +void worker_process_set_termination_deadline(WorkerProcess *wp, + struct ev_loop *loop) { + auto config = get_config(); + + if (!(config->worker_process_grace_shutdown_period > 0.)) { + return; + } + + wp->termination_deadline = + std::chrono::steady_clock::now() + + util::duration_from(config->worker_process_grace_shutdown_period); + + if (!ev_is_active(&worker_process_grace_period_timer)) { + worker_process_grace_period_timer.repeat = + config->worker_process_grace_shutdown_period; + + ev_timer_again(loop, &worker_process_grace_period_timer); + } +} +} // namespace + +namespace { +void worker_process_add(std::unique_ptr<WorkerProcess> wp) { + worker_processes.push_back(std::move(wp)); +} +} // namespace + +namespace { +void worker_process_remove(const WorkerProcess *wp, struct ev_loop *loop) { + for (auto it = std::begin(worker_processes); it != std::end(worker_processes); + ++it) { + auto &s = *it; + + if (s.get() != wp) { + continue; + } + + worker_processes.erase(it); + + if (worker_processes.empty()) { + ev_timer_stop(loop, &worker_process_grace_period_timer); + } + + break; + } +} +} // namespace + +namespace { +void worker_process_adjust_limit() { + auto config = get_config(); + + if (config->max_worker_processes && + worker_processes.size() > config->max_worker_processes) { + worker_processes.pop_front(); + } +} +} // namespace + +namespace { +void worker_process_remove_all(struct ev_loop *loop) { + std::deque<std::unique_ptr<WorkerProcess>>().swap(worker_processes); + + ev_timer_stop(loop, &worker_process_grace_period_timer); +} +} // namespace + +namespace { +// Send signal |signum| to all worker processes, and clears +// worker_processes. +void worker_process_kill(int signum, struct ev_loop *loop) { + for (auto &s : worker_processes) { + if (s->worker_pid == -1) { + continue; + } + kill(s->worker_pid, signum); + } + worker_process_remove_all(loop); +} +} // namespace + +namespace { +int save_pid() { + std::array<char, STRERROR_BUFSIZE> errbuf; + auto config = get_config(); + + constexpr auto SUFFIX = StringRef::from_lit(".XXXXXX"); + auto &pid_file = config->pid_file; + + auto len = config->pid_file.size() + SUFFIX.size(); + auto buf = std::make_unique<char[]>(len + 1); + auto p = buf.get(); + + p = std::copy(std::begin(pid_file), std::end(pid_file), p); + p = std::copy(std::begin(SUFFIX), std::end(SUFFIX), p); + *p = '\0'; + + auto temp_path = buf.get(); + + auto fd = mkstemp(temp_path); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto content = util::utos(config->pid) + '\n'; + + if (write(fd, content.c_str(), content.size()) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + if (fsync(fd) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + close(fd); + + if (rename(temp_path, pid_file.c_str()) == -1) { + auto error = errno; + LOG(ERROR) << "Could not save PID to file " << pid_file << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + unlink(temp_path); + + return -1; + } + + if (config->uid != 0) { + if (chown(pid_file.c_str(), config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of pid file " << pid_file << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + return 0; +} +} // namespace + +namespace { +void shrpx_sd_notifyf(int unset_environment, const char *format, ...) { +#ifdef HAVE_LIBSYSTEMD + va_list args; + + va_start(args, format); + sd_notifyf(unset_environment, format, va_arg(args, char *)); + va_end(args); +#endif // HAVE_LIBSYSTEMD +} +} // namespace + +namespace { +void exec_binary() { + int rv; + sigset_t oldset; + std::array<char, STRERROR_BUFSIZE> errbuf; + + LOG(NOTICE) << "Executing new binary"; + + shrpx_sd_notifyf(0, "RELOADING=1"); + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return; + } + + auto pid = fork(); + + if (pid != 0) { + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "fork() failed errno=" << error; + } else { + // update PID tracking information in systemd + shrpx_sd_notifyf(0, "MAINPID=%d\n", pid); + } + + rv = shrpx_signal_set(&oldset); + + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring signal mask failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + exit(EXIT_FAILURE); + } + + return; + } + + // child process + + shrpx_signal_unset_main_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Unblocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + nghttp2_Exit(EXIT_FAILURE); + } + + auto exec_path = + util::get_exec_path(suconfig.argc, suconfig.argv, suconfig.cwd); + + if (!exec_path) { + LOG(ERROR) << "Could not resolve the executable path"; + nghttp2_Exit(EXIT_FAILURE); + } + + auto argv = std::make_unique<char *[]>(suconfig.argc + 1); + + argv[0] = exec_path; + for (int i = 1; i < suconfig.argc; ++i) { + argv[i] = suconfig.argv[i]; + } + argv[suconfig.argc] = nullptr; + + size_t envlen = 0; + for (char **p = environ; *p; ++p, ++envlen) + ; + + auto config = get_config(); + auto &listenerconf = config->conn.listener; + + // 2 for ENV_ORIG_PID and terminal nullptr. + auto envp = std::make_unique<char *[]>(envlen + listenerconf.addrs.size() + + worker_processes.size() + 2); + size_t envidx = 0; + + std::vector<ImmutableString> fd_envs; + for (size_t i = 0; i < listenerconf.addrs.size(); ++i) { + auto &addr = listenerconf.addrs[i]; + auto s = ENV_ACCEPT_PREFIX.str(); + s += util::utos(i + 1); + s += '='; + if (addr.host_unix) { + s += "unix,"; + s += util::utos(addr.fd); + s += ','; + s += addr.host; + } else { + s += "tcp,"; + s += util::utos(addr.fd); + } + + fd_envs.emplace_back(s); + envp[envidx++] = const_cast<char *>(fd_envs.back().c_str()); + } + + auto ipc_fd_str = ENV_ORIG_PID.str(); + ipc_fd_str += '='; + ipc_fd_str += util::utos(config->pid); + envp[envidx++] = const_cast<char *>(ipc_fd_str.c_str()); + +#ifdef ENABLE_HTTP3 + std::vector<ImmutableString> quic_lwps; + for (size_t i = 0; i < worker_processes.size(); ++i) { + auto &wp = worker_processes[i]; + auto s = ENV_QUIC_WORKER_PROCESS_PREFIX.str(); + s += util::utos(i + 1); + s += '='; + s += util::utos(wp->quic_ipc_fd); + for (auto &cid_prefix : wp->cid_prefixes) { + s += ','; + s += util::format_hex(cid_prefix); + } + + quic_lwps.emplace_back(s); + envp[envidx++] = const_cast<char *>(quic_lwps.back().c_str()); + } +#endif // ENABLE_HTTP3 + + for (size_t i = 0; i < envlen; ++i) { + auto env = StringRef{environ[i]}; + if (util::starts_with(env, ENV_ACCEPT_PREFIX) || + util::starts_with(env, ENV_LISTENER4_FD) || + util::starts_with(env, ENV_LISTENER6_FD) || + util::starts_with(env, ENV_PORT) || + util::starts_with(env, ENV_UNIX_FD) || + util::starts_with(env, ENV_UNIX_PATH) || + util::starts_with(env, ENV_ORIG_PID) || + util::starts_with(env, ENV_QUIC_WORKER_PROCESS_PREFIX)) { + continue; + } + + envp[envidx++] = environ[i]; + } + + envp[envidx++] = nullptr; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "cmdline"; + for (int i = 0; argv[i]; ++i) { + LOG(INFO) << i << ": " << argv[i]; + } + LOG(INFO) << "environ"; + for (int i = 0; envp[i]; ++i) { + LOG(INFO) << i << ": " << envp[i]; + } + } + + // restores original stderr + restore_original_fds(); + + // reloading finished + shrpx_sd_notifyf(0, "READY=1"); + + if (execve(argv[0], argv.get(), envp.get()) == -1) { + auto error = errno; + LOG(ERROR) << "execve failed: errno=" << error; + nghttp2_Exit(EXIT_FAILURE); + } +} +} // namespace + +namespace { +void ipc_send(WorkerProcess *wp, uint8_t ipc_event) { + std::array<char, STRERROR_BUFSIZE> errbuf; + ssize_t nwrite; + while ((nwrite = write(wp->ipc_fd, &ipc_event, 1)) == -1 && errno == EINTR) + ; + + if (nwrite < 0) { + auto error = errno; + LOG(ERROR) << "Could not send IPC event to worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return; + } + + if (nwrite == 0) { + LOG(ERROR) << "Could not send IPC event due to pipe overflow"; + return; + } +} +} // namespace + +namespace { +void reopen_log(WorkerProcess *wp) { + LOG(NOTICE) << "Reopening log files: main process"; + + auto config = get_config(); + auto &loggingconf = config->logging; + + (void)reopen_log_files(loggingconf); + redirect_stderr_to_errorlog(loggingconf); + ipc_send(wp, SHRPX_IPC_REOPEN_LOG); +} +} // namespace + +namespace { +void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) { + switch (w->signum) { + case REOPEN_LOG_SIGNAL: + for (auto &wp : worker_processes) { + reopen_log(wp.get()); + } + + return; + case EXEC_BINARY_SIGNAL: + exec_binary(); + return; + case GRACEFUL_SHUTDOWN_SIGNAL: { + auto &listenerconf = get_config()->conn.listener; + for (auto &addr : listenerconf.addrs) { + close(addr.fd); + } + + for (auto &wp : worker_processes) { + ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN); + worker_process_set_termination_deadline(wp.get(), loop); + } + + return; + } + case RELOAD_SIGNAL: + reload_config(); + + return; + default: + worker_process_kill(w->signum, loop); + ev_break(loop); + return; + } +} +} // namespace + +namespace { +void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) { + auto wp = static_cast<WorkerProcess *>(w->data); + + log_chld(w->rpid, w->rstatus, "Worker process"); + + worker_process_remove(wp, loop); + + if (worker_processes.empty()) { + ev_break(loop); + } +} +} // namespace + +namespace { +int create_unix_domain_server_socket(UpstreamAddr &faddr, + std::vector<InheritedAddr> &iaddrs) { + std::array<char, STRERROR_BUFSIZE> errbuf; + auto found = std::find_if( + std::begin(iaddrs), std::end(iaddrs), [&faddr](const InheritedAddr &ia) { + return !ia.used && ia.host_unix && ia.host == faddr.host; + }); + + if (found != std::end(iaddrs)) { + LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host + << (faddr.tls ? ", tls" : ""); + (*found).used = true; + faddr.fd = (*found).fd; + faddr.hostport = StringRef::from_lit("localhost"); + + return 0; + } + +#ifdef SOCK_NONBLOCK + auto fd = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK, 0); + if (fd == -1) { + auto error = errno; + LOG(FATAL) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } +#else // !SOCK_NONBLOCK + auto fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) { + auto error = errno; + LOG(FATAL) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + util::make_socket_nonblocking(fd); +#endif // !SOCK_NONBLOCK + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + auto error = errno; + LOG(FATAL) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + sockaddr_union addr; + addr.un.sun_family = AF_UNIX; + if (faddr.host.size() + 1 > sizeof(addr.un.sun_path)) { + LOG(FATAL) << "UNIX domain socket path " << faddr.host << " is too long > " + << sizeof(addr.un.sun_path); + close(fd); + return -1; + } + // copy path including terminal NULL + std::copy_n(faddr.host.c_str(), faddr.host.size() + 1, addr.un.sun_path); + + // unlink (remove) already existing UNIX domain socket path + unlink(faddr.host.c_str()); + + if (bind(fd, &addr.sa, sizeof(addr.un)) != 0) { + auto error = errno; + LOG(FATAL) << "Failed to bind UNIX domain socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + auto &listenerconf = get_config()->conn.listener; + + if (listen(fd, listenerconf.backlog) != 0) { + auto error = errno; + LOG(FATAL) << "Failed to listen to UNIX domain socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + return -1; + } + + LOG(NOTICE) << "Listening on UNIX domain socket " << faddr.host + << (faddr.tls ? ", tls" : ""); + + faddr.fd = fd; + faddr.hostport = StringRef::from_lit("localhost"); + + return 0; +} +} // namespace + +namespace { +int create_tcp_server_socket(UpstreamAddr &faddr, + std::vector<InheritedAddr> &iaddrs) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int fd = -1; + int rv; + + auto &listenerconf = get_config()->conn.listener; + + auto service = util::utos(faddr.port); + addrinfo hints{}; + hints.ai_family = faddr.family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + + auto node = + faddr.host == StringRef::from_lit("*") ? nullptr : faddr.host.c_str(); + + addrinfo *res, *rp; + rv = getaddrinfo(node, service.c_str(), &hints, &res); +#ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(node, service.c_str(), &hints, &res); + } +#endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to get IPv" << (faddr.family == AF_INET ? "4" : "6") + << " address for " << faddr.host << ", port " << faddr.port + << ": " << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + std::array<char, NI_MAXHOST> host; + + for (rp = res; rp; rp = rp->ai_next) { + + rv = getnameinfo(rp->ai_addr, rp->ai_addrlen, host.data(), host.size(), + nullptr, 0, NI_NUMERICHOST); + + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed: " << gai_strerror(rv); + continue; + } + + auto found = std::find_if(std::begin(iaddrs), std::end(iaddrs), + [&host, &faddr](const InheritedAddr &ia) { + return !ia.used && !ia.host_unix && + ia.host == host.data() && + ia.port == faddr.port; + }); + + if (found != std::end(iaddrs)) { + (*found).used = true; + fd = (*found).fd; + break; + } + +#ifdef SOCK_NONBLOCK + fd = + socket(rp->ai_family, rp->ai_socktype | SOCK_NONBLOCK, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } +#else // !SOCK_NONBLOCK + fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (fd == -1) { + auto error = errno; + LOG(WARN) << "socket() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + util::make_socket_nonblocking(fd); +#endif // !SOCK_NONBLOCK + int val = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set SO_REUSEADDR option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + +#ifdef IPV6_V6ONLY + if (faddr.family == AF_INET6) { + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set IPV6_V6ONLY option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + } +#endif // IPV6_V6ONLY + +#ifdef TCP_DEFER_ACCEPT + val = 3; + if (setsockopt(fd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set TCP_DEFER_ACCEPT option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } +#endif // TCP_DEFER_ACCEPT + + // When we are executing new binary, and the old binary did not + // bind privileged port (< 1024) for some reason, binding to those + // ports will fail with permission denied error. + if (bind(fd, rp->ai_addr, rp->ai_addrlen) == -1) { + auto error = errno; + LOG(WARN) << "bind() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + if (listenerconf.fastopen > 0) { + val = listenerconf.fastopen; + if (setsockopt(fd, SOL_TCP, TCP_FASTOPEN, &val, + static_cast<socklen_t>(sizeof(val))) == -1) { + auto error = errno; + LOG(WARN) << "Failed to set TCP_FASTOPEN option to listener socket: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + if (listen(fd, listenerconf.backlog) == -1) { + auto error = errno; + LOG(WARN) << "listen() syscall failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + break; + } + + if (!rp) { + LOG(FATAL) << "Listening " << (faddr.family == AF_INET ? "IPv4" : "IPv6") + << " socket failed"; + + return -1; + } + + faddr.fd = fd; + faddr.hostport = util::make_http_hostport(mod_config()->balloc, + StringRef{host.data()}, faddr.port); + + LOG(NOTICE) << "Listening on " << faddr.hostport + << (faddr.tls ? ", tls" : ""); + + return 0; +} +} // namespace + +namespace { +// Returns array of InheritedAddr constructed from |config|. This +// function is intended to be used when reloading configuration, and +// |config| is usually a current configuration. +std::vector<InheritedAddr> +get_inherited_addr_from_config(BlockAllocator &balloc, Config *config) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + + auto &listenerconf = config->conn.listener; + + std::vector<InheritedAddr> iaddrs(listenerconf.addrs.size()); + + size_t idx = 0; + for (auto &addr : listenerconf.addrs) { + auto &iaddr = iaddrs[idx++]; + + if (addr.host_unix) { + iaddr.host = addr.host; + iaddr.host_unix = true; + iaddr.fd = addr.fd; + + continue; + } + + iaddr.port = addr.port; + iaddr.fd = addr.fd; + + // We have to getsockname/getnameinfo for fd, since we may have + // '*' appear in addr.host, which makes comparison against "real" + // address fail. + + sockaddr_union su; + socklen_t salen = sizeof(su); + + // We already added entry to iaddrs. Even if we got errors, we + // don't remove it. This is required because we have to close the + // socket if it is not reused. The empty host name usually does + // not match anything. + + if (getsockname(addr.fd, &su.sa, &salen) != 0) { + auto error = errno; + LOG(WARN) << "getsockname() syscall failed (fd=" << addr.fd + << "): " << xsi_strerror(error, errbuf.data(), errbuf.size()); + continue; + } + + std::array<char, NI_MAXHOST> host; + rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed (fd=" << addr.fd + << "): " << gai_strerror(rv); + continue; + } + + iaddr.host = make_string_ref(balloc, StringRef{host.data()}); + } + + return iaddrs; +} +} // namespace + +namespace { +// Returns array of InheritedAddr constructed from environment +// variables. This function handles the old environment variable +// names used in 1.7.0 or earlier. +std::vector<InheritedAddr> get_inherited_addr_from_env(Config *config) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + std::vector<InheritedAddr> iaddrs; + + { + // Upgrade from 1.7.0 or earlier + auto portenv = getenv(ENV_PORT.c_str()); + if (portenv) { + size_t i = 1; + for (const auto &env_name : {ENV_LISTENER4_FD, ENV_LISTENER6_FD}) { + auto fdenv = getenv(env_name.c_str()); + if (fdenv) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += util::utos(i); + std::string value = "tcp,"; + value += fdenv; + setenv(name.c_str(), value.c_str(), 0); + ++i; + } + } + } else { + // The return value of getenv may be allocated statically. + if (getenv(ENV_UNIX_PATH.c_str()) && getenv(ENV_UNIX_FD.c_str())) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += '1'; + std::string value = "unix,"; + value += getenv(ENV_UNIX_FD.c_str()); + value += ','; + value += getenv(ENV_UNIX_PATH.c_str()); + setenv(name.c_str(), value.c_str(), 0); + } + } + } + + for (size_t i = 1;; ++i) { + auto name = ENV_ACCEPT_PREFIX.str(); + name += util::utos(i); + auto env = getenv(name.c_str()); + if (!env) { + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Read env " << name << "=" << env; + } + + auto end_type = strchr(env, ','); + if (!end_type) { + continue; + } + + auto type = StringRef(env, end_type); + auto value = end_type + 1; + + if (type == StringRef::from_lit("unix")) { + auto endfd = strchr(value, ','); + if (!endfd) { + continue; + } + auto fd = util::parse_uint(reinterpret_cast<const uint8_t *>(value), + endfd - value); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " + << std::string(value, endfd - value); + continue; + } + + auto path = endfd + 1; + if (strlen(path) == 0) { + LOG(WARN) << "Empty UNIX domain socket path (fd=" << fd << ")"; + close(fd); + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit UNIX domain socket fd=" << fd + << ", path=" << path; + } + + InheritedAddr addr{}; + addr.host = make_string_ref(config->balloc, StringRef{path}); + addr.host_unix = true; + addr.fd = static_cast<int>(fd); + iaddrs.push_back(std::move(addr)); + } + + if (type == StringRef::from_lit("tcp")) { + auto fd = util::parse_uint(value); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " << value; + continue; + } + + sockaddr_union su; + socklen_t salen = sizeof(su); + + if (getsockname(fd, &su.sa, &salen) != 0) { + auto error = errno; + LOG(WARN) << "getsockname() syscall failed (fd=" << fd + << "): " << xsi_strerror(error, errbuf.data(), errbuf.size()); + close(fd); + continue; + } + + uint16_t port; + + switch (su.storage.ss_family) { + case AF_INET: + port = ntohs(su.in.sin_port); + break; + case AF_INET6: + port = ntohs(su.in6.sin6_port); + break; + default: + close(fd); + continue; + } + + std::array<char, NI_MAXHOST> host; + rv = getnameinfo(&su.sa, salen, host.data(), host.size(), nullptr, 0, + NI_NUMERICHOST); + if (rv != 0) { + LOG(WARN) << "getnameinfo() failed (fd=" << fd + << "): " << gai_strerror(rv); + close(fd); + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit TCP socket fd=" << fd + << ", address=" << host.data() << ", port=" << port; + } + + InheritedAddr addr{}; + addr.host = make_string_ref(config->balloc, StringRef{host.data()}); + addr.port = static_cast<uint16_t>(port); + addr.fd = static_cast<int>(fd); + iaddrs.push_back(std::move(addr)); + continue; + } + } + + return iaddrs; +} +} // namespace + +namespace { +// Closes all sockets which are not reused. +void close_unused_inherited_addr(const std::vector<InheritedAddr> &iaddrs) { + for (auto &ia : iaddrs) { + if (ia.used) { + continue; + } + + close(ia.fd); + } +} +} // namespace + +namespace { +// Returns the PID of the original main process from environment +// variable ENV_ORIG_PID. +pid_t get_orig_pid_from_env() { + auto s = getenv(ENV_ORIG_PID.c_str()); + if (s == nullptr) { + return -1; + } + return util::parse_uint(s); +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +std::vector<QUICLingeringWorkerProcess> + inherited_quic_lingering_worker_processes; +} // namespace + +namespace { +std::vector<QUICLingeringWorkerProcess> +get_inherited_quic_lingering_worker_process_from_env() { + std::vector<QUICLingeringWorkerProcess> iwps; + + for (size_t i = 1;; ++i) { + auto name = ENV_QUIC_WORKER_PROCESS_PREFIX.str(); + name += util::utos(i); + auto env = getenv(name.c_str()); + if (!env) { + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Read env " << name << "=" << env; + } + + auto envend = env + strlen(env); + + auto end_fd = std::find(env, envend, ','); + if (end_fd == envend) { + continue; + } + + auto fd = + util::parse_uint(reinterpret_cast<const uint8_t *>(env), end_fd - env); + if (fd == -1) { + LOG(WARN) << "Could not parse file descriptor from " + << StringRef{env, static_cast<size_t>(end_fd - env)}; + continue; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit worker process QUIC IPC socket fd=" << fd; + } + + util::make_socket_closeonexec(fd); + + std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes; + + auto p = end_fd + 1; + for (;;) { + auto end = std::find(p, envend, ','); + + auto hex_cid_prefix = StringRef{p, end}; + if (hex_cid_prefix.size() != SHRPX_QUIC_CID_PREFIXLEN * 2 || + !util::is_hex_string(hex_cid_prefix)) { + LOG(WARN) << "Found invalid CID prefix=" << hex_cid_prefix; + break; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Inherit worker process CID prefix=" << hex_cid_prefix; + } + + cid_prefixes.emplace_back(); + + util::decode_hex(std::begin(cid_prefixes.back()), hex_cid_prefix); + + if (end == envend) { + break; + } + + p = end + 1; + } + + iwps.emplace_back(std::move(cid_prefixes), fd); + } + + return iwps; +} +} // namespace +#endif // ENABLE_HTTP3 + +namespace { +int create_acceptor_socket(Config *config, std::vector<InheritedAddr> &iaddrs) { + std::array<char, STRERROR_BUFSIZE> errbuf; + auto &listenerconf = config->conn.listener; + + for (auto &addr : listenerconf.addrs) { + if (addr.host_unix) { + if (create_unix_domain_server_socket(addr, iaddrs) != 0) { + return -1; + } + + if (config->uid != 0) { + // fd is not associated to inode, so we cannot use fchown(2) + // here. https://lkml.org/lkml/2004/11/1/84 + if (chown(addr.host.c_str(), config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of UNIX domain socket " << addr.host + << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + continue; + } + + if (create_tcp_server_socket(addr, iaddrs) != 0) { + return -1; + } + } + + return 0; +} +} // namespace + +namespace { +int call_daemon() { +#ifdef __sgi + return _daemonize(0, 0, 0, 0); +#else // !__sgi +# ifdef HAVE_LIBSYSTEMD + if (sd_booted() && (getenv("NOTIFY_SOCKET") != nullptr)) { + LOG(NOTICE) << "Daemonising disabled under systemd"; + chdir("/"); + return 0; + } +# endif // HAVE_LIBSYSTEMD + return util::daemonize(0, 0); +#endif // !__sgi +} +} // namespace + +namespace { +// Opens IPC socket used to communicate with worker proess. The +// communication is unidirectional; that is main process sends +// messages to the worker process. On success, ipc_fd[0] is for +// reading, and ipc_fd[1] for writing, just like pipe(2). +int create_ipc_socket(std::array<int, 2> &ipc_fd) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + + rv = pipe(ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create pipe to communicate worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (int i = 0; i < 2; ++i) { + auto fd = ipc_fd[i]; + util::make_socket_nonblocking(fd); + util::make_socket_closeonexec(fd); + } + + return 0; +} +} // namespace + +namespace { +int create_worker_process_ready_ipc_socket(std::array<int, 2> &ipc_fd) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + + rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create socket pair to communicate worker process " + "readiness: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (auto fd : ipc_fd) { + util::make_socket_closeonexec(fd); + } + + util::make_socket_nonblocking(ipc_fd[0]); + + return 0; +} +} // namespace + +#ifdef ENABLE_HTTP3 +namespace { +int create_quic_ipc_socket(std::array<int, 2> &quic_ipc_fd) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + + rv = socketpair(AF_UNIX, SOCK_DGRAM, 0, quic_ipc_fd.data()); + if (rv == -1) { + auto error = errno; + LOG(WARN) << "Failed to create socket pair to communicate worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + for (auto fd : quic_ipc_fd) { + util::make_socket_nonblocking(fd); + } + + return 0; +} +} // namespace + +namespace { +int generate_cid_prefix( + std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> &cid_prefixes, + const Config *config) { + auto &apiconf = config->api; + auto &quicconf = config->quic; + + size_t num_cid_prefix; + if (config->single_thread) { + num_cid_prefix = 1; + } else { + num_cid_prefix = config->num_worker; + + // API endpoint occupies the one dedicated worker thread. + // Although such worker never gets QUIC traffic, we create CID + // prefix for it to make code a bit simpler. + if (apiconf.enabled) { + ++num_cid_prefix; + } + } + + cid_prefixes.resize(num_cid_prefix); + + for (auto &cid_prefix : cid_prefixes) { + if (create_cid_prefix(cid_prefix.data(), quicconf.server_id.data()) != 0) { + return -1; + } + } + + return 0; +} +} // namespace + +namespace { +std::vector<QUICLingeringWorkerProcess> +collect_quic_lingering_worker_processes() { + std::vector<QUICLingeringWorkerProcess> quic_lwps{ + std::begin(inherited_quic_lingering_worker_processes), + std::end(inherited_quic_lingering_worker_processes)}; + + for (auto &wp : worker_processes) { + quic_lwps.emplace_back(wp->cid_prefixes, wp->quic_ipc_fd); + } + + return quic_lwps; +} +} // namespace +#endif // ENABLE_HTTP3 + +namespace { +ev_signal reopen_log_signalev; +ev_signal exec_binary_signalev; +ev_signal graceful_shutdown_signalev; +ev_signal reload_signalev; +} // namespace + +namespace { +void start_signal_watchers(struct ev_loop *loop) { + ev_signal_init(&reopen_log_signalev, signal_cb, REOPEN_LOG_SIGNAL); + ev_signal_start(loop, &reopen_log_signalev); + + ev_signal_init(&exec_binary_signalev, signal_cb, EXEC_BINARY_SIGNAL); + ev_signal_start(loop, &exec_binary_signalev); + + ev_signal_init(&graceful_shutdown_signalev, signal_cb, + GRACEFUL_SHUTDOWN_SIGNAL); + ev_signal_start(loop, &graceful_shutdown_signalev); + + ev_signal_init(&reload_signalev, signal_cb, RELOAD_SIGNAL); + ev_signal_start(loop, &reload_signalev); +} +} // namespace + +namespace { +void shutdown_signal_watchers(struct ev_loop *loop) { + ev_signal_stop(loop, &reload_signalev); + ev_signal_stop(loop, &graceful_shutdown_signalev); + ev_signal_stop(loop, &exec_binary_signalev); + ev_signal_stop(loop, &reopen_log_signalev); +} +} // namespace + +namespace { +// A pair of connected socket with which a worker process tells main +// process that it is ready for service. A worker process writes its +// PID to worker_process_ready_ipc_fd[1] and main process reads it +// from worker_process_ready_ipc_fd[0]. +std::array<int, 2> worker_process_ready_ipc_fd; +} // namespace + +namespace { +ev_io worker_process_ready_ipcev; +} // namespace + +namespace { +// PID received via NGHTTPX_ORIG_PID environment variable. +pid_t orig_pid = -1; +} // namespace + +namespace { +void worker_process_ready_ipc_readcb(struct ev_loop *loop, ev_io *w, + int revents) { + std::array<uint8_t, 8> buf; + ssize_t nread; + + while ((nread = read(w->fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + + if (nread == -1) { + std::array<char, STRERROR_BUFSIZE> errbuf; + auto error = errno; + + LOG(ERROR) << "Failed to read data from worker process ready IPC channel: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + return; + } + + if (nread == 0) { + return; + } + + if (nread != sizeof(pid_t)) { + LOG(ERROR) << "Read " << nread + << " bytes from worker process ready IPC channel"; + + return; + } + + pid_t pid; + + memcpy(&pid, buf.data(), sizeof(pid)); + + LOG(NOTICE) << "Worker process pid=" << pid << " is ready"; + + for (auto &wp : worker_processes) { + // Send graceful shutdown signal to all worker processes prior to + // pid. + if (wp->worker_pid == pid) { + break; + } + + LOG(INFO) << "Sending graceful shutdown event to worker process pid=" + << wp->worker_pid; + + ipc_send(wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN); + worker_process_set_termination_deadline(wp.get(), loop); + } + + if (orig_pid != -1) { + LOG(NOTICE) << "Send QUIT signal to the original main process to tell " + "that we are ready to serve requests."; + kill(orig_pid, SIGQUIT); + + orig_pid = -1; + } +} +} // namespace + +namespace { +void start_worker_process_ready_ipc_watcher(struct ev_loop *loop) { + ev_io_init(&worker_process_ready_ipcev, worker_process_ready_ipc_readcb, + worker_process_ready_ipc_fd[0], EV_READ); + ev_io_start(loop, &worker_process_ready_ipcev); +} +} // namespace + +namespace { +void shutdown_worker_process_ready_ipc_watcher(struct ev_loop *loop) { + ev_io_stop(loop, &worker_process_ready_ipcev); +} +} // namespace + +namespace { +// Creates worker process, and returns PID of worker process. On +// success, file descriptor for IPC (send only) is assigned to +// |main_ipc_fd|. In child process, we will close file descriptors +// which are inherited from previous configuration/process, but not +// used in the current configuration. +pid_t fork_worker_process( + int &main_ipc_fd +#ifdef ENABLE_HTTP3 + , + int &wp_quic_ipc_fd +#endif // ENABLE_HTTP3 + , + const std::vector<InheritedAddr> &iaddrs +#ifdef ENABLE_HTTP3 + , + const std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> + &cid_prefixes, + const std::vector<QUICLingeringWorkerProcess> &quic_lwps +#endif // ENABLE_HTTP3 +) { + std::array<char, STRERROR_BUFSIZE> errbuf; + int rv; + sigset_t oldset; + + std::array<int, 2> ipc_fd; + + rv = create_ipc_socket(ipc_fd); + if (rv != 0) { + return -1; + } + +#ifdef ENABLE_HTTP3 + std::array<int, 2> quic_ipc_fd; + + rv = create_quic_ipc_socket(quic_ipc_fd); + if (rv != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + rv = shrpx_signal_block_all(&oldset); + if (rv != 0) { + auto error = errno; + LOG(ERROR) << "Blocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + close(ipc_fd[0]); + close(ipc_fd[1]); + + return -1; + } + + auto config = get_config(); + + pid_t pid = 0; + + if (!config->single_process) { + pid = fork(); + } + + if (pid == 0) { + // We are in new process now, update pid for logger. + log_config()->pid = getpid(); + + ev_loop_fork(EV_DEFAULT); + + for (auto &addr : config->conn.listener.addrs) { + util::make_socket_closeonexec(addr.fd); + } + +#ifdef ENABLE_HTTP3 + util::make_socket_closeonexec(quic_ipc_fd[0]); + + for (auto &lwp : quic_lwps) { + util::make_socket_closeonexec(lwp.quic_ipc_fd); + } + + for (auto &wp : worker_processes) { + util::make_socket_closeonexec(wp->quic_ipc_fd); + // Do not close quic_ipc_fd. + wp->quic_ipc_fd = -1; + } +#endif // ENABLE_HTTP3 + + if (!config->single_process) { + close(worker_process_ready_ipc_fd[0]); + shutdown_worker_process_ready_ipc_watcher(EV_DEFAULT); + + shutdown_signal_watchers(EV_DEFAULT); + } + + // Remove all WorkerProcesses to stop any registered watcher on + // default loop. + worker_process_remove_all(EV_DEFAULT); + + close_unused_inherited_addr(iaddrs); + + shrpx_signal_set_worker_proc_ign_handler(); + + rv = shrpx_signal_unblock_all(); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Unblocking all signals failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + if (config->single_process) { + exit(EXIT_FAILURE); + } else { + nghttp2_Exit(EXIT_FAILURE); + } + } + + if (!config->single_process) { + close(ipc_fd[1]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[1]); +#endif // ENABLE_HTTP3 + } + + WorkerProcessConfig wpconf{ + .ipc_fd = ipc_fd[0], + .ready_ipc_fd = worker_process_ready_ipc_fd[1], +#ifdef ENABLE_HTTP3 + .cid_prefixes = cid_prefixes, + .quic_ipc_fd = quic_ipc_fd[0], + .quic_lingering_worker_processes = quic_lwps, +#endif // ENABLE_HTTP3 + }; + rv = worker_process_event_loop(&wpconf); + if (rv != 0) { + LOG(FATAL) << "Worker process returned error"; + + if (config->single_process) { + exit(EXIT_FAILURE); + } else { + nghttp2_Exit(EXIT_FAILURE); + } + } + + LOG(NOTICE) << "Worker process shutting down momentarily"; + + // call exit(...) instead of nghttp2_Exit to get leak sanitizer report + if (config->single_process) { + exit(EXIT_SUCCESS); + } else { + nghttp2_Exit(EXIT_SUCCESS); + } + } + + // parent process + if (pid == -1) { + auto error = errno; + LOG(ERROR) << "Could not spawn worker process: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + + rv = shrpx_signal_set(&oldset); + if (rv != 0) { + auto error = errno; + LOG(FATAL) << "Restoring signal mask failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + + exit(EXIT_FAILURE); + } + + if (pid == -1) { + close(ipc_fd[0]); + close(ipc_fd[1]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[0]); + close(quic_ipc_fd[1]); +#endif // ENABLE_HTTP3 + + return -1; + } + + close(ipc_fd[0]); +#ifdef ENABLE_HTTP3 + close(quic_ipc_fd[0]); +#endif // ENABLE_HTTP3 + + main_ipc_fd = ipc_fd[1]; +#ifdef ENABLE_HTTP3 + wp_quic_ipc_fd = quic_ipc_fd[1]; +#endif // ENABLE_HTTP3 + + LOG(NOTICE) << "Worker process [" << pid << "] spawned"; + + return pid; +} +} // namespace + +namespace { +int event_loop() { + std::array<char, STRERROR_BUFSIZE> errbuf; + + shrpx_signal_set_main_proc_ign_handler(); + + auto config = mod_config(); + + if (config->daemon) { + if (call_daemon() == -1) { + auto error = errno; + LOG(FATAL) << "Failed to daemonize: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + // We get new PID after successful daemon(). + mod_config()->pid = getpid(); + + // daemon redirects stderr file descriptor to /dev/null, so we + // need this. + redirect_stderr_to_errorlog(config->logging); + } + + // update systemd PID tracking + shrpx_sd_notifyf(0, "MAINPID=%d\n", config->pid); + + { + auto iaddrs = get_inherited_addr_from_env(config); + + if (create_acceptor_socket(config, iaddrs) != 0) { + return -1; + } + + close_unused_inherited_addr(iaddrs); + } + + orig_pid = get_orig_pid_from_env(); + +#ifdef ENABLE_HTTP3 + inherited_quic_lingering_worker_processes = + get_inherited_quic_lingering_worker_process_from_env(); +#endif // ENABLE_HTTP3 + + auto loop = ev_default_loop(config->ev_loop_flags); + + int ipc_fd = 0; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd = 0; + + auto quic_lwps = collect_quic_lingering_worker_processes(); + + std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes; + + if (generate_cid_prefix(cid_prefixes, config) != 0) { + return -1; + } +#endif // ENABLE_HTTP3 + + if (!config->single_process) { + start_signal_watchers(loop); + } + + create_worker_process_ready_ipc_socket(worker_process_ready_ipc_fd); + start_worker_process_ready_ipc_watcher(loop); + + auto pid = fork_worker_process(ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd +#endif // ENABLE_HTTP3 + , + {} +#ifdef ENABLE_HTTP3 + , + cid_prefixes, quic_lwps +#endif // ENABLE_HTTP3 + ); + + if (pid == -1) { + return -1; + } + + ev_timer_init(&worker_process_grace_period_timer, + worker_process_grace_period_timercb, 0., 0.); + + worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd, cid_prefixes +#endif // ENABLE_HTTP3 + )); + + // Write PID file when we are ready to accept connection from peer. + // This makes easier to write restart script for nghttpx. Because + // when we know that PID file is recreated, it means we can send + // QUIT signal to the old process to make it shutdown gracefully. + if (!config->pid_file.empty()) { + save_pid(); + } + + shrpx_sd_notifyf(0, "READY=1"); + + ev_run(loop, 0); + + ev_timer_stop(loop, &worker_process_grace_period_timer); + + shutdown_worker_process_ready_ipc_watcher(loop); + + // config is now stale if reload has happened. + if (!get_config()->single_process) { + shutdown_signal_watchers(loop); + } + + return 0; +} +} // namespace + +namespace { +// Returns true if regular file or symbolic link |path| exists. +bool conf_exists(const char *path) { + struct stat buf; + int rv = stat(path, &buf); + return rv == 0 && (buf.st_mode & (S_IFREG | S_IFLNK)); +} +} // namespace + +namespace { +constexpr auto DEFAULT_ALPN_LIST = + StringRef::from_lit("h2,h2-16,h2-14,http/1.1"); +} // namespace + +namespace { +constexpr auto DEFAULT_TLS_MIN_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#ifdef TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.3"); +#else // !TLS1_3_VERSION +constexpr auto DEFAULT_TLS_MAX_PROTO_VERSION = StringRef::from_lit("TLSv1.2"); +#endif // !TLS1_3_VERSION +} // namespace + +namespace { +constexpr auto DEFAULT_ACCESSLOG_FORMAT = + StringRef::from_lit(R"($remote_addr - - [$time_local] )" + R"("$request" $status $body_bytes_sent )" + R"("$http_referer" "$http_user_agent")"); +} // namespace + +namespace { +void fill_default_config(Config *config) { + config->num_worker = 1; + config->conf_path = StringRef::from_lit("/etc/nghttpx/nghttpx.conf"); + config->pid = getpid(); + +#ifdef NOTHREADS + config->single_thread = true; +#endif // NOTHREADS + + if (ev_supported_backends() & ~ev_recommended_backends() & EVBACKEND_KQUEUE) { + config->ev_loop_flags = ev_recommended_backends() | EVBACKEND_KQUEUE; + } + + auto &tlsconf = config->tls; + { + auto &ticketconf = tlsconf.ticket; + { + auto &memcachedconf = ticketconf.memcached; + memcachedconf.max_retry = 3; + memcachedconf.max_fail = 2; + memcachedconf.interval = 10_min; + memcachedconf.family = AF_UNSPEC; + } + + auto &session_cacheconf = tlsconf.session_cache; + { + auto &memcachedconf = session_cacheconf.memcached; + memcachedconf.family = AF_UNSPEC; + } + + ticketconf.cipher = EVP_aes_128_cbc(); + } + + { + auto &ocspconf = tlsconf.ocsp; + // ocsp update interval = 14400 secs = 4 hours, borrowed from h2o + ocspconf.update_interval = 4_h; + ocspconf.fetch_ocsp_response_file = + StringRef::from_lit(PKGDATADIR "/fetch-ocsp-response"); + } + + { + auto &dyn_recconf = tlsconf.dyn_rec; + dyn_recconf.warmup_threshold = 1_m; + dyn_recconf.idle_timeout = 1_s; + } + + tlsconf.session_timeout = std::chrono::hours(12); + tlsconf.ciphers = StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.client.ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_CIPHER_LIST); + tlsconf.client.tls13_ciphers = + StringRef::from_lit(nghttp2::tls::DEFAULT_TLS13_CIPHER_LIST); + tlsconf.min_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MIN_PROTO_VERSION); + tlsconf.max_proto_version = + tls::proto_version_from_string(DEFAULT_TLS_MAX_PROTO_VERSION); + tlsconf.max_early_data = 16_k; + tlsconf.ecdh_curves = StringRef::from_lit("X25519:P-256:P-384:P-521"); + + auto &httpconf = config->http; + httpconf.server_name = StringRef::from_lit("nghttpx"); + httpconf.no_host_rewrite = true; + httpconf.request_header_field_buffer = 64_k; + httpconf.max_request_header_fields = 100; + httpconf.response_header_field_buffer = 64_k; + httpconf.max_response_header_fields = 500; + httpconf.redirect_https_port = StringRef::from_lit("443"); + httpconf.max_requests = std::numeric_limits<size_t>::max(); + httpconf.xfp.add = true; + httpconf.xfp.strip_incoming = true; + httpconf.early_data.strip_incoming = true; + + auto &http2conf = config->http2; + { + auto &upstreamconf = http2conf.upstream; + + { + auto &timeoutconf = upstreamconf.timeout; + timeoutconf.settings = 10_s; + } + + // window size for HTTP/2 upstream connection per stream. 2**16-1 + // = 64KiB-1, which is HTTP/2 default. + upstreamconf.window_size = 64_k - 1; + // HTTP/2 has connection-level flow control. The default window + // size for HTTP/2 is 64KiB - 1. + upstreamconf.connection_window_size = 64_k - 1; + upstreamconf.max_concurrent_streams = 100; + + upstreamconf.encoder_dynamic_table_size = 4_k; + upstreamconf.decoder_dynamic_table_size = 4_k; + + nghttp2_option_new(&upstreamconf.option); + nghttp2_option_set_no_auto_window_update(upstreamconf.option, 1); + nghttp2_option_set_no_recv_client_magic(upstreamconf.option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.option, upstreamconf.encoder_dynamic_table_size); + nghttp2_option_set_server_fallback_rfc7540_priorities(upstreamconf.option, + 1); + nghttp2_option_set_builtin_recv_extension_type(upstreamconf.option, + NGHTTP2_PRIORITY_UPDATE); + + // For API endpoint, we enable automatic window update. This is + // because we are a sink. + nghttp2_option_new(&upstreamconf.alt_mode_option); + nghttp2_option_set_no_recv_client_magic(upstreamconf.alt_mode_option, 1); + nghttp2_option_set_max_deflate_dynamic_table_size( + upstreamconf.alt_mode_option, upstreamconf.encoder_dynamic_table_size); + } + + http2conf.timeout.stream_write = 1_min; + + { + auto &downstreamconf = http2conf.downstream; + + { + auto &timeoutconf = downstreamconf.timeout; + timeoutconf.settings = 10_s; + } + + downstreamconf.window_size = 64_k - 1; + downstreamconf.connection_window_size = (1u << 31) - 1; + downstreamconf.max_concurrent_streams = 100; + + downstreamconf.encoder_dynamic_table_size = 4_k; + downstreamconf.decoder_dynamic_table_size = 4_k; + + nghttp2_option_new(&downstreamconf.option); + nghttp2_option_set_no_auto_window_update(downstreamconf.option, 1); + nghttp2_option_set_peer_max_concurrent_streams(downstreamconf.option, 100); + nghttp2_option_set_max_deflate_dynamic_table_size( + downstreamconf.option, downstreamconf.encoder_dynamic_table_size); + } + +#ifdef ENABLE_HTTP3 + auto &quicconf = config->quic; + { + auto &upstreamconf = quicconf.upstream; + + { + auto &timeoutconf = upstreamconf.timeout; + timeoutconf.idle = 30_s; + } + + auto &bpfconf = quicconf.bpf; + bpfconf.prog_file = StringRef::from_lit(PKGLIBDIR "/reuseport_kern.o"); + + upstreamconf.congestion_controller = NGTCP2_CC_ALGO_CUBIC; + + upstreamconf.initial_rtt = + static_cast<ev_tstamp>(NGTCP2_DEFAULT_INITIAL_RTT) / NGTCP2_SECONDS; + } + + if (RAND_bytes(quicconf.server_id.data(), quicconf.server_id.size()) != 1) { + assert(0); + abort(); + } + + auto &http3conf = config->http3; + { + auto &upstreamconf = http3conf.upstream; + + upstreamconf.max_concurrent_streams = 100; + upstreamconf.window_size = 256_k; + upstreamconf.connection_window_size = 1_m; + upstreamconf.max_window_size = 6_m; + upstreamconf.max_connection_window_size = 8_m; + } +#endif // ENABLE_HTTP3 + + auto &loggingconf = config->logging; + { + auto &accessconf = loggingconf.access; + accessconf.format = + parse_log_format(config->balloc, DEFAULT_ACCESSLOG_FORMAT); + + auto &errorconf = loggingconf.error; + errorconf.file = StringRef::from_lit("/dev/stderr"); + } + + loggingconf.syslog_facility = LOG_DAEMON; + loggingconf.severity = NOTICE; + + auto &connconf = config->conn; + { + auto &listenerconf = connconf.listener; + { + // Default accept() backlog + listenerconf.backlog = 65536; + listenerconf.timeout.sleep = 30_s; + } + } + + { + auto &upstreamconf = connconf.upstream; + { + auto &timeoutconf = upstreamconf.timeout; + // Read timeout for HTTP2 upstream connection + timeoutconf.http2_read = 3_min; + + // Read timeout for HTTP3 upstream connection + timeoutconf.http3_read = 3_min; + + // Read timeout for non-HTTP2 upstream connection + timeoutconf.read = 1_min; + + // Write timeout for HTTP2/non-HTTP2 upstream connection + timeoutconf.write = 30_s; + + // Keep alive timeout for HTTP/1 upstream connection + timeoutconf.idle_read = 1_min; + } + } + + { + connconf.downstream = std::make_shared<DownstreamConfig>(); + auto &downstreamconf = *connconf.downstream; + { + auto &timeoutconf = downstreamconf.timeout; + // Read/Write timeouts for downstream connection + timeoutconf.read = 1_min; + timeoutconf.write = 30_s; + // Timeout for pooled (idle) connections + timeoutconf.idle_read = 2_s; + timeoutconf.connect = 30_s; + timeoutconf.max_backoff = 120_s; + } + + downstreamconf.connections_per_host = 8; + downstreamconf.request_buffer_size = 16_k; + downstreamconf.response_buffer_size = 128_k; + downstreamconf.family = AF_UNSPEC; + } + + auto &apiconf = config->api; + apiconf.max_request_body = 32_m; + + auto &dnsconf = config->dns; + { + auto &timeoutconf = dnsconf.timeout; + timeoutconf.cache = 10_s; + timeoutconf.lookup = 5_s; + } + dnsconf.max_try = 2; +} + +} // namespace + +namespace { +void print_version(std::ostream &out) { + out << "nghttpx nghttp2/" NGHTTP2_VERSION +#ifdef ENABLE_HTTP3 + " ngtcp2/" NGTCP2_VERSION " nghttp3/" NGHTTP3_VERSION +#endif // ENABLE_HTTP3 + << std::endl; +} +} // namespace + +namespace { +void print_usage(std::ostream &out) { + out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>] +A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)" + << std::endl; +} +} // namespace + +namespace { +void print_help(std::ostream &out) { + auto config = get_config(); + + print_usage(out); + out << R"( + <PRIVATE_KEY> + Set path to server's private key. Required unless + "no-tls" parameter is used in --frontend option. + <CERT> Set path to server's certificate. Required unless + "no-tls" parameter is used in --frontend option. To + make OCSP stapling work, this must be an absolute path. + +Options: + The options are categorized into several groups. + +Connections: + -b, --backend=(<HOST>,<PORT>|unix:<PATH>)[;[<PATTERN>[:...]][[;<PARAM>]...] + + Set backend host and port. The multiple backend + addresses are accepted by repeating this option. UNIX + domain socket can be specified by prefixing path name + with "unix:" (e.g., unix:/var/run/backend.sock). + + Optionally, if <PATTERN>s are given, the backend address + is only used if request matches the pattern. The + pattern matching is closely designed to ServeMux in + net/http package of Go programming language. <PATTERN> + consists of path, host + path or just host. The path + must start with "/". If it ends with "/", it matches + all request path in its subtree. To deal with the + request to the directory without trailing slash, the + path which ends with "/" also matches the request path + which only lacks trailing '/' (e.g., path "/foo/" + matches request path "/foo"). If it does not end with + "/", it performs exact match against the request path. + If host is given, it performs a match against the + request host. For a request received on the frontend + listener with "sni-fwd" parameter enabled, SNI host is + used instead of a request host. If host alone is given, + "/" is appended to it, so that it matches all request + paths under the host (e.g., specifying "nghttp2.org" + equals to "nghttp2.org/"). CONNECT method is treated + specially. It does not have path, and we don't allow + empty path. To workaround this, we assume that CONNECT + method has "/" as path. + + Patterns with host take precedence over patterns with + just path. Then, longer patterns take precedence over + shorter ones. + + Host can include "*" in the left most position to + indicate wildcard match (only suffix match is done). + The "*" must match at least one character. For example, + host pattern "*.nghttp2.org" matches against + "www.nghttp2.org" and "git.ngttp2.org", but does not + match against "nghttp2.org". The exact hosts match + takes precedence over the wildcard hosts match. + + If path part ends with "*", it is treated as wildcard + path. The wildcard path behaves differently from the + normal path. For normal path, match is made around the + boundary of path component separator,"/". On the other + hand, the wildcard path does not take into account the + path component separator. All paths which include the + wildcard path without last "*" as prefix, and are + strictly longer than wildcard path without last "*" are + matched. "*" must match at least one character. For + example, the pattern "/foo*" matches "/foo/" and + "/foobar". But it does not match "/foo", or "/fo". + + If <PATTERN> is omitted or empty string, "/" is used as + pattern, which matches all request paths (catch-all + pattern). The catch-all backend must be given. + + When doing a match, nghttpx made some normalization to + pattern, request host and path. For host part, they are + converted to lower case. For path part, percent-encoded + unreserved characters defined in RFC 3986 are decoded, + and any dot-segments (".." and ".") are resolved and + removed. + + For example, -b'127.0.0.1,8080;nghttp2.org/httpbin/' + matches the request host "nghttp2.org" and the request + path "/httpbin/get", but does not match the request host + "nghttp2.org" and the request path "/index.html". + + The multiple <PATTERN>s can be specified, delimiting + them by ":". Specifying + -b'127.0.0.1,8080;nghttp2.org:www.nghttp2.org' has the + same effect to specify -b'127.0.0.1,8080;nghttp2.org' + and -b'127.0.0.1,8080;www.nghttp2.org'. + + The backend addresses sharing same <PATTERN> are grouped + together forming load balancing group. + + Several parameters <PARAM> are accepted after <PATTERN>. + The parameters are delimited by ";". The available + parameters are: "proto=<PROTO>", "tls", + "sni=<SNI_HOST>", "fall=<N>", "rise=<N>", + "affinity=<METHOD>", "dns", "redirect-if-not-tls", + "upgrade-scheme", "mruby=<PATH>", + "read-timeout=<DURATION>", "write-timeout=<DURATION>", + "group=<GROUP>", "group-weight=<N>", "weight=<N>", and + "dnf". The parameter consists of keyword, and + optionally followed by "=" and value. For example, the + parameter "proto=h2" consists of the keyword "proto" and + value "h2". The parameter "tls" consists of the keyword + "tls" without value. Each parameter is described as + follows. + + The backend application protocol can be specified using + optional "proto" parameter, and in the form of + "proto=<PROTO>". <PROTO> should be one of the following + list without quotes: "h2", "http/1.1". The default + value of <PROTO> is "http/1.1". Note that usually "h2" + refers to HTTP/2 over TLS. But in this option, it may + mean HTTP/2 over cleartext TCP unless "tls" keyword is + used (see below). + + TLS can be enabled by specifying optional "tls" + parameter. TLS is not enabled by default. + + With "sni=<SNI_HOST>" parameter, it can override the TLS + SNI field value with given <SNI_HOST>. This will + default to the backend <HOST> name + + The feature to detect whether backend is online or + offline can be enabled using optional "fall" and "rise" + parameters. Using "fall=<N>" parameter, if nghttpx + cannot connect to a this backend <N> times in a row, + this backend is assumed to be offline, and it is + excluded from load balancing. If <N> is 0, this backend + never be excluded from load balancing whatever times + nghttpx cannot connect to it, and this is the default. + There is also "rise=<N>" parameter. After backend was + excluded from load balancing group, nghttpx periodically + attempts to make a connection to the failed backend, and + if the connection is made successfully <N> times in a + row, the backend is assumed to be online, and it is now + eligible for load balancing target. If <N> is 0, a + backend is permanently offline, once it goes in that + state, and this is the default behaviour. + + The session affinity is enabled using + "affinity=<METHOD>" parameter. If "ip" is given in + <METHOD>, client IP based session affinity is enabled. + If "cookie" is given in <METHOD>, cookie based session + affinity is enabled. If "none" is given in <METHOD>, + session affinity is disabled, and this is the default. + The session affinity is enabled per <PATTERN>. If at + least one backend has "affinity" parameter, and its + <METHOD> is not "none", session affinity is enabled for + all backend servers sharing the same <PATTERN>. It is + advised to set "affinity" parameter to all backend + explicitly if session affinity is desired. The session + affinity may break if one of the backend gets + unreachable, or backend settings are reloaded or + replaced by API. + + If "affinity=cookie" is used, the additional + configuration is required. + "affinity-cookie-name=<NAME>" must be used to specify a + name of cookie to use. Optionally, + "affinity-cookie-path=<PATH>" can be used to specify a + path which cookie is applied. The optional + "affinity-cookie-secure=<SECURE>" controls the Secure + attribute of a cookie. The default value is "auto", and + the Secure attribute is determined by a request scheme. + If a request scheme is "https", then Secure attribute is + set. Otherwise, it is not set. If <SECURE> is "yes", + the Secure attribute is always set. If <SECURE> is + "no", the Secure attribute is always omitted. + "affinity-cookie-stickiness=<STICKINESS>" controls + stickiness of this affinity. If <STICKINESS> is + "loose", removing or adding a backend server might break + the affinity and the request might be forwarded to a + different backend server. If <STICKINESS> is "strict", + removing the designated backend server breaks affinity, + but adding new backend server does not cause breakage. + If the designated backend server becomes unavailable, + new backend server is chosen as if the request does not + have an affinity cookie. <STICKINESS> defaults to + "loose". + + By default, name resolution of backend host name is done + at start up, or reloading configuration. If "dns" + parameter is given, name resolution takes place + dynamically. This is useful if backend address changes + frequently. If "dns" is given, name resolution of + backend host name at start up, or reloading + configuration is skipped. + + If "redirect-if-not-tls" parameter is used, the matched + backend requires that frontend connection is TLS + encrypted. If it isn't, nghttpx responds to the request + with 308 status code, and https URI the client should + use instead is included in Location header field. The + port number in redirect URI is 443 by default, and can + be changed using --redirect-https-port option. If at + least one backend has "redirect-if-not-tls" parameter, + this feature is enabled for all backend servers sharing + the same <PATTERN>. It is advised to set + "redirect-if-no-tls" parameter to all backends + explicitly if this feature is desired. + + If "upgrade-scheme" parameter is used along with "tls" + parameter, HTTP/2 :scheme pseudo header field is changed + to "https" from "http" when forwarding a request to this + particular backend. This is a workaround for a backend + server which requires "https" :scheme pseudo header + field on TLS encrypted connection. + + "mruby=<PATH>" parameter specifies a path to mruby + script file which is invoked when this pattern is + matched. All backends which share the same pattern must + have the same mruby path. + + "read-timeout=<DURATION>" and "write-timeout=<DURATION>" + parameters specify the read and write timeout of the + backend connection when this pattern is matched. All + backends which share the same pattern must have the same + timeouts. If these timeouts are entirely omitted for a + pattern, --backend-read-timeout and + --backend-write-timeout are used. + + "group=<GROUP>" parameter specifies the name of group + this backend address belongs to. By default, it belongs + to the unnamed default group. The name of group is + unique per pattern. "group-weight=<N>" parameter + specifies the weight of the group. The higher weight + gets more frequently selected by the load balancing + algorithm. <N> must be [1, 256] inclusive. The weight + 8 has 4 times more weight than 2. <N> must be the same + for all addresses which share the same <GROUP>. If + "group-weight" is omitted in an address, but the other + address which belongs to the same group specifies + "group-weight", its weight is used. If no + "group-weight" is specified for all addresses, the + weight of a group becomes 1. "group" and "group-weight" + are ignored if session affinity is enabled. + + "weight=<N>" parameter specifies the weight of the + backend address inside a group which this address + belongs to. The higher weight gets more frequently + selected by the load balancing algorithm. <N> must be + [1, 256] inclusive. The weight 8 has 4 times more + weight than weight 2. If this parameter is omitted, + weight becomes 1. "weight" is ignored if session + affinity is enabled. + + If "dnf" parameter is specified, an incoming request is + not forwarded to a backend and just consumed along with + the request body (actually a backend server never be + contacted). It is expected that the HTTP response is + generated by mruby script (see "mruby=<PATH>" parameter + above). "dnf" is an abbreviation of "do not forward". + + Since ";" and ":" are used as delimiter, <PATTERN> must + not contain these characters. In order to include ":" + in <PATTERN>, one has to specify "%3A" (which is + percent-encoded from of ":") instead. Since ";" has + special meaning in shell, the option value must be + quoted. + + Default: )" + << DEFAULT_DOWNSTREAM_HOST << "," << DEFAULT_DOWNSTREAM_PORT << R"( + -f, --frontend=(<HOST>,<PORT>|unix:<PATH>)[[;<PARAM>]...] + Set frontend host and port. If <HOST> is '*', it + assumes all addresses including both IPv4 and IPv6. + UNIX domain socket can be specified by prefixing path + name with "unix:" (e.g., unix:/var/run/nghttpx.sock). + This option can be used multiple times to listen to + multiple addresses. + + This option can take 0 or more parameters, which are + described below. Note that "api" and "healthmon" + parameters are mutually exclusive. + + Optionally, TLS can be disabled by specifying "no-tls" + parameter. TLS is enabled by default. + + If "sni-fwd" parameter is used, when performing a match + to select a backend server, SNI host name received from + the client is used instead of the request host. See + --backend option about the pattern match. + + To make this frontend as API endpoint, specify "api" + parameter. This is disabled by default. It is + important to limit the access to the API frontend. + Otherwise, someone may change the backend server, and + break your services, or expose confidential information + to the outside the world. + + To make this frontend as health monitor endpoint, + specify "healthmon" parameter. This is disabled by + default. Any requests which come through this address + are replied with 200 HTTP status, without no body. + + To accept PROXY protocol version 1 and 2 on frontend + connection, specify "proxyproto" parameter. This is + disabled by default. + + To receive HTTP/3 (QUIC) traffic, specify "quic" + parameter. It makes nghttpx listen on UDP port rather + than TCP port. UNIX domain socket, "api", and + "healthmon" parameters cannot be used with "quic" + parameter. + + Default: *,3000 + --backlog=<N> + Set listen backlog size. + Default: )" + << config->conn.listener.backlog << R"( + --backend-address-family=(auto|IPv4|IPv6) + Specify address family of backend connections. If + "auto" is given, both IPv4 and IPv6 are considered. If + "IPv4" is given, only IPv4 address is considered. If + "IPv6" is given, only IPv6 address is considered. + Default: auto + --backend-http-proxy-uri=<URI> + Specify proxy URI in the form + http://[<USER>:<PASS>@]<PROXY>:<PORT>. If a proxy + requires authentication, specify <USER> and <PASS>. + Note that they must be properly percent-encoded. This + proxy is used when the backend connection is HTTP/2. + First, make a CONNECT request to the proxy and it + connects to the backend on behalf of nghttpx. This + forms tunnel. After that, nghttpx performs SSL/TLS + handshake with the downstream through the tunnel. The + timeouts when connecting and making CONNECT request can + be specified by --backend-read-timeout and + --backend-write-timeout options. + +Performance: + -n, --workers=<N> + Set the number of worker threads. + Default: )" + << config->num_worker << R"( + --single-thread + Run everything in one thread inside the worker process. + This feature is provided for better debugging + experience, or for the platforms which lack thread + support. If threading is disabled, this option is + always enabled. + --read-rate=<SIZE> + Set maximum average read rate on frontend connection. + Setting 0 to this option means read rate is unlimited. + Default: )" + << config->conn.upstream.ratelimit.read.rate << R"( + --read-burst=<SIZE> + Set maximum read burst size on frontend connection. + Setting 0 to this option means read burst size is + unlimited. + Default: )" + << config->conn.upstream.ratelimit.read.burst << R"( + --write-rate=<SIZE> + Set maximum average write rate on frontend connection. + Setting 0 to this option means write rate is unlimited. + Default: )" + << config->conn.upstream.ratelimit.write.rate << R"( + --write-burst=<SIZE> + Set maximum write burst size on frontend connection. + Setting 0 to this option means write burst size is + unlimited. + Default: )" + << config->conn.upstream.ratelimit.write.burst << R"( + --worker-read-rate=<SIZE> + Set maximum average read rate on frontend connection per + worker. Setting 0 to this option means read rate is + unlimited. Not implemented yet. + Default: 0 + --worker-read-burst=<SIZE> + Set maximum read burst size on frontend connection per + worker. Setting 0 to this option means read burst size + is unlimited. Not implemented yet. + Default: 0 + --worker-write-rate=<SIZE> + Set maximum average write rate on frontend connection + per worker. Setting 0 to this option means write rate + is unlimited. Not implemented yet. + Default: 0 + --worker-write-burst=<SIZE> + Set maximum write burst size on frontend connection per + worker. Setting 0 to this option means write burst size + is unlimited. Not implemented yet. + Default: 0 + --worker-frontend-connections=<N> + Set maximum number of simultaneous connections frontend + accepts. Setting 0 means unlimited. + Default: )" + << config->conn.upstream.worker_connections << R"( + --backend-connections-per-host=<N> + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per origin host. + This option is meaningful when --http2-proxy option is + used. The origin host is determined by authority + portion of request URI (or :authority header field for + HTTP/2). To limit the number of connections per + frontend for default mode, use + --backend-connections-per-frontend. + Default: )" + << config->conn.downstream->connections_per_host << R"( + --backend-connections-per-frontend=<N> + Set maximum number of backend concurrent connections + (and/or streams in case of HTTP/2) per frontend. This + option is only used for default mode. 0 means + unlimited. To limit the number of connections per host + with --http2-proxy option, use + --backend-connections-per-host. + Default: )" + << config->conn.downstream->connections_per_frontend << R"( + --rlimit-nofile=<N> + Set maximum number of open files (RLIMIT_NOFILE) to <N>. + If 0 is given, nghttpx does not set the limit. + Default: )" + << config->rlimit_nofile << R"( + --rlimit-memlock=<N> + Set maximum number of bytes of memory that may be locked + into RAM. If 0 is given, nghttpx does not set the + limit. + Default: )" + << config->rlimit_memlock << R"( + --backend-request-buffer=<SIZE> + Set buffer size used to store backend request. + Default: )" + << util::utos_unit(config->conn.downstream->request_buffer_size) << R"( + --backend-response-buffer=<SIZE> + Set buffer size used to store backend response. + Default: )" + << util::utos_unit(config->conn.downstream->response_buffer_size) << R"( + --fastopen=<N> + Enables "TCP Fast Open" for the listening socket and + limits the maximum length for the queue of connections + that have not yet completed the three-way handshake. If + value is 0 then fast open is disabled. + Default: )" + << config->conn.listener.fastopen << R"( + --no-kqueue Don't use kqueue. This option is only applicable for + the platforms which have kqueue. For other platforms, + this option will be simply ignored. + +Timeout: + --frontend-http2-read-timeout=<DURATION> + Specify read timeout for HTTP/2 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http2_read) << R"( + --frontend-http3-read-timeout=<DURATION> + Specify read timeout for HTTP/3 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.http3_read) << R"( + --frontend-read-timeout=<DURATION> + Specify read timeout for HTTP/1.1 frontend connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.read) << R"( + --frontend-write-timeout=<DURATION> + Specify write timeout for all frontend connections. + Default: )" + << util::duration_str(config->conn.upstream.timeout.write) << R"( + --frontend-keep-alive-timeout=<DURATION> + Specify keep-alive timeout for frontend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.upstream.timeout.idle_read) << R"( + --stream-read-timeout=<DURATION> + Specify read timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_read) << R"( + --stream-write-timeout=<DURATION> + Specify write timeout for HTTP/2 streams. 0 means no + timeout. + Default: )" + << util::duration_str(config->http2.timeout.stream_write) << R"( + --backend-read-timeout=<DURATION> + Specify read timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.read) << R"( + --backend-write-timeout=<DURATION> + Specify write timeout for backend connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.write) << R"( + --backend-connect-timeout=<DURATION> + Specify timeout before establishing TCP connection to + backend. + Default: )" + << util::duration_str(config->conn.downstream->timeout.connect) << R"( + --backend-keep-alive-timeout=<DURATION> + Specify keep-alive timeout for backend HTTP/1 + connection. + Default: )" + << util::duration_str(config->conn.downstream->timeout.idle_read) << R"( + --listener-disable-timeout=<DURATION> + After accepting connection failed, connection listener + is disabled for a given amount of time. Specifying 0 + disables this feature. + Default: )" + << util::duration_str(config->conn.listener.timeout.sleep) << R"( + --frontend-http2-setting-timeout=<DURATION> + Specify timeout before SETTINGS ACK is received from + client. + Default: )" + << util::duration_str(config->http2.upstream.timeout.settings) << R"( + --backend-http2-settings-timeout=<DURATION> + Specify timeout before SETTINGS ACK is received from + backend server. + Default: )" + << util::duration_str(config->http2.downstream.timeout.settings) << R"( + --backend-max-backoff=<DURATION> + Specify maximum backoff interval. This is used when + doing health check against offline backend (see "fail" + parameter in --backend option). It is also used to + limit the maximum interval to temporarily disable + backend when nghttpx failed to connect to it. These + intervals are calculated using exponential backoff, and + consecutive failed attempts increase the interval. This + option caps its maximum value. + Default: )" + << util::duration_str(config->conn.downstream->timeout.max_backoff) << R"( + +SSL/TLS: + --ciphers=<SUITE> + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use --tls13-ciphers for TLSv1.3. + Default: )" + << config->tls.ciphers << R"( + --tls13-ciphers=<SUITE> + Set allowed cipher list for frontend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + --ciphers for TLSv1.2 or earlier. + Default: )" + << config->tls.tls13_ciphers << R"( + --client-ciphers=<SUITE> + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.2 or earlier. + Use --tls13-client-ciphers for TLSv1.3. + Default: )" + << config->tls.client.ciphers << R"( + --tls13-client-ciphers=<SUITE> + Set allowed cipher list for backend connection. The + format of the string is described in OpenSSL ciphers(1). + This option sets cipher suites for TLSv1.3. Use + --tls13-client-ciphers for TLSv1.2 or earlier. + Default: )" + << config->tls.client.tls13_ciphers << R"( + --ecdh-curves=<LIST> + Set supported curve list for frontend connections. + <LIST> is a colon separated list of curve NID or names + in the preference order. The supported curves depend on + the linked OpenSSL library. This function requires + OpenSSL >= 1.0.2. + Default: )" + << config->tls.ecdh_curves << R"( + -k, --insecure + Don't verify backend server's certificate if TLS is + enabled for backend connections. + --cacert=<PATH> + Set path to trusted CA certificate file. It is used in + backend TLS connections to verify peer's certificate. + It is also used to verify OCSP response from the script + set by --fetch-ocsp-response-file. The file must be in + PEM format. It can contain multiple certificates. If + the linked OpenSSL is configured to load system wide + certificates, they are loaded at startup regardless of + this option. + --private-key-passwd-file=<PATH> + Path to file that contains password for the server's + private key. If none is given and the private key is + password protected it'll be requested interactively. + --subcert=<KEYPATH>:<CERTPATH>[[;<PARAM>]...] + Specify additional certificate and private key file. + nghttpx will choose certificates based on the hostname + indicated by client using TLS SNI extension. If nghttpx + is built with OpenSSL >= 1.0.2, the shared elliptic + curves (e.g., P-256) between client and server are also + taken into consideration. This allows nghttpx to send + ECDSA certificate to modern clients, while sending RSA + based certificate to older clients. This option can be + used multiple times. To make OCSP stapling work, + <CERTPATH> must be absolute path. + + Additional parameter can be specified in <PARAM>. The + available <PARAM> is "sct-dir=<DIR>". + + "sct-dir=<DIR>" specifies the path to directory which + contains *.sct files for TLS + signed_certificate_timestamp extension (RFC 6962). This + feature requires OpenSSL >= 1.0.2. See also + --tls-sct-dir option. + --dh-param-file=<PATH> + Path to file that contains DH parameters in PEM format. + Without this option, DHE cipher suites are not + available. + --alpn-list=<LIST> + Comma delimited list of ALPN protocol identifier sorted + in the order of preference. That means most desirable + protocol comes first. The parameter must be delimited + by a single comma only and any white spaces are treated + as a part of protocol string. + Default: )" + << DEFAULT_ALPN_LIST + << R"( + --verify-client + Require and verify client certificate. + --verify-client-cacert=<PATH> + Path to file that contains CA certificates to verify + client certificate. The file must be in PEM format. It + can contain multiple certificates. + --verify-client-tolerate-expired + Accept expired client certificate. Operator should + handle the expired client certificate by some means + (e.g., mruby script). Otherwise, this option might + cause a security risk. + --client-private-key-file=<PATH> + Path to file that contains client private key used in + backend client authentication. + --client-cert-file=<PATH> + Path to file that contains client certificate used in + backend client authentication. + --tls-min-proto-version=<VER> + Specify minimum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + --tls-min-proto-version and --tls-max-proto-version are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". If a protocol version lower + than TLSv1.2 is specified, make sure that the compatible + ciphers are included in --ciphers option. The default + cipher list only includes ciphers compatible with + TLSv1.2 or above. The available versions are: + )" +#ifdef TLS1_3_VERSION + "TLSv1.3, " +#endif // TLS1_3_VERSION + "TLSv1.2, TLSv1.1, and TLSv1.0" + R"( + Default: )" + << DEFAULT_TLS_MIN_PROTO_VERSION + << R"( + --tls-max-proto-version=<VER> + Specify maximum SSL/TLS protocol. The name matching is + done in case-insensitive manner. The versions between + --tls-min-proto-version and --tls-max-proto-version are + enabled. If the protocol list advertised by client does + not overlap this range, you will receive the error + message "unknown protocol". The available versions are: + )" +#ifdef TLS1_3_VERSION + "TLSv1.3, " +#endif // TLS1_3_VERSION + "TLSv1.2, TLSv1.1, and TLSv1.0" + R"( + Default: )" + << DEFAULT_TLS_MAX_PROTO_VERSION << R"( + --tls-ticket-key-file=<PATH> + Path to file that contains random data to construct TLS + session ticket parameters. If aes-128-cbc is given in + --tls-ticket-key-cipher, the file must contain exactly + 48 bytes. If aes-256-cbc is given in + --tls-ticket-key-cipher, the file must contain exactly + 80 bytes. This options can be used repeatedly to + specify multiple ticket parameters. If several files + are given, only the first key is used to encrypt TLS + session tickets. Other keys are accepted but server + will issue new session ticket with first key. This + allows session key rotation. Please note that key + rotation does not occur automatically. User should + rearrange files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keys are discarded and it is treated + as if none of this option is given. If this option is + not given or an error occurred while opening or reading + a file, key is generated every 1 hour internally and + they are valid for 12 hours. This is recommended if + ticket key sharing between nghttpx instances is not + required. + --tls-ticket-key-memcached=<HOST>,<PORT>[;tls] + Specify address of memcached server to get TLS ticket + keys for session resumption. This enables shared TLS + ticket key between multiple nghttpx instances. nghttpx + does not set TLS ticket key to memcached. The external + ticket key generator is required. nghttpx just gets TLS + ticket keys from memcached, and use them, possibly + replacing current set of keys. It is up to extern TLS + ticket key generator to rotate keys frequently. See + "TLS SESSION TICKET RESUMPTION" section in manual page + to know the data format in memcached entry. Optionally, + memcached connection can be encrypted with TLS by + specifying "tls" parameter. + --tls-ticket-key-memcached-address-family=(auto|IPv4|IPv6) + Specify address family of memcached connections to get + TLS ticket keys. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + Default: auto + --tls-ticket-key-memcached-interval=<DURATION> + Set interval to get TLS ticket keys from memcached. + Default: )" + << util::duration_str(config->tls.ticket.memcached.interval) << R"( + --tls-ticket-key-memcached-max-retry=<N> + Set maximum number of consecutive retries before + abandoning TLS ticket key retrieval. If this number is + reached, the attempt is considered as failure, and + "failure" count is incremented by 1, which contributed + to the value controlled + --tls-ticket-key-memcached-max-fail option. + Default: )" + << config->tls.ticket.memcached.max_retry << R"( + --tls-ticket-key-memcached-max-fail=<N> + Set maximum number of consecutive failure before + disabling TLS ticket until next scheduled key retrieval. + Default: )" + << config->tls.ticket.memcached.max_fail << R"( + --tls-ticket-key-cipher=<CIPHER> + Specify cipher to encrypt TLS session ticket. Specify + either aes-128-cbc or aes-256-cbc. By default, + aes-128-cbc is used. + --tls-ticket-key-memcached-cert-file=<PATH> + Path to client certificate for memcached connections to + get TLS ticket keys. + --tls-ticket-key-memcached-private-key-file=<PATH> + Path to client private key for memcached connections to + get TLS ticket keys. + --fetch-ocsp-response-file=<PATH> + Path to fetch-ocsp-response script file. It should be + absolute path. + Default: )" + << config->tls.ocsp.fetch_ocsp_response_file << R"( + --ocsp-update-interval=<DURATION> + Set interval to update OCSP response cache. + Default: )" + << util::duration_str(config->tls.ocsp.update_interval) << R"( + --ocsp-startup + Start accepting connections after initial attempts to + get OCSP responses finish. It does not matter some of + the attempts fail. This feature is useful if OCSP + responses must be available before accepting + connections. + --no-verify-ocsp + nghttpx does not verify OCSP response. + --no-ocsp Disable OCSP stapling. + --tls-session-cache-memcached=<HOST>,<PORT>[;tls] + Specify address of memcached server to store session + cache. This enables shared session cache between + multiple nghttpx instances. Optionally, memcached + connection can be encrypted with TLS by specifying "tls" + parameter. + --tls-session-cache-memcached-address-family=(auto|IPv4|IPv6) + Specify address family of memcached connections to store + session cache. If "auto" is given, both IPv4 and IPv6 + are considered. If "IPv4" is given, only IPv4 address + is considered. If "IPv6" is given, only IPv6 address is + considered. + Default: auto + --tls-session-cache-memcached-cert-file=<PATH> + Path to client certificate for memcached connections to + store session cache. + --tls-session-cache-memcached-private-key-file=<PATH> + Path to client private key for memcached connections to + store session cache. + --tls-dyn-rec-warmup-threshold=<SIZE> + Specify the threshold size for TLS dynamic record size + behaviour. During a TLS session, after the threshold + number of bytes have been written, the TLS record size + will be increased to the maximum allowed (16K). The max + record size will continue to be used on the active TLS + session. After --tls-dyn-rec-idle-timeout has elapsed, + the record size is reduced to 1300 bytes. Specify 0 to + always use the maximum record size, regardless of idle + period. This behaviour applies to all TLS based + frontends, and TLS HTTP/2 backends. + Default: )" + << util::utos_unit(config->tls.dyn_rec.warmup_threshold) << R"( + --tls-dyn-rec-idle-timeout=<DURATION> + Specify TLS dynamic record size behaviour timeout. See + --tls-dyn-rec-warmup-threshold for more information. + This behaviour applies to all TLS based frontends, and + TLS HTTP/2 backends. + Default: )" + << util::duration_str(config->tls.dyn_rec.idle_timeout) << R"( + --no-http2-cipher-block-list + Allow block listed cipher suite on frontend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + --client-no-http2-cipher-block-list + Allow block listed cipher suite on backend HTTP/2 + connection. See + https://tools.ietf.org/html/rfc7540#appendix-A for the + complete HTTP/2 cipher suites block list. + --tls-sct-dir=<DIR> + Specifies the directory where *.sct files exist. All + *.sct files in <DIR> are read, and sent as + extension_data of TLS signed_certificate_timestamp (RFC + 6962) to client. These *.sct files are for the + certificate specified in positional command-line + argument <CERT>, or certificate option in configuration + file. For additional certificates, use --subcert + option. This option requires OpenSSL >= 1.0.2. + --psk-secrets=<PATH> + Read list of PSK identity and secrets from <PATH>. This + is used for frontend connection. The each line of input + file is formatted as <identity>:<hex-secret>, where + <identity> is PSK identity, and <hex-secret> is secret + in hex. An empty line, and line which starts with '#' + are skipped. The default enabled cipher list might not + contain any PSK cipher suite. In that case, desired PSK + cipher suites must be enabled using --ciphers option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use --no-http2-cipher-block-list option. + But be aware its implications. + --client-psk-secrets=<PATH> + Read PSK identity and secrets from <PATH>. This is used + for backend connection. The each line of input file is + formatted as <identity>:<hex-secret>, where <identity> + is PSK identity, and <hex-secret> is secret in hex. An + empty line, and line which starts with '#' are skipped. + The first identity and secret pair encountered is used. + The default enabled cipher list might not contain any + PSK cipher suite. In that case, desired PSK cipher + suites must be enabled using --client-ciphers option. + The desired PSK cipher suite may be block listed by + HTTP/2. To use those cipher suites with HTTP/2, + consider to use --client-no-http2-cipher-block-list + option. But be aware its implications. + --tls-no-postpone-early-data + By default, except for QUIC connections, nghttpx + postpones forwarding HTTP requests sent in early data, + including those sent in partially in it, until TLS + handshake finishes. If all backend server recognizes + "Early-Data" header field, using this option makes + nghttpx not postpone forwarding request and get full + potential of 0-RTT data. + --tls-max-early-data=<SIZE> + Sets the maximum amount of 0-RTT data that server + accepts. + Default: )" + << util::utos_unit(config->tls.max_early_data) << R"( + --tls-ktls Enable ktls. For server, ktls is enable if + --tls-session-cache-memcached is not configured. + +HTTP/2: + -c, --frontend-http2-max-concurrent-streams=<N> + Set the maximum number of the concurrent streams in one + frontend HTTP/2 session. + Default: )" + << config->http2.upstream.max_concurrent_streams << R"( + --backend-http2-max-concurrent-streams=<N> + Set the maximum number of the concurrent streams in one + backend HTTP/2 session. This sets maximum number of + concurrent opened pushed streams. The maximum number of + concurrent requests are set by a remote server. + Default: )" + << config->http2.downstream.max_concurrent_streams << R"( + --frontend-http2-window-size=<SIZE> + Sets the per-stream initial window size of HTTP/2 + frontend connection. + Default: )" + << config->http2.upstream.window_size << R"( + --frontend-http2-connection-window-size=<SIZE> + Sets the per-connection window size of HTTP/2 frontend + connection. + Default: )" + << config->http2.upstream.connection_window_size << R"( + --backend-http2-window-size=<SIZE> + Sets the initial window size of HTTP/2 backend + connection. + Default: )" + << config->http2.downstream.window_size << R"( + --backend-http2-connection-window-size=<SIZE> + Sets the per-connection window size of HTTP/2 backend + connection. + Default: )" + << config->http2.downstream.connection_window_size << R"( + --http2-no-cookie-crumbling + Don't crumble cookie header field. + --padding=<N> + Add at most <N> bytes to a HTTP/2 frame payload as + padding. Specify 0 to disable padding. This option is + meant for debugging purpose and not intended to enhance + protocol security. + --no-server-push + Disable HTTP/2 server push. Server push is supported by + default mode and HTTP/2 frontend via Link header field. + It is also supported if both frontend and backend are + HTTP/2 in default mode. In this case, server push from + backend session is relayed to frontend, and server push + via Link header field is also supported. + --frontend-http2-optimize-write-buffer-size + (Experimental) Enable write buffer size optimization in + frontend HTTP/2 TLS connection. This optimization aims + to reduce write buffer size so that it only contains + bytes which can send immediately. This makes server + more responsive to prioritized HTTP/2 stream because the + buffering of lower priority stream is reduced. This + option is only effective on recent Linux platform. + --frontend-http2-optimize-window-size + (Experimental) Automatically tune connection level + window size of frontend HTTP/2 TLS connection. If this + feature is enabled, connection window size starts with + the default window size, 65535 bytes. nghttpx + automatically adjusts connection window size based on + TCP receiving window size. The maximum window size is + capped by the value specified by + --frontend-http2-connection-window-size. Since the + stream is subject to stream level window size, it should + be adjusted using --frontend-http2-window-size option as + well. This option is only effective on recent Linux + platform. + --frontend-http2-encoder-dynamic-table-size=<SIZE> + Specify the maximum dynamic table size of HPACK encoder + in the frontend HTTP/2 connection. The decoder (client) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which client specified. + Default: )" + << util::utos_unit(config->http2.upstream.encoder_dynamic_table_size) + << R"( + --frontend-http2-decoder-dynamic-table-size=<SIZE> + Specify the maximum dynamic table size of HPACK decoder + in the frontend HTTP/2 connection. + Default: )" + << util::utos_unit(config->http2.upstream.decoder_dynamic_table_size) + << R"( + --backend-http2-encoder-dynamic-table-size=<SIZE> + Specify the maximum dynamic table size of HPACK encoder + in the backend HTTP/2 connection. The decoder (backend) + specifies the maximum dynamic table size it accepts. + Then the negotiated dynamic table size is the minimum of + this option value and the value which backend specified. + Default: )" + << util::utos_unit(config->http2.downstream.encoder_dynamic_table_size) + << R"( + --backend-http2-decoder-dynamic-table-size=<SIZE> + Specify the maximum dynamic table size of HPACK decoder + in the backend HTTP/2 connection. + Default: )" + << util::utos_unit(config->http2.downstream.decoder_dynamic_table_size) + << R"( + +Mode: + (default mode) + Accept HTTP/2, and HTTP/1.1 over SSL/TLS. "no-tls" + parameter is used in --frontend option, accept HTTP/2 + and HTTP/1.1 over cleartext TCP. The incoming HTTP/1.1 + connection can be upgraded to HTTP/2 through HTTP + Upgrade. + -s, --http2-proxy + Like default mode, but enable forward proxy. This is so + called HTTP/2 proxy mode. + +Logging: + -L, --log-level=<LEVEL> + Set the severity level of log output. <LEVEL> must be + one of INFO, NOTICE, WARN, ERROR and FATAL. + Default: NOTICE + --accesslog-file=<PATH> + Set path to write access log. To reopen file, send USR1 + signal to nghttpx. + --accesslog-syslog + Send access log to syslog. If this option is used, + --accesslog-file option is ignored. + --accesslog-format=<FORMAT> + Specify format string for access log. The default + format is combined format. The following variables are + available: + + * $remote_addr: client IP address. + * $time_local: local time in Common Log format. + * $time_iso8601: local time in ISO 8601 format. + * $request: HTTP request line. + * $status: HTTP response status code. + * $body_bytes_sent: the number of bytes sent to client + as response body. + * $http_<VAR>: value of HTTP request header <VAR> where + '_' in <VAR> is replaced with '-'. + * $remote_port: client port. + * $server_port: server port. + * $request_time: request processing time in seconds with + milliseconds resolution. + * $pid: PID of the running process. + * $alpn: ALPN identifier of the protocol which generates + the response. For HTTP/1, ALPN is always http/1.1, + regardless of minor version. + * $tls_cipher: cipher used for SSL/TLS connection. + * $tls_client_fingerprint_sha256: SHA-256 fingerprint of + client certificate. + * $tls_client_fingerprint_sha1: SHA-1 fingerprint of + client certificate. + * $tls_client_subject_name: subject name in client + certificate. + * $tls_client_issuer_name: issuer name in client + certificate. + * $tls_client_serial: serial number in client + certificate. + * $tls_protocol: protocol for SSL/TLS connection. + * $tls_session_id: session ID for SSL/TLS connection. + * $tls_session_reused: "r" if SSL/TLS session was + reused. Otherwise, "." + * $tls_sni: SNI server name for SSL/TLS connection. + * $backend_host: backend host used to fulfill the + request. "-" if backend host is not available. + * $backend_port: backend port used to fulfill the + request. "-" if backend host is not available. + * $method: HTTP method + * $path: Request path including query. For CONNECT + request, authority is recorded. + * $path_without_query: $path up to the first '?' + character. For CONNECT request, authority is + recorded. + * $protocol_version: HTTP version (e.g., HTTP/1.1, + HTTP/2) + + The variable can be enclosed by "{" and "}" for + disambiguation (e.g., ${remote_addr}). + + Default: )" + << DEFAULT_ACCESSLOG_FORMAT << R"( + --accesslog-write-early + Write access log when response header fields are + received from backend rather than when request + transaction finishes. + --errorlog-file=<PATH> + Set path to write error log. To reopen file, send USR1 + signal to nghttpx. stderr will be redirected to the + error log file unless --errorlog-syslog is used. + Default: )" + << config->logging.error.file << R"( + --errorlog-syslog + Send error log to syslog. If this option is used, + --errorlog-file option is ignored. + --syslog-facility=<FACILITY> + Set syslog facility to <FACILITY>. + Default: )" + << str_syslog_facility(config->logging.syslog_facility) << R"( + +HTTP: + --add-x-forwarded-for + Append X-Forwarded-For header field to the downstream + request. + --strip-incoming-x-forwarded-for + Strip X-Forwarded-For header field from inbound client + requests. + --no-add-x-forwarded-proto + Don't append additional X-Forwarded-Proto header field + to the backend request. If inbound client sets + X-Forwarded-Proto, and + --no-strip-incoming-x-forwarded-proto option is used, + they are passed to the backend. + --no-strip-incoming-x-forwarded-proto + Don't strip X-Forwarded-Proto header field from inbound + client requests. + --add-forwarded=<LIST> + Append RFC 7239 Forwarded header field with parameters + specified in comma delimited list <LIST>. The supported + parameters are "by", "for", "host", and "proto". By + default, the value of "by" and "for" parameters are + obfuscated string. See --forwarded-by and + --forwarded-for options respectively. Note that nghttpx + does not translate non-standard X-Forwarded-* header + fields into Forwarded header field, and vice versa. + --strip-incoming-forwarded + Strip Forwarded header field from inbound client + requests. + --forwarded-by=(obfuscated|ip|<VALUE>) + Specify the parameter value sent out with "by" parameter + of Forwarded header field. If "obfuscated" is given, + the string is randomly generated at startup. If "ip" is + given, the interface address of the connection, + including port number, is sent with "by" parameter. In + case of UNIX domain socket, "localhost" is used instead + of address and port. User can also specify the static + obfuscated string. The limitation is that it must start + with "_", and only consists of character set + [A-Za-z0-9._-], as described in RFC 7239. + Default: obfuscated + --forwarded-for=(obfuscated|ip) + Specify the parameter value sent out with "for" + parameter of Forwarded header field. If "obfuscated" is + given, the string is randomly generated for each client + connection. If "ip" is given, the remote client address + of the connection, without port number, is sent with + "for" parameter. In case of UNIX domain socket, + "localhost" is used instead of address. + Default: obfuscated + --no-via Don't append to Via header field. If Via header field + is received, it is left unaltered. + --no-strip-incoming-early-data + Don't strip Early-Data header field from inbound client + requests. + --no-location-rewrite + Don't rewrite location header field in default mode. + When --http2-proxy is used, location header field will + not be altered regardless of this option. + --host-rewrite + Rewrite host and :authority header fields in default + mode. When --http2-proxy is used, these headers will + not be altered regardless of this option. + --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> + Specify protocol ID, port, host and origin of + alternative service. <HOST>, <ORIGIN> and <PARAMS> are + optional. Empty <HOST> and <ORIGIN> are allowed and + they are treated as nothing is specified. They are + advertised in alt-svc header field only in HTTP/1.1 + frontend. This option can be used multiple times to + specify multiple alternative services. + Example: --altsvc="h2,443,,,ma=3600; persist=1" + --http2-altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]> + Just like --altsvc option, but this altsvc is only sent + in HTTP/2 frontend. + --add-request-header=<HEADER> + Specify additional header field to add to request header + set. The field name must be lowercase. This option + just appends header field and won't replace anything + already set. This option can be used several times to + specify multiple header fields. + Example: --add-request-header="foo: bar" + --add-response-header=<HEADER> + Specify additional header field to add to response + header set. The field name must be lowercase. This + option just appends header field and won't replace + anything already set. This option can be used several + times to specify multiple header fields. + Example: --add-response-header="foo: bar" + --request-header-field-buffer=<SIZE> + Set maximum buffer size for incoming HTTP request header + field list. This is the sum of header name and value in + bytes. If trailer fields exist, they are counted + towards this number. + Default: )" + << util::utos_unit(config->http.request_header_field_buffer) << R"( + --max-request-header-fields=<N> + Set maximum number of incoming HTTP request header + fields. If trailer fields exist, they are counted + towards this number. + Default: )" + << config->http.max_request_header_fields << R"( + --response-header-field-buffer=<SIZE> + Set maximum buffer size for incoming HTTP response + header field list. This is the sum of header name and + value in bytes. If trailer fields exist, they are + counted towards this number. + Default: )" + << util::utos_unit(config->http.response_header_field_buffer) << R"( + --max-response-header-fields=<N> + Set maximum number of incoming HTTP response header + fields. If trailer fields exist, they are counted + towards this number. + Default: )" + << config->http.max_response_header_fields << R"( + --error-page=(<CODE>|*)=<PATH> + Set file path to custom error page served when nghttpx + originally generates HTTP error status code <CODE>. + <CODE> must be greater than or equal to 400, and at most + 599. If "*" is used instead of <CODE>, it matches all + HTTP status code. If error status code comes from + backend server, the custom error pages are not used. + --server-name=<NAME> + Change server response header field value to <NAME>. + Default: )" + << config->http.server_name << R"( + --no-server-rewrite + Don't rewrite server header field in default mode. When + --http2-proxy is used, these headers will not be altered + regardless of this option. + --redirect-https-port=<PORT> + Specify the port number which appears in Location header + field when redirect to HTTPS URI is made due to + "redirect-if-not-tls" parameter in --backend option. + Default: )" + << config->http.redirect_https_port << R"( + --require-http-scheme + Always require http or https scheme in HTTP request. It + also requires that https scheme must be used for an + encrypted connection. Otherwise, http scheme must be + used. This option is recommended for a server + deployment which directly faces clients and the services + it provides only require http or https scheme. + +API: + --api-max-request-body=<SIZE> + Set the maximum size of request body for API request. + Default: )" + << util::utos_unit(config->api.max_request_body) << R"( + +DNS: + --dns-cache-timeout=<DURATION> + Set duration that cached DNS results remain valid. Note + that nghttpx caches the unsuccessful results as well. + Default: )" + << util::duration_str(config->dns.timeout.cache) << R"( + --dns-lookup-timeout=<DURATION> + Set timeout that DNS server is given to respond to the + initial DNS query. For the 2nd and later queries, + server is given time based on this timeout, and it is + scaled linearly. + Default: )" + << util::duration_str(config->dns.timeout.lookup) << R"( + --dns-max-try=<N> + Set the number of DNS query before nghttpx gives up name + lookup. + Default: )" + << config->dns.max_try << R"( + --frontend-max-requests=<N> + The number of requests that single frontend connection + can process. For HTTP/2, this is the number of streams + in one HTTP/2 connection. For HTTP/1, this is the + number of keep alive requests. This is hint to nghttpx, + and it may allow additional few requests. The default + value is unlimited. + +Debug: + --frontend-http2-dump-request-header=<PATH> + Dumps request headers received by HTTP/2 frontend to the + file denoted in <PATH>. The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option -n<N>, where <N> >= 2. + --frontend-http2-dump-response-header=<PATH> + Dumps response headers sent from HTTP/2 frontend to the + file denoted in <PATH>. The output is done in HTTP/1 + header field format and each header block is followed by + an empty line. This option is not thread safe and MUST + NOT be used with option -n<N>, where <N> >= 2. + -o, --frontend-frame-debug + Print HTTP/2 frames in frontend to stderr. This option + is not thread safe and MUST NOT be used with option + -n=N, where N >= 2. + +Process: + -D, --daemon + Run in a background. If -D is used, the current working + directory is changed to '/'. + --pid-file=<PATH> + Set path to save PID of this program. + --user=<USER> + Run this program as <USER>. This option is intended to + be used to drop root privileges. + --single-process + Run this program in a single process mode for debugging + purpose. Without this option, nghttpx creates at least + 2 processes: main and worker processes. If this option + is used, main and worker are unified into a single + process. nghttpx still spawns additional process if + neverbleed is used. In the single process mode, the + signal handling feature is disabled. + --max-worker-processes=<N> + The maximum number of worker processes. nghttpx spawns + new worker process when it reloads its configuration. + The previous worker process enters graceful termination + period and will terminate when it finishes handling the + existing connections. However, if reloading + configurations happen very frequently, the worker + processes might be piled up if they take a bit long time + to finish the existing connections. With this option, + if the number of worker processes exceeds the given + value, the oldest worker process is terminated + immediately. Specifying 0 means no limit and it is the + default behaviour. + --worker-process-grace-shutdown-period=<DURATION> + Maximum period for a worker process to terminate + gracefully. When a worker process enters in graceful + shutdown period (e.g., when nghttpx reloads its + configuration) and it does not finish handling the + existing connections in the given period of time, it is + immediately terminated. Specifying 0 means no limit and + it is the default behaviour. + +Scripting: + --mruby-file=<PATH> + Set mruby script file + --ignore-per-pattern-mruby-error + Ignore mruby compile error for per-pattern mruby script + file. If error occurred, it is treated as if no mruby + file were specified for the pattern. +)"; + +#ifdef ENABLE_HTTP3 + out << R"( +HTTP/3 and QUIC: + --frontend-quic-idle-timeout=<DURATION> + Specify an idle timeout for QUIC connection. + Default: )" + << util::duration_str(config->quic.upstream.timeout.idle) << R"( + --frontend-quic-debug-log + Output QUIC debug log to /dev/stderr. + --quic-bpf-program-file=<PATH> + Specify a path to eBPF program file reuseport_kern.o to + direct an incoming QUIC UDP datagram to a correct + socket. + Default: )" + << config->quic.bpf.prog_file << R"( + --frontend-quic-early-data + Enable early data on frontend QUIC connections. nghttpx + sends "Early-Data" header field to a backend server if a + request is received in early data and handshake has not + finished. All backend servers should deal with possibly + replayed requests. + --frontend-quic-qlog-dir=<DIR> + Specify a directory where a qlog file is written for + frontend QUIC connections. A qlog file is created per + each QUIC connection. The file name is ISO8601 basic + format, followed by "-", server Source Connection ID and + ".sqlog". + --frontend-quic-require-token + Require an address validation token for a frontend QUIC + connection. Server sends a token in Retry packet or + NEW_TOKEN frame in the previous connection. + --frontend-quic-congestion-controller=<CC> + Specify a congestion controller algorithm for a frontend + QUIC connection. <CC> should be either "cubic" or + "bbr". + Default: )" + << (config->quic.upstream.congestion_controller == NGTCP2_CC_ALGO_CUBIC + ? "cubic" + : "bbr") + << R"( + --frontend-quic-secret-file=<PATH> + Path to file that contains secure random data to be used + as QUIC keying materials. It is used to derive keys for + encrypting tokens and Connection IDs. It is not used to + encrypt QUIC packets. Each line of this file must + contain exactly 136 bytes hex-encoded string (when + decoded the byte string is 68 bytes long). The first 2 + bits of decoded byte string are used to identify the + keying material. An empty line or a line which starts + '#' is ignored. The file can contain more than one + keying materials. Because the identifier is 2 bits, at + most 4 keying materials are read and the remaining data + is discarded. The first keying material in the file is + primarily used for encryption and decryption for new + connection. The other ones are used to decrypt data for + the existing connections. Specifying multiple keying + materials enables key rotation. Please note that key + rotation does not occur automatically. User should + update files or change options values and restart + nghttpx gracefully. If opening or reading given file + fails, all loaded keying materials are discarded and it + is treated as if none of this option is given. If this + option is not given or an error occurred while opening + or reading a file, a keying material is generated + internally on startup and reload. + --quic-server-id=<HEXSTRING> + Specify server ID encoded in Connection ID to identify + this particular server instance. Connection ID is + encrypted and this part is not visible in public. It + must be 4 bytes long and must be encoded in hex string + (which is 8 bytes long). If this option is omitted, a + random server ID is generated on startup and + configuration reload. + --frontend-quic-initial-rtt=<DURATION> + Specify the initial RTT of the frontend QUIC connection. + Default: )" + << util::duration_str(config->quic.upstream.initial_rtt) << R"( + --no-quic-bpf + Disable eBPF. + --frontend-http3-window-size=<SIZE> + Sets the per-stream initial window size of HTTP/3 + frontend connection. + Default: )" + << util::utos_unit(config->http3.upstream.window_size) << R"( + --frontend-http3-connection-window-size=<SIZE> + Sets the per-connection window size of HTTP/3 frontend + connection. + Default: )" + << util::utos_unit(config->http3.upstream.connection_window_size) << R"( + --frontend-http3-max-window-size=<SIZE> + Sets the maximum per-stream window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by --frontend-http3-window-size + and the window size grows up to <SIZE> bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_window_size) << R"( + --frontend-http3-max-connection-window-size=<SIZE> + Sets the maximum per-connection window size of HTTP/3 + frontend connection. The window size is adjusted based + on the receiving rate of stream data. The initial value + is the value specified by + --frontend-http3-connection-window-size and the window + size grows up to <SIZE> bytes. + Default: )" + << util::utos_unit(config->http3.upstream.max_connection_window_size) + << R"( + --frontend-http3-max-concurrent-streams=<N> + Set the maximum number of the concurrent streams in one + frontend HTTP/3 connection. + Default: )" + << config->http3.upstream.max_concurrent_streams << R"( +)"; +#endif // ENABLE_HTTP3 + + out << R"( +Misc: + --conf=<PATH> + Load configuration from <PATH>. Please note that + nghttpx always tries to read the default configuration + file if --conf is not given. + Default: )" + << config->conf_path << R"( + --include=<PATH> + Load additional configurations from <PATH>. File <PATH> + is read when configuration parser encountered this + option. This option can be used multiple times, or even + recursively. + -v, --version + Print version and exit. + -h, --help Print this help and exit. + +-- + + The <SIZE> argument is an integer and an optional unit (e.g., 10K is + 10 * 1024). Units are K, M and G (powers of 1024). + + The <DURATION> argument is an integer and an optional unit (e.g., 1s + is 1 second and 500ms is 500 milliseconds). Units are h, m, s or ms + (hours, minutes, seconds and milliseconds, respectively). If a unit + is omitted, a second is used as unit.)" + << std::endl; +} +} // namespace + +namespace { +int process_options(Config *config, + std::vector<std::pair<StringRef, StringRef>> &cmdcfgs) { + std::array<char, STRERROR_BUFSIZE> errbuf; + std::map<StringRef, size_t> pattern_addr_indexer; + if (conf_exists(config->conf_path.c_str())) { + LOG(NOTICE) << "Loading configuration from " << config->conf_path; + std::set<StringRef> include_set; + if (load_config(config, config->conf_path.c_str(), include_set, + pattern_addr_indexer) == -1) { + LOG(FATAL) << "Failed to load configuration from " << config->conf_path; + return -1; + } + assert(include_set.empty()); + } + + // Reopen log files using configurations in file + reopen_log_files(config->logging); + + { + std::set<StringRef> include_set; + + for (auto &p : cmdcfgs) { + if (parse_config(config, p.first, p.second, include_set, + pattern_addr_indexer) == -1) { + LOG(FATAL) << "Failed to parse command-line argument."; + return -1; + } + } + + assert(include_set.empty()); + } + + Log::set_severity_level(config->logging.severity); + + auto &loggingconf = config->logging; + + if (loggingconf.access.syslog || loggingconf.error.syslog) { + openlog("nghttpx", LOG_NDELAY | LOG_NOWAIT | LOG_PID, + loggingconf.syslog_facility); + } + + if (reopen_log_files(config->logging) != 0) { + LOG(FATAL) << "Failed to open log file"; + return -1; + } + + redirect_stderr_to_errorlog(loggingconf); + + if (config->uid != 0) { + if (log_config()->accesslog_fd != -1 && + fchown(log_config()->accesslog_fd, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of access log file failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + if (log_config()->errorlog_fd != -1 && + fchown(log_config()->errorlog_fd, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of error log file failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + + if (config->single_thread) { + LOG(WARN) << "single-thread: Set workers to 1"; + config->num_worker = 1; + } + + auto &http2conf = config->http2; + { + auto &dumpconf = http2conf.upstream.debug.dump; + + if (!dumpconf.request_header_file.empty()) { + auto path = dumpconf.request_header_file.c_str(); + auto f = open_file_for_write(path); + + if (f == nullptr) { + LOG(FATAL) << "Failed to open http2 upstream request header file: " + << path; + return -1; + } + + dumpconf.request_header = f; + + if (config->uid != 0) { + if (chown(path, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of http2 upstream request header file " + << path << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + } + + if (!dumpconf.response_header_file.empty()) { + auto path = dumpconf.response_header_file.c_str(); + auto f = open_file_for_write(path); + + if (f == nullptr) { + LOG(FATAL) << "Failed to open http2 upstream response header file: " + << path; + return -1; + } + + dumpconf.response_header = f; + + if (config->uid != 0) { + if (chown(path, config->uid, config->gid) == -1) { + auto error = errno; + LOG(WARN) << "Changing owner of http2 upstream response header file" + << " " << path << " failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + } + } + + auto &tlsconf = config->tls; + + if (tlsconf.alpn_list.empty()) { + tlsconf.alpn_list = util::split_str(DEFAULT_ALPN_LIST, ','); + } + + if (!tlsconf.tls_proto_list.empty()) { + tlsconf.tls_proto_mask = tls::create_tls_proto_mask(tlsconf.tls_proto_list); + } + + // TODO We depends on the ordering of protocol version macro in + // OpenSSL. + if (tlsconf.min_proto_version > tlsconf.max_proto_version) { + LOG(ERROR) << "tls-max-proto-version must be equal to or larger than " + "tls-min-proto-version"; + return -1; + } + + if (tls::set_alpn_prefs(tlsconf.alpn_prefs, tlsconf.alpn_list) != 0) { + return -1; + } + + tlsconf.bio_method = create_bio_method(); + + auto &listenerconf = config->conn.listener; + auto &upstreamconf = config->conn.upstream; + + if (listenerconf.addrs.empty()) { + UpstreamAddr addr{}; + addr.host = StringRef::from_lit("*"); + addr.port = 3000; + addr.tls = true; + addr.family = AF_INET; + addr.index = 0; + listenerconf.addrs.push_back(addr); + addr.family = AF_INET6; + addr.index = 1; + listenerconf.addrs.push_back(std::move(addr)); + } + + if (upstreamconf.worker_connections == 0) { + upstreamconf.worker_connections = std::numeric_limits<size_t>::max(); + } + + if (tls::upstream_tls_enabled(config->conn) && + (tlsconf.private_key_file.empty() || tlsconf.cert_file.empty())) { + LOG(FATAL) << "TLS private key and certificate files are required. " + "Specify them in command-line, or in configuration file " + "using private-key-file and certificate-file options."; + return -1; + } + + if (tls::upstream_tls_enabled(config->conn) && !tlsconf.ocsp.disabled) { + struct stat buf; + if (stat(tlsconf.ocsp.fetch_ocsp_response_file.c_str(), &buf) != 0) { + tlsconf.ocsp.disabled = true; + LOG(WARN) << "--fetch-ocsp-response-file: " + << tlsconf.ocsp.fetch_ocsp_response_file + << " not found. OCSP stapling has been disabled."; + } + } + + if (configure_downstream_group(config, config->http2_proxy, false, tlsconf) != + 0) { + return -1; + } + + std::array<char, util::max_hostport> hostport_buf; + + auto &proxy = config->downstream_http_proxy; + if (!proxy.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{proxy.host}, proxy.port); + if (resolve_hostname(&proxy.addr, proxy.host.c_str(), proxy.port, + AF_UNSPEC) == -1) { + LOG(FATAL) << "Resolving backend HTTP proxy address failed: " << hostport; + return -1; + } + LOG(NOTICE) << "Backend HTTP proxy address: " << hostport << " -> " + << util::to_numeric_addr(&proxy.addr); + } + + { + auto &memcachedconf = tlsconf.session_cache.memcached; + if (!memcachedconf.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{memcachedconf.host}, + memcachedconf.port); + if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), + memcachedconf.port, memcachedconf.family) == -1) { + LOG(FATAL) + << "Resolving memcached address for TLS session cache failed: " + << hostport; + return -1; + } + LOG(NOTICE) << "Memcached address for TLS session cache: " << hostport + << " -> " << util::to_numeric_addr(&memcachedconf.addr); + if (memcachedconf.tls) { + LOG(NOTICE) << "Connection to memcached for TLS session cache will be " + "encrypted by TLS"; + } + } + } + + { + auto &memcachedconf = tlsconf.ticket.memcached; + if (!memcachedconf.host.empty()) { + auto hostport = util::make_hostport(std::begin(hostport_buf), + StringRef{memcachedconf.host}, + memcachedconf.port); + if (resolve_hostname(&memcachedconf.addr, memcachedconf.host.c_str(), + memcachedconf.port, memcachedconf.family) == -1) { + LOG(FATAL) << "Resolving memcached address for TLS ticket key failed: " + << hostport; + return -1; + } + LOG(NOTICE) << "Memcached address for TLS ticket key: " << hostport + << " -> " << util::to_numeric_addr(&memcachedconf.addr); + if (memcachedconf.tls) { + LOG(NOTICE) << "Connection to memcached for TLS ticket key will be " + "encrypted by TLS"; + } + } + } + + if (config->rlimit_nofile) { + struct rlimit lim = {static_cast<rlim_t>(config->rlimit_nofile), + static_cast<rlim_t>(config->rlimit_nofile)}; + if (setrlimit(RLIMIT_NOFILE, &lim) != 0) { + auto error = errno; + LOG(WARN) << "Setting rlimit-nofile failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } + +#ifdef RLIMIT_MEMLOCK + if (config->rlimit_memlock) { + struct rlimit lim = {static_cast<rlim_t>(config->rlimit_memlock), + static_cast<rlim_t>(config->rlimit_memlock)}; + if (setrlimit(RLIMIT_MEMLOCK, &lim) != 0) { + auto error = errno; + LOG(WARN) << "Setting rlimit-memlock failed: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + } + } +#endif // RLIMIT_MEMLOCK + + auto &fwdconf = config->http.forwarded; + + if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED && + fwdconf.by_obfuscated.empty()) { + // 2 for '_' and terminal NULL + auto iov = make_byte_ref(config->balloc, SHRPX_OBFUSCATED_NODE_LENGTH + 2); + auto p = iov.base; + *p++ = '_'; + auto gen = util::make_mt19937(); + p = util::random_alpha_digit(p, p + SHRPX_OBFUSCATED_NODE_LENGTH, gen); + *p = '\0'; + fwdconf.by_obfuscated = StringRef{iov.base, p}; + } + + if (config->http2.upstream.debug.frame_debug) { + // To make it sync to logging + set_output(stderr); + if (isatty(fileno(stdout))) { + set_color_output(true); + } + reset_timer(); + } + + config->http2.upstream.callbacks = create_http2_upstream_callbacks(); + config->http2.downstream.callbacks = create_http2_downstream_callbacks(); + + if (!config->http.altsvcs.empty()) { + config->http.altsvc_header_value = + http::create_altsvc_header_value(config->balloc, config->http.altsvcs); + } + + if (!config->http.http2_altsvcs.empty()) { + config->http.http2_altsvc_header_value = http::create_altsvc_header_value( + config->balloc, config->http.http2_altsvcs); + } + + return 0; +} +} // namespace + +namespace { +// Closes file descriptor which are opened for listeners in config, +// and are not inherited from |iaddrs|. +void close_not_inherited_fd(Config *config, + const std::vector<InheritedAddr> &iaddrs) { + auto &listenerconf = config->conn.listener; + + for (auto &addr : listenerconf.addrs) { + auto inherited = std::find_if( + std::begin(iaddrs), std::end(iaddrs), + [&addr](const InheritedAddr &iaddr) { return addr.fd == iaddr.fd; }); + + if (inherited != std::end(iaddrs)) { + continue; + } + + close(addr.fd); + } +} +} // namespace + +namespace { +void reload_config() { + int rv; + + LOG(NOTICE) << "Reloading configuration"; + + auto cur_config = mod_config(); + auto new_config = std::make_unique<Config>(); + + fill_default_config(new_config.get()); + + new_config->conf_path = + make_string_ref(new_config->balloc, cur_config->conf_path); + // daemon option is ignored here. + new_config->daemon = cur_config->daemon; + // loop is reused, and ev_loop_flags gets ignored + new_config->ev_loop_flags = cur_config->ev_loop_flags; + new_config->config_revision = cur_config->config_revision + 1; + + rv = process_options(new_config.get(), suconfig.cmdcfgs); + if (rv != 0) { + LOG(ERROR) << "Failed to process new configuration"; + return; + } + + auto iaddrs = get_inherited_addr_from_config(new_config->balloc, cur_config); + + if (create_acceptor_socket(new_config.get(), iaddrs) != 0) { + close_not_inherited_fd(new_config.get(), iaddrs); + return; + } + + // According to libev documentation, flags are ignored since we have + // already created first default loop. + auto loop = ev_default_loop(new_config->ev_loop_flags); + + int ipc_fd = 0; +#ifdef ENABLE_HTTP3 + int quic_ipc_fd = 0; + + auto quic_lwps = collect_quic_lingering_worker_processes(); + + std::vector<std::array<uint8_t, SHRPX_QUIC_CID_PREFIXLEN>> cid_prefixes; + + if (generate_cid_prefix(cid_prefixes, new_config.get()) != 0) { + close_not_inherited_fd(new_config.get(), iaddrs); + return; + } +#endif // ENABLE_HTTP3 + + // fork_worker_process and forked child process assumes new + // configuration can be obtained from get_config(). + + auto old_config = replace_config(std::move(new_config)); + + auto pid = fork_worker_process(ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd +#endif // ENABLE_HTTP3 + + , + iaddrs +#ifdef ENABLE_HTTP3 + , + cid_prefixes, quic_lwps +#endif // ENABLE_HTTP3 + ); + + if (pid == -1) { + LOG(ERROR) << "Failed to process new configuration"; + + new_config = replace_config(std::move(old_config)); + close_not_inherited_fd(new_config.get(), iaddrs); + + return; + } + + close_unused_inherited_addr(iaddrs); + + worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd +#ifdef ENABLE_HTTP3 + , + quic_ipc_fd, cid_prefixes +#endif // ENABLE_HTTP3 + )); + + worker_process_adjust_limit(); + + if (!get_config()->pid_file.empty()) { + save_pid(); + } +} +} // namespace + +int main(int argc, char **argv) { + int rv; + std::array<char, STRERROR_BUFSIZE> errbuf; + +#ifdef HAVE_LIBBPF + libbpf_set_strict_mode(LIBBPF_STRICT_ALL); +#endif // HAVE_LIBBPF + + Log::set_severity_level(NOTICE); + create_config(); + fill_default_config(mod_config()); + + // make copy of stderr + store_original_fds(); + + // First open log files with default configuration, so that we can + // log errors/warnings while reading configuration files. + reopen_log_files(get_config()->logging); + + suconfig.original_argv = argv; + + // We have to copy argv, since getopt_long may change its content. + suconfig.argc = argc; + suconfig.argv = new char *[argc]; + + for (int i = 0; i < argc; ++i) { + suconfig.argv[i] = strdup(argv[i]); + if (suconfig.argv[i] == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to copy argv: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + exit(EXIT_FAILURE); + } + } + + suconfig.cwd = getcwd(nullptr, 0); + if (suconfig.cwd == nullptr) { + auto error = errno; + LOG(FATAL) << "failed to get current working directory: errno=" << error; + exit(EXIT_FAILURE); + } + + auto &cmdcfgs = suconfig.cmdcfgs; + + while (1) { + static int flag = 0; + static constexpr option long_options[] = { + {SHRPX_OPT_DAEMON.c_str(), no_argument, nullptr, 'D'}, + {SHRPX_OPT_LOG_LEVEL.c_str(), required_argument, nullptr, 'L'}, + {SHRPX_OPT_BACKEND.c_str(), required_argument, nullptr, 'b'}, + {SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), required_argument, + nullptr, 'c'}, + {SHRPX_OPT_FRONTEND.c_str(), required_argument, nullptr, 'f'}, + {"help", no_argument, nullptr, 'h'}, + {SHRPX_OPT_INSECURE.c_str(), no_argument, nullptr, 'k'}, + {SHRPX_OPT_WORKERS.c_str(), required_argument, nullptr, 'n'}, + {SHRPX_OPT_CLIENT_PROXY.c_str(), no_argument, nullptr, 'p'}, + {SHRPX_OPT_HTTP2_PROXY.c_str(), no_argument, nullptr, 's'}, + {"version", no_argument, nullptr, 'v'}, + {SHRPX_OPT_FRONTEND_FRAME_DEBUG.c_str(), no_argument, nullptr, 'o'}, + {SHRPX_OPT_ADD_X_FORWARDED_FOR.c_str(), no_argument, &flag, 1}, + {SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT.c_str(), required_argument, + &flag, 2}, + {SHRPX_OPT_FRONTEND_READ_TIMEOUT.c_str(), required_argument, &flag, 3}, + {SHRPX_OPT_FRONTEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 4}, + {SHRPX_OPT_BACKEND_READ_TIMEOUT.c_str(), required_argument, &flag, 5}, + {SHRPX_OPT_BACKEND_WRITE_TIMEOUT.c_str(), required_argument, &flag, 6}, + {SHRPX_OPT_ACCESSLOG_FILE.c_str(), required_argument, &flag, 7}, + {SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, &flag, + 8}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 9}, + {SHRPX_OPT_PID_FILE.c_str(), required_argument, &flag, 10}, + {SHRPX_OPT_USER.c_str(), required_argument, &flag, 11}, + {"conf", required_argument, &flag, 12}, + {SHRPX_OPT_SYSLOG_FACILITY.c_str(), required_argument, &flag, 14}, + {SHRPX_OPT_BACKLOG.c_str(), required_argument, &flag, 15}, + {SHRPX_OPT_CIPHERS.c_str(), required_argument, &flag, 16}, + {SHRPX_OPT_CLIENT.c_str(), no_argument, &flag, 17}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS.c_str(), required_argument, &flag, + 18}, + {SHRPX_OPT_CACERT.c_str(), required_argument, &flag, 19}, + {SHRPX_OPT_BACKEND_IPV4.c_str(), no_argument, &flag, 20}, + {SHRPX_OPT_BACKEND_IPV6.c_str(), no_argument, &flag, 21}, + {SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE.c_str(), required_argument, &flag, + 22}, + {SHRPX_OPT_NO_VIA.c_str(), no_argument, &flag, 23}, + {SHRPX_OPT_SUBCERT.c_str(), required_argument, &flag, 24}, + {SHRPX_OPT_HTTP2_BRIDGE.c_str(), no_argument, &flag, 25}, + {SHRPX_OPT_BACKEND_HTTP_PROXY_URI.c_str(), required_argument, &flag, + 26}, + {SHRPX_OPT_BACKEND_NO_TLS.c_str(), no_argument, &flag, 27}, + {SHRPX_OPT_OCSP_STARTUP.c_str(), no_argument, &flag, 28}, + {SHRPX_OPT_FRONTEND_NO_TLS.c_str(), no_argument, &flag, 29}, + {SHRPX_OPT_NO_VERIFY_OCSP.c_str(), no_argument, &flag, 30}, + {SHRPX_OPT_BACKEND_TLS_SNI_FIELD.c_str(), required_argument, &flag, 31}, + {SHRPX_OPT_DH_PARAM_FILE.c_str(), required_argument, &flag, 33}, + {SHRPX_OPT_READ_RATE.c_str(), required_argument, &flag, 34}, + {SHRPX_OPT_READ_BURST.c_str(), required_argument, &flag, 35}, + {SHRPX_OPT_WRITE_RATE.c_str(), required_argument, &flag, 36}, + {SHRPX_OPT_WRITE_BURST.c_str(), required_argument, &flag, 37}, + {SHRPX_OPT_NPN_LIST.c_str(), required_argument, &flag, 38}, + {SHRPX_OPT_VERIFY_CLIENT.c_str(), no_argument, &flag, 39}, + {SHRPX_OPT_VERIFY_CLIENT_CACERT.c_str(), required_argument, &flag, 40}, + {SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE.c_str(), required_argument, &flag, + 41}, + {SHRPX_OPT_CLIENT_CERT_FILE.c_str(), required_argument, &flag, 42}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER.c_str(), + required_argument, &flag, 43}, + {SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER.c_str(), + required_argument, &flag, 44}, + {SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING.c_str(), no_argument, &flag, 45}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 46}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS.c_str(), + required_argument, &flag, 47}, + {SHRPX_OPT_TLS_PROTO_LIST.c_str(), required_argument, &flag, 48}, + {SHRPX_OPT_PADDING.c_str(), required_argument, &flag, 49}, + {SHRPX_OPT_WORKER_READ_RATE.c_str(), required_argument, &flag, 50}, + {SHRPX_OPT_WORKER_READ_BURST.c_str(), required_argument, &flag, 51}, + {SHRPX_OPT_WORKER_WRITE_RATE.c_str(), required_argument, &flag, 52}, + {SHRPX_OPT_WORKER_WRITE_BURST.c_str(), required_argument, &flag, 53}, + {SHRPX_OPT_ALTSVC.c_str(), required_argument, &flag, 54}, + {SHRPX_OPT_ADD_RESPONSE_HEADER.c_str(), required_argument, &flag, 55}, + {SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS.c_str(), required_argument, + &flag, 56}, + {SHRPX_OPT_ACCESSLOG_SYSLOG.c_str(), no_argument, &flag, 57}, + {SHRPX_OPT_ERRORLOG_FILE.c_str(), required_argument, &flag, 58}, + {SHRPX_OPT_ERRORLOG_SYSLOG.c_str(), no_argument, &flag, 59}, + {SHRPX_OPT_STREAM_READ_TIMEOUT.c_str(), required_argument, &flag, 60}, + {SHRPX_OPT_STREAM_WRITE_TIMEOUT.c_str(), required_argument, &flag, 61}, + {SHRPX_OPT_NO_LOCATION_REWRITE.c_str(), no_argument, &flag, 62}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST.c_str(), + required_argument, &flag, 63}, + {SHRPX_OPT_LISTENER_DISABLE_TIMEOUT.c_str(), required_argument, &flag, + 64}, + {SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR.c_str(), no_argument, &flag, + 65}, + {SHRPX_OPT_ACCESSLOG_FORMAT.c_str(), required_argument, &flag, 66}, + {SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND.c_str(), + required_argument, &flag, 67}, + {SHRPX_OPT_TLS_TICKET_KEY_FILE.c_str(), required_argument, &flag, 68}, + {SHRPX_OPT_RLIMIT_NOFILE.c_str(), required_argument, &flag, 69}, + {SHRPX_OPT_BACKEND_RESPONSE_BUFFER.c_str(), required_argument, &flag, + 71}, + {SHRPX_OPT_BACKEND_REQUEST_BUFFER.c_str(), required_argument, &flag, + 72}, + {SHRPX_OPT_NO_HOST_REWRITE.c_str(), no_argument, &flag, 73}, + {SHRPX_OPT_NO_SERVER_PUSH.c_str(), no_argument, &flag, 74}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER.c_str(), + required_argument, &flag, 76}, + {SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE.c_str(), required_argument, &flag, + 77}, + {SHRPX_OPT_OCSP_UPDATE_INTERVAL.c_str(), required_argument, &flag, 78}, + {SHRPX_OPT_NO_OCSP.c_str(), no_argument, &flag, 79}, + {SHRPX_OPT_HEADER_FIELD_BUFFER.c_str(), required_argument, &flag, 80}, + {SHRPX_OPT_MAX_HEADER_FIELDS.c_str(), required_argument, &flag, 81}, + {SHRPX_OPT_ADD_REQUEST_HEADER.c_str(), required_argument, &flag, 82}, + {SHRPX_OPT_INCLUDE.c_str(), required_argument, &flag, 83}, + {SHRPX_OPT_TLS_TICKET_KEY_CIPHER.c_str(), required_argument, &flag, 84}, + {SHRPX_OPT_HOST_REWRITE.c_str(), no_argument, &flag, 85}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED.c_str(), required_argument, + &flag, 86}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED.c_str(), required_argument, &flag, + 87}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL.c_str(), required_argument, + &flag, 88}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY.c_str(), + required_argument, &flag, 89}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL.c_str(), required_argument, + &flag, 90}, + {SHRPX_OPT_MRUBY_FILE.c_str(), required_argument, &flag, 91}, + {SHRPX_OPT_ACCEPT_PROXY_PROTOCOL.c_str(), no_argument, &flag, 93}, + {SHRPX_OPT_FASTOPEN.c_str(), required_argument, &flag, 94}, + {SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD.c_str(), required_argument, + &flag, 95}, + {SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 96}, + {SHRPX_OPT_ADD_FORWARDED.c_str(), required_argument, &flag, 97}, + {SHRPX_OPT_STRIP_INCOMING_FORWARDED.c_str(), no_argument, &flag, 98}, + {SHRPX_OPT_FORWARDED_BY.c_str(), required_argument, &flag, 99}, + {SHRPX_OPT_FORWARDED_FOR.c_str(), required_argument, &flag, 100}, + {SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 101}, + {SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS.c_str(), required_argument, &flag, + 102}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, &flag, 103}, + {SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER.c_str(), required_argument, + &flag, 104}, + {SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS.c_str(), required_argument, &flag, + 105}, + {SHRPX_OPT_BACKEND_HTTP1_TLS.c_str(), no_argument, &flag, 106}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS.c_str(), no_argument, &flag, + 108}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 109}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 110}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS.c_str(), no_argument, &flag, + 111}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE.c_str(), + required_argument, &flag, 112}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE.c_str(), + required_argument, &flag, 113}, + {SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 114}, + {SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY.c_str(), + required_argument, &flag, 115}, + {SHRPX_OPT_BACKEND_ADDRESS_FAMILY.c_str(), required_argument, &flag, + 116}, + {SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 117}, + {SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 118}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND.c_str(), required_argument, + &flag, 119}, + {SHRPX_OPT_BACKEND_TLS.c_str(), no_argument, &flag, 120}, + {SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST.c_str(), required_argument, + &flag, 121}, + {SHRPX_OPT_ERROR_PAGE.c_str(), required_argument, &flag, 122}, + {SHRPX_OPT_NO_KQUEUE.c_str(), no_argument, &flag, 123}, + {SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 124}, + {SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT.c_str(), required_argument, + &flag, 125}, + {SHRPX_OPT_API_MAX_REQUEST_BODY.c_str(), required_argument, &flag, 126}, + {SHRPX_OPT_BACKEND_MAX_BACKOFF.c_str(), required_argument, &flag, 127}, + {SHRPX_OPT_SERVER_NAME.c_str(), required_argument, &flag, 128}, + {SHRPX_OPT_NO_SERVER_REWRITE.c_str(), no_argument, &flag, 129}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE.c_str(), + no_argument, &flag, 130}, + {SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE.c_str(), no_argument, + &flag, 131}, + {SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 132}, + {SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 133}, + {SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE.c_str(), required_argument, &flag, + 134}, + {SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 135}, + {SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 136}, + {SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 137}, + {SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 138}, + {SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE.c_str(), + required_argument, &flag, 139}, + {SHRPX_OPT_ECDH_CURVES.c_str(), required_argument, &flag, 140}, + {SHRPX_OPT_TLS_SCT_DIR.c_str(), required_argument, &flag, 141}, + {SHRPX_OPT_BACKEND_CONNECT_TIMEOUT.c_str(), required_argument, &flag, + 142}, + {SHRPX_OPT_DNS_CACHE_TIMEOUT.c_str(), required_argument, &flag, 143}, + {SHRPX_OPT_DNS_LOOKUP_TIMEOUT.c_str(), required_argument, &flag, 144}, + {SHRPX_OPT_DNS_MAX_TRY.c_str(), required_argument, &flag, 145}, + {SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT.c_str(), required_argument, + &flag, 146}, + {SHRPX_OPT_PSK_SECRETS.c_str(), required_argument, &flag, 147}, + {SHRPX_OPT_CLIENT_PSK_SECRETS.c_str(), required_argument, &flag, 148}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST.c_str(), no_argument, + &flag, 149}, + {SHRPX_OPT_CLIENT_CIPHERS.c_str(), required_argument, &flag, 150}, + {SHRPX_OPT_ACCESSLOG_WRITE_EARLY.c_str(), no_argument, &flag, 151}, + {SHRPX_OPT_TLS_MIN_PROTO_VERSION.c_str(), required_argument, &flag, + 152}, + {SHRPX_OPT_TLS_MAX_PROTO_VERSION.c_str(), required_argument, &flag, + 153}, + {SHRPX_OPT_REDIRECT_HTTPS_PORT.c_str(), required_argument, &flag, 154}, + {SHRPX_OPT_FRONTEND_MAX_REQUESTS.c_str(), required_argument, &flag, + 155}, + {SHRPX_OPT_SINGLE_THREAD.c_str(), no_argument, &flag, 156}, + {SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO.c_str(), no_argument, &flag, 157}, + {SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO.c_str(), no_argument, + &flag, 158}, + {SHRPX_OPT_SINGLE_PROCESS.c_str(), no_argument, &flag, 159}, + {SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED.c_str(), no_argument, &flag, + 160}, + {SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR.c_str(), no_argument, &flag, + 161}, + {SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA.c_str(), no_argument, &flag, 162}, + {SHRPX_OPT_TLS_MAX_EARLY_DATA.c_str(), required_argument, &flag, 163}, + {SHRPX_OPT_TLS13_CIPHERS.c_str(), required_argument, &flag, 164}, + {SHRPX_OPT_TLS13_CLIENT_CIPHERS.c_str(), required_argument, &flag, 165}, + {SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA.c_str(), no_argument, &flag, + 166}, + {SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, &flag, 167}, + {SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST.c_str(), no_argument, + &flag, 168}, + {SHRPX_OPT_QUIC_BPF_PROGRAM_FILE.c_str(), required_argument, &flag, + 169}, + {SHRPX_OPT_NO_QUIC_BPF.c_str(), no_argument, &flag, 170}, + {SHRPX_OPT_HTTP2_ALTSVC.c_str(), required_argument, &flag, 171}, + {SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT.c_str(), required_argument, + &flag, 172}, + {SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT.c_str(), required_argument, &flag, + 173}, + {SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG.c_str(), no_argument, &flag, 174}, + {SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE.c_str(), required_argument, &flag, + 175}, + {SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 176}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE.c_str(), required_argument, + &flag, 177}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE.c_str(), + required_argument, &flag, 178}, + {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(), + required_argument, &flag, 179}, + {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180}, + {SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag, + 181}, + {SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN.c_str(), no_argument, &flag, + 182}, + {SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER.c_str(), + required_argument, &flag, 183}, + {SHRPX_OPT_QUIC_SERVER_ID.c_str(), required_argument, &flag, 185}, + {SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE.c_str(), required_argument, &flag, + 186}, + {SHRPX_OPT_RLIMIT_MEMLOCK.c_str(), required_argument, &flag, 187}, + {SHRPX_OPT_MAX_WORKER_PROCESSES.c_str(), required_argument, &flag, 188}, + {SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD.c_str(), + required_argument, &flag, 189}, + {SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT.c_str(), required_argument, &flag, + 190}, + {SHRPX_OPT_REQUIRE_HTTP_SCHEME.c_str(), no_argument, &flag, 191}, + {SHRPX_OPT_TLS_KTLS.c_str(), no_argument, &flag, 192}, + {SHRPX_OPT_ALPN_LIST.c_str(), required_argument, &flag, 193}, + {nullptr, 0, nullptr, 0}}; + + int option_index = 0; + int c = getopt_long(argc, argv, "DL:b:c:f:hkn:opsv", long_options, + &option_index); + if (c == -1) { + break; + } + switch (c) { + case 'D': + cmdcfgs.emplace_back(SHRPX_OPT_DAEMON, StringRef::from_lit("yes")); + break; + case 'L': + cmdcfgs.emplace_back(SHRPX_OPT_LOG_LEVEL, StringRef{optarg}); + break; + case 'b': + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND, StringRef{optarg}); + break; + case 'c': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 'f': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND, StringRef{optarg}); + break; + case 'h': + print_help(std::cout); + exit(EXIT_SUCCESS); + case 'k': + cmdcfgs.emplace_back(SHRPX_OPT_INSECURE, StringRef::from_lit("yes")); + break; + case 'n': + cmdcfgs.emplace_back(SHRPX_OPT_WORKERS, StringRef{optarg}); + break; + case 'o': + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_FRAME_DEBUG, + StringRef::from_lit("yes")); + break; + case 'p': + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PROXY, StringRef::from_lit("yes")); + break; + case 's': + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_PROXY, StringRef::from_lit("yes")); + break; + case 'v': + print_version(std::cout); + exit(EXIT_SUCCESS); + case '?': + util::show_candidates(argv[optind - 1], long_options); + exit(EXIT_FAILURE); + case 0: + switch (flag) { + case 1: + // --add-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_ADD_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 2: + // --frontend-http2-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_READ_TIMEOUT, + StringRef{optarg}); + break; + case 3: + // --frontend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_READ_TIMEOUT, + StringRef{optarg}); + break; + case 4: + // --frontend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 5: + // --backend-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_READ_TIMEOUT, StringRef{optarg}); + break; + case 6: + // --backend-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_WRITE_TIMEOUT, + StringRef{optarg}); + break; + case 7: + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FILE, StringRef{optarg}); + break; + case 8: + // --backend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 9: + // --frontend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 10: + cmdcfgs.emplace_back(SHRPX_OPT_PID_FILE, StringRef{optarg}); + break; + case 11: + cmdcfgs.emplace_back(SHRPX_OPT_USER, StringRef{optarg}); + break; + case 12: + // --conf + mod_config()->conf_path = + make_string_ref(mod_config()->balloc, StringRef{optarg}); + break; + case 14: + // --syslog-facility + cmdcfgs.emplace_back(SHRPX_OPT_SYSLOG_FACILITY, StringRef{optarg}); + break; + case 15: + // --backlog + cmdcfgs.emplace_back(SHRPX_OPT_BACKLOG, StringRef{optarg}); + break; + case 16: + // --ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CIPHERS, StringRef{optarg}); + break; + case 17: + // --client + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT, StringRef::from_lit("yes")); + break; + case 18: + // --backend-http2-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_BITS, + StringRef{optarg}); + break; + case 19: + // --cacert + cmdcfgs.emplace_back(SHRPX_OPT_CACERT, StringRef{optarg}); + break; + case 20: + // --backend-ipv4 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV4, + StringRef::from_lit("yes")); + break; + case 21: + // --backend-ipv6 + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_IPV6, + StringRef::from_lit("yes")); + break; + case 22: + // --private-key-passwd-file + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_PASSWD_FILE, + StringRef{optarg}); + break; + case 23: + // --no-via + cmdcfgs.emplace_back(SHRPX_OPT_NO_VIA, StringRef::from_lit("yes")); + break; + case 24: + // --subcert + cmdcfgs.emplace_back(SHRPX_OPT_SUBCERT, StringRef{optarg}); + break; + case 25: + // --http2-bridge + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_BRIDGE, + StringRef::from_lit("yes")); + break; + case 26: + // --backend-http-proxy-uri + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP_PROXY_URI, + StringRef{optarg}); + break; + case 27: + // --backend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 28: + // --ocsp-startup + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_STARTUP, + StringRef::from_lit("yes")); + break; + case 29: + // --frontend-no-tls + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_NO_TLS, + StringRef::from_lit("yes")); + break; + case 30: + // --no-verify-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_VERIFY_OCSP, + StringRef::from_lit("yes")); + break; + case 31: + // --backend-tls-sni-field + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS_SNI_FIELD, + StringRef{optarg}); + break; + case 33: + // --dh-param-file + cmdcfgs.emplace_back(SHRPX_OPT_DH_PARAM_FILE, StringRef{optarg}); + break; + case 34: + // --read-rate + cmdcfgs.emplace_back(SHRPX_OPT_READ_RATE, StringRef{optarg}); + break; + case 35: + // --read-burst + cmdcfgs.emplace_back(SHRPX_OPT_READ_BURST, StringRef{optarg}); + break; + case 36: + // --write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_RATE, StringRef{optarg}); + break; + case 37: + // --write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WRITE_BURST, StringRef{optarg}); + break; + case 38: + // --npn-list + cmdcfgs.emplace_back(SHRPX_OPT_NPN_LIST, StringRef{optarg}); + break; + case 39: + // --verify-client + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT, + StringRef::from_lit("yes")); + break; + case 40: + // --verify-client-cacert + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_CACERT, StringRef{optarg}); + break; + case 41: + // --client-private-key-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 42: + // --client-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CERT_FILE, StringRef{optarg}); + break; + case 43: + // --frontend-http2-dump-request-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + StringRef{optarg}); + break; + case 44: + // --frontend-http2-dump-response-header + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + StringRef{optarg}); + break; + case 45: + // --http2-no-cookie-crumbling + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING, + StringRef::from_lit("yes")); + break; + case 46: + // --frontend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 47: + // --backend-http2-connection-window-bits + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_BITS, + StringRef{optarg}); + break; + case 48: + // --tls-proto-list + cmdcfgs.emplace_back(SHRPX_OPT_TLS_PROTO_LIST, StringRef{optarg}); + break; + case 49: + // --padding + cmdcfgs.emplace_back(SHRPX_OPT_PADDING, StringRef{optarg}); + break; + case 50: + // --worker-read-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_RATE, StringRef{optarg}); + break; + case 51: + // --worker-read-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_READ_BURST, StringRef{optarg}); + break; + case 52: + // --worker-write-rate + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_RATE, StringRef{optarg}); + break; + case 53: + // --worker-write-burst + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_WRITE_BURST, StringRef{optarg}); + break; + case 54: + // --altsvc + cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, StringRef{optarg}); + break; + case 55: + // --add-response-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, StringRef{optarg}); + break; + case 56: + // --worker-frontend-connections + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_FRONTEND_CONNECTIONS, + StringRef{optarg}); + break; + case 57: + // --accesslog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 58: + // --errorlog-file + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_FILE, StringRef{optarg}); + break; + case 59: + // --errorlog-syslog + cmdcfgs.emplace_back(SHRPX_OPT_ERRORLOG_SYSLOG, + StringRef::from_lit("yes")); + break; + case 60: + // --stream-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_READ_TIMEOUT, StringRef{optarg}); + break; + case 61: + // --stream-write-timeout + cmdcfgs.emplace_back(SHRPX_OPT_STREAM_WRITE_TIMEOUT, StringRef{optarg}); + break; + case 62: + // --no-location-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_LOCATION_REWRITE, + StringRef::from_lit("yes")); + break; + case 63: + // --backend-http1-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 64: + // --listener-disable-timeout + cmdcfgs.emplace_back(SHRPX_OPT_LISTENER_DISABLE_TIMEOUT, + StringRef{optarg}); + break; + case 65: + // --strip-incoming-x-forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_X_FORWARDED_FOR, + StringRef::from_lit("yes")); + break; + case 66: + // --accesslog-format + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_FORMAT, StringRef{optarg}); + break; + case 67: + // --backend-http1-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 68: + // --tls-ticket-key-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_FILE, StringRef{optarg}); + break; + case 69: + // --rlimit-nofile + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_NOFILE, StringRef{optarg}); + break; + case 71: + // --backend-response-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_RESPONSE_BUFFER, + StringRef{optarg}); + break; + case 72: + // --backend-request-buffer + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_REQUEST_BUFFER, + StringRef{optarg}); + break; + case 73: + // --no-host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 74: + // --no-server-push + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_PUSH, + StringRef::from_lit("yes")); + break; + case 76: + // --backend-http2-connections-per-worker + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTIONS_PER_WORKER, + StringRef{optarg}); + break; + case 77: + // --fetch-ocsp-response-file + cmdcfgs.emplace_back(SHRPX_OPT_FETCH_OCSP_RESPONSE_FILE, + StringRef{optarg}); + break; + case 78: + // --ocsp-update-interval + cmdcfgs.emplace_back(SHRPX_OPT_OCSP_UPDATE_INTERVAL, StringRef{optarg}); + break; + case 79: + // --no-ocsp + cmdcfgs.emplace_back(SHRPX_OPT_NO_OCSP, StringRef::from_lit("yes")); + break; + case 80: + // --header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_HEADER_FIELD_BUFFER, StringRef{optarg}); + break; + case 81: + // --max-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_HEADER_FIELDS, StringRef{optarg}); + break; + case 82: + // --add-request-header + cmdcfgs.emplace_back(SHRPX_OPT_ADD_REQUEST_HEADER, StringRef{optarg}); + break; + case 83: + // --include + cmdcfgs.emplace_back(SHRPX_OPT_INCLUDE, StringRef{optarg}); + break; + case 84: + // --tls-ticket-key-cipher + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_CIPHER, + StringRef{optarg}); + break; + case 85: + // --host-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_HOST_REWRITE, + StringRef::from_lit("yes")); + break; + case 86: + // --tls-session-cache-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED, + StringRef{optarg}); + break; + case 87: + // --tls-ticket-key-memcached + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED, + StringRef{optarg}); + break; + case 88: + // --tls-ticket-key-memcached-interval + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_INTERVAL, + StringRef{optarg}); + break; + case 89: + // --tls-ticket-key-memcached-max-retry + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY, + StringRef{optarg}); + break; + case 90: + // --tls-ticket-key-memcached-max-fail + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL, + StringRef{optarg}); + break; + case 91: + // --mruby-file + cmdcfgs.emplace_back(SHRPX_OPT_MRUBY_FILE, StringRef{optarg}); + break; + case 93: + // --accept-proxy-protocol + cmdcfgs.emplace_back(SHRPX_OPT_ACCEPT_PROXY_PROTOCOL, + StringRef::from_lit("yes")); + break; + case 94: + // --fastopen + cmdcfgs.emplace_back(SHRPX_OPT_FASTOPEN, StringRef{optarg}); + break; + case 95: + // --tls-dyn-rec-warmup-threshold + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_WARMUP_THRESHOLD, + StringRef{optarg}); + break; + case 96: + // --tls-dyn-rec-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_TLS_DYN_REC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 97: + // --add-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_ADD_FORWARDED, StringRef{optarg}); + break; + case 98: + // --strip-incoming-forwarded + cmdcfgs.emplace_back(SHRPX_OPT_STRIP_INCOMING_FORWARDED, + StringRef::from_lit("yes")); + break; + case 99: + // --forwarded-by + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_BY, StringRef{optarg}); + break; + case 100: + // --forwarded-for + cmdcfgs.emplace_back(SHRPX_OPT_FORWARDED_FOR, StringRef{optarg}); + break; + case 101: + // --response-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_RESPONSE_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 102: + // --max-response-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_RESPONSE_HEADER_FIELDS, + StringRef{optarg}); + break; + case 103: + // --no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 104: + // --request-header-field-buffer + cmdcfgs.emplace_back(SHRPX_OPT_REQUEST_HEADER_FIELD_BUFFER, + StringRef{optarg}); + break; + case 105: + // --max-request-header-fields + cmdcfgs.emplace_back(SHRPX_OPT_MAX_REQUEST_HEADER_FIELDS, + StringRef{optarg}); + break; + case 106: + // --backend-http1-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP1_TLS, + StringRef::from_lit("yes")); + break; + case 108: + // --tls-session-cache-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 109: + // --tls-session-cache-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 110: + // --tls-session-cache-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 111: + // --tls-ticket-key-memcached-tls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_TLS, + StringRef::from_lit("yes")); + break; + case 112: + // --tls-ticket-key-memcached-cert-file + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_CERT_FILE, + StringRef{optarg}); + break; + case 113: + // --tls-ticket-key-memcached-private-key-file + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE, + StringRef{optarg}); + break; + case 114: + // --tls-ticket-key-memcached-address-family + cmdcfgs.emplace_back(SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 115: + // --tls-session-cache-memcached-address-family + cmdcfgs.emplace_back( + SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 116: + // --backend-address-family + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_ADDRESS_FAMILY, + StringRef{optarg}); + break; + case 117: + // --frontend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 118: + // --backend-http2-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 119: + // --backend-connections-per-frontend + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND, + StringRef{optarg}); + break; + case 120: + // --backend-tls + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_TLS, StringRef::from_lit("yes")); + break; + case 121: + // --backend-connections-per-host + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECTIONS_PER_HOST, + StringRef{optarg}); + break; + case 122: + // --error-page + cmdcfgs.emplace_back(SHRPX_OPT_ERROR_PAGE, StringRef{optarg}); + break; + case 123: + // --no-kqueue + cmdcfgs.emplace_back(SHRPX_OPT_NO_KQUEUE, StringRef::from_lit("yes")); + break; + case 124: + // --frontend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 125: + // --backend-http2-settings-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_SETTINGS_TIMEOUT, + StringRef{optarg}); + break; + case 126: + // --api-max-request-body + cmdcfgs.emplace_back(SHRPX_OPT_API_MAX_REQUEST_BODY, StringRef{optarg}); + break; + case 127: + // --backend-max-backoff + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_MAX_BACKOFF, StringRef{optarg}); + break; + case 128: + // --server-name + cmdcfgs.emplace_back(SHRPX_OPT_SERVER_NAME, StringRef{optarg}); + break; + case 129: + // --no-server-rewrite + cmdcfgs.emplace_back(SHRPX_OPT_NO_SERVER_REWRITE, + StringRef::from_lit("yes")); + break; + case 130: + // --frontend-http2-optimize-write-buffer-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE, + StringRef::from_lit("yes")); + break; + case 131: + // --frontend-http2-optimize-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE, + StringRef::from_lit("yes")); + break; + case 132: + // --frontend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 133: + // --frontend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 134: + // --backend-http2-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE, + StringRef{optarg}); + break; + case 135: + // --backend-http2-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 136: + // --frontend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 137: + // --frontend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 138: + // --backend-http2-encoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 139: + // --backend-http2-decoder-dynamic-table-size + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE, + StringRef{optarg}); + break; + case 140: + // --ecdh-curves + cmdcfgs.emplace_back(SHRPX_OPT_ECDH_CURVES, StringRef{optarg}); + break; + case 141: + // --tls-sct-dir + cmdcfgs.emplace_back(SHRPX_OPT_TLS_SCT_DIR, StringRef{optarg}); + break; + case 142: + // --backend-connect-timeout + cmdcfgs.emplace_back(SHRPX_OPT_BACKEND_CONNECT_TIMEOUT, + StringRef{optarg}); + break; + case 143: + // --dns-cache-timeout + cmdcfgs.emplace_back(SHRPX_OPT_DNS_CACHE_TIMEOUT, StringRef{optarg}); + break; + case 144: + // --dns-lookup-timeou + cmdcfgs.emplace_back(SHRPX_OPT_DNS_LOOKUP_TIMEOUT, StringRef{optarg}); + break; + case 145: + // --dns-max-try + cmdcfgs.emplace_back(SHRPX_OPT_DNS_MAX_TRY, StringRef{optarg}); + break; + case 146: + // --frontend-keep-alive-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_KEEP_ALIVE_TIMEOUT, + StringRef{optarg}); + break; + case 147: + // --psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_PSK_SECRETS, StringRef{optarg}); + break; + case 148: + // --client-psk-secrets + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_PSK_SECRETS, StringRef{optarg}); + break; + case 149: + // --client-no-http2-cipher-black-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST, + StringRef::from_lit("yes")); + break; + case 150: + // --client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 151: + // --accesslog-write-early + cmdcfgs.emplace_back(SHRPX_OPT_ACCESSLOG_WRITE_EARLY, + StringRef::from_lit("yes")); + break; + case 152: + // --tls-min-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MIN_PROTO_VERSION, + StringRef{optarg}); + break; + case 153: + // --tls-max-proto-version + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_PROTO_VERSION, + StringRef{optarg}); + break; + case 154: + // --redirect-https-port + cmdcfgs.emplace_back(SHRPX_OPT_REDIRECT_HTTPS_PORT, StringRef{optarg}); + break; + case 155: + // --frontend-max-requests + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_MAX_REQUESTS, + StringRef{optarg}); + break; + case 156: + // --single-thread + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_THREAD, + StringRef::from_lit("yes")); + break; + case 157: + // --no-add-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_ADD_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 158: + // --no-strip-incoming-x-forwarded-proto + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_X_FORWARDED_PROTO, + StringRef::from_lit("yes")); + break; + case 159: + // --single-process + cmdcfgs.emplace_back(SHRPX_OPT_SINGLE_PROCESS, + StringRef::from_lit("yes")); + break; + case 160: + // --verify-client-tolerate-expired + cmdcfgs.emplace_back(SHRPX_OPT_VERIFY_CLIENT_TOLERATE_EXPIRED, + StringRef::from_lit("yes")); + break; + case 161: + // --ignore-per-pattern-mruby-error + cmdcfgs.emplace_back(SHRPX_OPT_IGNORE_PER_PATTERN_MRUBY_ERROR, + StringRef::from_lit("yes")); + break; + case 162: + // --tls-no-postpone-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_NO_POSTPONE_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 163: + // --tls-max-early-data + cmdcfgs.emplace_back(SHRPX_OPT_TLS_MAX_EARLY_DATA, StringRef{optarg}); + break; + case 164: + // --tls13-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CIPHERS, StringRef{optarg}); + break; + case 165: + // --tls13-client-ciphers + cmdcfgs.emplace_back(SHRPX_OPT_TLS13_CLIENT_CIPHERS, StringRef{optarg}); + break; + case 166: + // --no-strip-incoming-early-data + cmdcfgs.emplace_back(SHRPX_OPT_NO_STRIP_INCOMING_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 167: + // --no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 168: + // --client-no-http2-cipher-block-list + cmdcfgs.emplace_back(SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST, + StringRef::from_lit("yes")); + break; + case 169: + // --quic-bpf-program-file + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_BPF_PROGRAM_FILE, + StringRef{optarg}); + break; + case 170: + // --no-quic-bpf + cmdcfgs.emplace_back(SHRPX_OPT_NO_QUIC_BPF, StringRef::from_lit("yes")); + break; + case 171: + // --http2-altsvc + cmdcfgs.emplace_back(SHRPX_OPT_HTTP2_ALTSVC, StringRef{optarg}); + break; + case 172: + // --frontend-http3-read-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_READ_TIMEOUT, + StringRef{optarg}); + break; + case 173: + // --frontend-quic-idle-timeout + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_IDLE_TIMEOUT, + StringRef{optarg}); + break; + case 174: + // --frontend-quic-debug-log + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_DEBUG_LOG, + StringRef::from_lit("yes")); + break; + case 175: + // --frontend-http3-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_WINDOW_SIZE, + StringRef{optarg}); + break; + case 176: + // --frontend-http3-connection-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 177: + // --frontend-http3-max-window-size + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_WINDOW_SIZE, + StringRef{optarg}); + break; + case 178: + // --frontend-http3-max-connection-window-size + cmdcfgs.emplace_back( + SHRPX_OPT_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE, + StringRef{optarg}); + break; + case 179: + // --frontend-http3-max-concurrent-streams + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS, + StringRef{optarg}); + break; + case 180: + // --frontend-quic-early-data + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA, + StringRef::from_lit("yes")); + break; + case 181: + // --frontend-quic-qlog-dir + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR, + StringRef{optarg}); + break; + case 182: + // --frontend-quic-require-token + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_REQUIRE_TOKEN, + StringRef::from_lit("yes")); + break; + case 183: + // --frontend-quic-congestion-controller + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_CONGESTION_CONTROLLER, + StringRef{optarg}); + break; + case 185: + // --quic-server-id + cmdcfgs.emplace_back(SHRPX_OPT_QUIC_SERVER_ID, StringRef{optarg}); + break; + case 186: + // --frontend-quic-secret-file + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_SECRET_FILE, + StringRef{optarg}); + break; + case 187: + // --rlimit-memlock + cmdcfgs.emplace_back(SHRPX_OPT_RLIMIT_MEMLOCK, StringRef{optarg}); + break; + case 188: + // --max-worker-processes + cmdcfgs.emplace_back(SHRPX_OPT_MAX_WORKER_PROCESSES, StringRef{optarg}); + break; + case 189: + // --worker-process-grace-shutdown-period + cmdcfgs.emplace_back(SHRPX_OPT_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD, + StringRef{optarg}); + break; + case 190: + // --frontend-quic-initial-rtt + cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_INITIAL_RTT, + StringRef{optarg}); + break; + case 191: + // --require-http-scheme + cmdcfgs.emplace_back(SHRPX_OPT_REQUIRE_HTTP_SCHEME, + StringRef::from_lit("yes")); + break; + case 192: + // --tls-ktls + cmdcfgs.emplace_back(SHRPX_OPT_TLS_KTLS, StringRef::from_lit("yes")); + break; + case 193: + // --alpn-list + cmdcfgs.emplace_back(SHRPX_OPT_ALPN_LIST, StringRef{optarg}); + break; + default: + break; + } + break; + default: + break; + } + } + + if (argc - optind >= 2) { + cmdcfgs.emplace_back(SHRPX_OPT_PRIVATE_KEY_FILE, StringRef{argv[optind++]}); + cmdcfgs.emplace_back(SHRPX_OPT_CERTIFICATE_FILE, StringRef{argv[optind++]}); + } + + rv = process_options(mod_config(), cmdcfgs); + if (rv != 0) { + return -1; + } + + if (event_loop() != 0) { + return -1; + } + + LOG(NOTICE) << "Shutdown momentarily"; + + delete_log_config(); + + return 0; +} + +} // namespace shrpx + +int main(int argc, char **argv) { return run_app(shrpx::main, argc, argv); } |