Imported Upstream version 1.46.0
[platform/upstream/nghttp2.git] / src / shrpx.cc
index 4ff5e47..14427f9 100644 (file)
@@ -71,6 +71,7 @@
 
 #include <openssl/ssl.h>
 #include <openssl/err.h>
+#include <openssl/rand.h>
 #include <ev.h>
 
 #include <nghttp2/nghttp2.h>
@@ -86,6 +87,7 @@
 #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"
@@ -124,12 +126,21 @@ constexpr auto ENV_UNIX_PATH = StringRef::from_lit("NGHTTP2_UNIX_PATH");
 // 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 master
-// process, assuming that it created this master process as a result
-// of SIGUSR2.  The new master process is expected to send QUIT signal
-// to the original master process to shut it down gracefully.
+// 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
@@ -181,8 +192,24 @@ 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)
-      : loop(loop), worker_pid(worker_pid), ipc_fd(ipc_fd) {
+  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),
+        termination_deadline(0.)
+#ifdef ENABLE_HTTP3
+        ,
+        quic_ipc_fd(quic_ipc_fd),
+        cid_prefixes(cid_prefixes)
+#endif // ENABLE_HTTP3
+  {
     ev_signal_init(&reopen_log_signalev, signal_cb, REOPEN_LOG_SIGNAL);
     reopen_log_signalev.data = this;
     ev_signal_start(loop, &reopen_log_signalev);
@@ -211,6 +238,12 @@ struct 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);
@@ -232,6 +265,11 @@ struct WorkerProcess {
   struct ev_loop *loop;
   pid_t worker_pid;
   int ipc_fd;
+  ev_tstamp 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 {
@@ -243,13 +281,81 @@ 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 = ev_now(loop);
+  ev_tstamp next_repeat = 0.;
+
+  for (auto it = std::begin(worker_processes);
+       it != std::end(worker_processes);) {
+    auto &wp = *it;
+    if (!(wp->termination_deadline > 0.)) {
+      ++it;
+
+      continue;
+    }
+
+    auto d = wp->termination_deadline - now;
+    if (d > 0) {
+      if (!(next_repeat > 0.) || 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 > 0.) {
+    w->repeat = 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 =
+      ev_now(loop) + 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) {
+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;
@@ -259,28 +365,46 @@ void worker_process_remove(const WorkerProcess *wp) {
     }
 
     worker_processes.erase(it);
+
+    if (worker_processes.empty()) {
+      ev_timer_stop(loop, &worker_process_grace_period_timer);
+    }
+
     break;
   }
 }
 } // namespace
 
 namespace {
-void worker_process_remove_all() {
+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) {
+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();
+  worker_process_remove_all(loop);
 }
 } // namespace
 
@@ -419,7 +543,7 @@ void exec_binary() {
 
   // child process
 
-  shrpx_signal_unset_master_proc_ign_handler();
+  shrpx_signal_unset_main_proc_ign_handler();
 
   rv = shrpx_signal_unblock_all();
   if (rv != 0) {
@@ -454,8 +578,8 @@ void exec_binary() {
   auto &listenerconf = config->conn.listener;
 
   // 2 for ENV_ORIG_PID and terminal nullptr.
-  auto envp =
-      std::make_unique<char *[]>(envlen + listenerconf.addrs.size() + 2);
+  auto envp = std::make_unique<char *[]>(envlen + listenerconf.addrs.size() +
+                                         worker_processes.size() + 2);
   size_t envidx = 0;
 
   std::vector<ImmutableString> fd_envs;
@@ -483,6 +607,24 @@ void exec_binary() {
   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) ||
@@ -491,7 +633,8 @@ void exec_binary() {
         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_ORIG_PID) ||
+        util::starts_with(env, ENV_QUIC_WORKER_PROCESS_PREFIX)) {
       continue;
     }
 
@@ -548,7 +691,7 @@ void ipc_send(WorkerProcess *wp, uint8_t ipc_event) {
 
 namespace {
 void reopen_log(WorkerProcess *wp) {
-  LOG(NOTICE) << "Reopening log files: master process";
+  LOG(NOTICE) << "Reopening log files: main process";
 
   auto config = get_config();
   auto &loggingconf = config->logging;
@@ -580,13 +723,14 @@ void signal_cb(struct ev_loop *loop, ev_signal *w, int revents) {
       close(addr.fd);
     }
     ipc_send(wp, SHRPX_IPC_GRACEFUL_SHUTDOWN);
+    worker_process_set_termination_deadline(wp, loop);
     return;
   }
   case RELOAD_SIGNAL:
     reload_config(wp);
     return;
   default:
-    worker_process_kill(w->signum);
+    worker_process_kill(w->signum, loop);
     ev_break(loop);
     return;
   }
@@ -601,7 +745,7 @@ void worker_process_child_cb(struct ev_loop *loop, ev_child *w, int revents) {
 
   auto pid = wp->worker_pid;
 
-  worker_process_remove(wp);
+  worker_process_remove(wp, loop);
 
   if (worker_process_last_pid() == pid) {
     ev_break(loop);
@@ -1092,7 +1236,7 @@ void close_unused_inherited_addr(const std::vector<InheritedAddr> &iaddrs) {
 } // namespace
 
 namespace {
-// Returns the PID of the original master process from environment
+// 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());
@@ -1103,6 +1247,86 @@ pid_t get_orig_pid_from_env() {
 }
 } // 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;
@@ -1180,14 +1404,98 @@ int create_ipc_socket(std::array<int, 2> &ipc_fd) {
 }
 } // 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 {
 // 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,
-                          const std::vector<InheritedAddr> &iaddrs) {
+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;
@@ -1199,6 +1507,15 @@ pid_t fork_worker_process(int &main_ipc_fd,
     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;
@@ -1226,9 +1543,23 @@ pid_t fork_worker_process(int &main_ipc_fd,
       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
+
     // Remove all WorkerProcesses to stop any registered watcher on
     // default loop.
-    worker_process_remove_all();
+    worker_process_remove_all(EV_DEFAULT);
 
     close_unused_inherited_addr(iaddrs);
 
@@ -1249,9 +1580,19 @@ pid_t fork_worker_process(int &main_ipc_fd,
 
     if (!config->single_process) {
       close(ipc_fd[1]);
+#ifdef ENABLE_HTTP3
+      close(quic_ipc_fd[1]);
+#endif // ENABLE_HTTP3
     }
 
-    WorkerProcessConfig wpconf{ipc_fd[0]};
+    WorkerProcessConfig wpconf{
+        .ipc_fd = ipc_fd[0],
+#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";
@@ -1292,13 +1633,23 @@ pid_t fork_worker_process(int &main_ipc_fd,
   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";
 
@@ -1310,7 +1661,7 @@ namespace {
 int event_loop() {
   std::array<char, STRERROR_BUFSIZE> errbuf;
 
-  shrpx_signal_set_master_proc_ign_handler();
+  shrpx_signal_set_main_proc_ign_handler();
 
   auto config = mod_config();
 
@@ -1345,17 +1696,53 @@ int event_loop() {
 
   auto 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();
 
-  auto pid = fork_worker_process(ipc_fd, {});
+  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
+
+  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;
   }
 
-  worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd));
+  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
@@ -1369,13 +1756,15 @@ int event_loop() {
   shrpx_sd_notifyf(0, "READY=1");
 
   if (orig_pid != -1) {
-    LOG(NOTICE) << "Send QUIT signal to the original master process to tell "
+    LOG(NOTICE) << "Send QUIT signal to the original main process to tell "
                    "that we are ready to serve requests.";
     kill(orig_pid, SIGQUIT);
   }
 
   ev_run(loop, 0);
 
+  ev_timer_stop(loop, &worker_process_grace_period_timer);
+
   return 0;
 }
 } // namespace
@@ -1548,6 +1937,42 @@ void fill_default_config(Config *config) {
         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;
@@ -1578,6 +2003,9 @@ void fill_default_config(Config *config) {
       // 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;
 
@@ -1632,7 +2060,7 @@ void print_version(std::ostream &out) {
 namespace {
 void print_usage(std::ostream &out) {
   out << R"(Usage: nghttpx [OPTIONS]... [<PRIVATE_KEY> <CERT>]
-A reverse proxy for HTTP/2, and HTTP/1.)"
+A reverse proxy for HTTP/3, HTTP/2, and HTTP/1.)"
       << std::endl;
 }
 } // namespace
@@ -1740,12 +2168,13 @@ Connections:
               "affinity=<METHOD>",    "dns",    "redirect-if-not-tls",
               "upgrade-scheme",                        "mruby=<PATH>",
               "read-timeout=<DURATION>",   "write-timeout=<DURATION>",
-              "group=<GROUP>",  "group-weight=<N>", and  "weight=<N>".
-              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.
+              "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
@@ -1876,9 +2305,19 @@ Connections:
               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.  Since  ";" has  special
-              meaning in shell, the option value must be quoted.
+              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"(
@@ -1918,6 +2357,12 @@ Connections:
               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.
@@ -2026,6 +2471,12 @@ Performance:
               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: )"
@@ -2050,6 +2501,10 @@ Timeout:
               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: )"
@@ -2385,16 +2840,16 @@ SSL/TLS:
               TLS HTTP/2 backends.
               Default: )"
       << util::duration_str(config->tls.dyn_rec.idle_timeout) << R"(
-  --no-http2-cipher-black-list
-              Allow  black  listed  cipher suite  on  frontend  HTTP/2
+  --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 black list.
-  --client-no-http2-cipher-black-list
-              Allow  black  listed  cipher  suite  on  backend  HTTP/2
+              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 black list.
+              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
@@ -2413,9 +2868,9 @@ SSL/TLS:
               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  black listed  by
+              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-black-list  option.
+              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
@@ -2427,17 +2882,18 @@ SSL/TLS:
               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  black listed  by
+              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-black-list
+              consider   to  use   --client-no-http2-cipher-block-list
               option.  But be aware its implications.
   --tls-no-postpone-early-data
-              By default,  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.
+              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.
@@ -2607,6 +3063,14 @@ Logging:
                 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}).
@@ -2693,13 +3157,18 @@ HTTP:
               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]]>
+  --altsvc=<PROTOID,PORT[,HOST,[ORIGIN[,PARAMS]]]>
               Specify   protocol  ID,   port,  host   and  origin   of
-              alternative service.  <HOST>  and <ORIGIN> are optional.
-              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
+              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.  This  option just  appends header field  and won't
@@ -2822,11 +3291,32 @@ Process:
   --single-process
               Run this program in a  single process mode for debugging
               purpose.  Without this option,  nghttpx creates at least
-              2  processes:  master  and worker  processes.   If  this
-              option is  used, master  and worker  are unified  into a
-              single process.  nghttpx still spawns additional process
-              if neverbleed is used.  In  the single process mode, the
+              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>
@@ -2835,7 +3325,124 @@ Scripting:
               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
+              ".qlog".
+  --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
@@ -3016,8 +3623,10 @@ int process_options(Config *config,
     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));
   }
 
@@ -3048,9 +3657,12 @@ int process_options(Config *config,
     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(StringRef{proxy.host}, proxy.port);
+    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;
@@ -3063,7 +3675,8 @@ int process_options(Config *config,
   {
     auto &memcachedconf = tlsconf.session_cache.memcached;
     if (!memcachedconf.host.empty()) {
-      auto hostport = util::make_hostport(StringRef{memcachedconf.host},
+      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) {
@@ -3084,7 +3697,8 @@ int process_options(Config *config,
   {
     auto &memcachedconf = tlsconf.ticket.memcached;
     if (!memcachedconf.host.empty()) {
-      auto hostport = util::make_hostport(StringRef{memcachedconf.host},
+      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) {
@@ -3111,6 +3725,16 @@ int process_options(Config *config,
     }
   }
 
+  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());
+    }
+  }
+
   auto &fwdconf = config->http.forwarded;
 
   if (fwdconf.by_node_type == ForwardedNode::OBFUSCATED &&
@@ -3137,6 +3761,16 @@ int process_options(Config *config,
   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
@@ -3199,13 +3833,37 @@ void reload_config(WorkerProcess *wp) {
   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, iaddrs);
+  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";
@@ -3221,10 +3879,18 @@ void reload_config(WorkerProcess *wp) {
   // Send last worker process a graceful shutdown notice
   auto &last_wp = worker_processes.back();
   ipc_send(last_wp.get(), SHRPX_IPC_GRACEFUL_SHUTDOWN);
+  worker_process_set_termination_deadline(last_wp.get(), loop);
   // We no longer use signals for this worker.
   last_wp->shutdown_signal_watchers();
 
-  worker_process_add(std::make_unique<WorkerProcess>(loop, pid, ipc_fd));
+  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();
@@ -3520,6 +4186,44 @@ int main(int argc, char **argv) {
         {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},
         {nullptr, 0, nullptr, 0}};
 
     int option_index = 0;
@@ -4313,6 +5017,117 @@ int main(int argc, char **argv) {
         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;
       default:
         break;
       }