Imported Upstream version 1.46.0
[platform/upstream/nghttp2.git] / src / shrpx_config.cc
index 769ad9b..b8acbe5 100644 (file)
@@ -230,10 +230,81 @@ read_tls_ticket_key_file(const std::vector<StringRef> &files,
   return ticket_keys;
 }
 
+#ifdef ENABLE_HTTP3
+std::shared_ptr<QUICKeyingMaterials>
+read_quic_secret_file(const StringRef &path) {
+  constexpr size_t expectedlen =
+      SHRPX_QUIC_SECRET_RESERVEDLEN + SHRPX_QUIC_SECRETLEN + SHRPX_QUIC_SALTLEN;
+
+  auto qkms = std::make_shared<QUICKeyingMaterials>();
+  auto &kms = qkms->keying_materials;
+
+  std::ifstream f(path.c_str());
+  if (!f) {
+    LOG(ERROR) << "frontend-quic-secret-file: could not open file " << path;
+    return nullptr;
+  }
+
+  std::array<char, 4096> buf;
+
+  while (f.getline(buf.data(), buf.size())) {
+    auto len = strlen(buf.data());
+    if (len == 0 || buf[0] == '#') {
+      continue;
+    }
+
+    auto s = StringRef{std::begin(buf), std::begin(buf) + len};
+    if (s.size() != expectedlen * 2 || !util::is_hex_string(s)) {
+      LOG(ERROR) << "frontend-quic-secret-file: each line must be a "
+                 << expectedlen * 2 << " bytes hex encoded string";
+      return nullptr;
+    }
+
+    kms.emplace_back();
+    auto &qkm = kms.back();
+
+    auto p = std::begin(s);
+
+    util::decode_hex(std::begin(qkm.reserved),
+                     StringRef{p, p + qkm.reserved.size()});
+    p += qkm.reserved.size() * 2;
+    util::decode_hex(std::begin(qkm.secret),
+                     StringRef{p, p + qkm.secret.size()});
+    p += qkm.secret.size() * 2;
+    util::decode_hex(std::begin(qkm.salt), StringRef{p, p + qkm.salt.size()});
+    p += qkm.salt.size() * 2;
+
+    assert(static_cast<size_t>(p - std::begin(s)) == expectedlen * 2);
+
+    qkm.id = qkm.reserved[0] & 0xc0;
+
+    if (kms.size() == 4) {
+      break;
+    }
+  }
+
+  if (f.bad() || (!f.eof() && f.fail())) {
+    LOG(ERROR)
+        << "frontend-quic-secret-file: error occurred while reading file "
+        << path;
+    return nullptr;
+  }
+
+  if (kms.empty()) {
+    LOG(WARN)
+        << "frontend-quic-secret-file: no keying materials are present in file "
+        << path;
+    return nullptr;
+  }
+
+  return qkms;
+}
+#endif // ENABLE_HTTP3
+
 FILE *open_file_for_write(const char *filename) {
   std::array<char, STRERROR_BUFSIZE> errbuf;
 
-#if defined O_CLOEXEC
+#ifdef O_CLOEXEC
   auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC,
                  S_IRUSR | S_IWUSR);
 #else
@@ -373,6 +444,57 @@ int parse_int(T *dest, const StringRef &opt, const char *optarg) {
 }
 
 namespace {
+int parse_altsvc(AltSvc &altsvc, const StringRef &opt,
+                 const StringRef &optarg) {
+  // PROTOID, PORT, HOST, ORIGIN, PARAMS.
+  auto tokens = util::split_str(optarg, ',', 5);
+
+  if (tokens.size() < 2) {
+    // Requires at least protocol_id and port
+    LOG(ERROR) << opt << ": too few parameters: " << optarg;
+    return -1;
+  }
+
+  int port;
+
+  if (parse_uint(&port, opt, tokens[1]) != 0) {
+    return -1;
+  }
+
+  if (port < 1 ||
+      port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
+    LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
+    return -1;
+  }
+
+  altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
+
+  altsvc.port = port;
+  altsvc.service = make_string_ref(config->balloc, tokens[1]);
+
+  if (tokens.size() > 2) {
+    if (!tokens[2].empty()) {
+      altsvc.host = make_string_ref(config->balloc, tokens[2]);
+    }
+
+    if (tokens.size() > 3) {
+      if (!tokens[3].empty()) {
+        altsvc.origin = make_string_ref(config->balloc, tokens[3]);
+      }
+
+      if (tokens.size() > 4) {
+        if (!tokens[4].empty()) {
+          altsvc.params = make_string_ref(config->balloc, tokens[4]);
+        }
+      }
+    }
+  }
+
+  return 0;
+}
+} // namespace
+
+namespace {
 // generated by gennghttpxfun.py
 LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
   switch (namelen) {
@@ -387,6 +509,11 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
     break;
   case 4:
     switch (name[3]) {
+    case 'h':
+      if (util::strieq_l("pat", name, 3)) {
+        return LogFragmentType::PATH;
+      }
+      break;
     case 'n':
       if (util::strieq_l("alp", name, 3)) {
         return LogFragmentType::ALPN;
@@ -396,6 +523,11 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
     break;
   case 6:
     switch (name[5]) {
+    case 'd':
+      if (util::strieq_l("metho", name, 5)) {
+        return LogFragmentType::METHOD;
+      }
+      break;
     case 's':
       if (util::strieq_l("statu", name, 5)) {
         return LogFragmentType::STATUS;
@@ -502,6 +634,15 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
       break;
     }
     break;
+  case 16:
+    switch (name[15]) {
+    case 'n':
+      if (util::strieq_l("protocol_versio", name, 15)) {
+        return LogFragmentType::PROTOCOL_VERSION;
+      }
+      break;
+    }
+    break;
   case 17:
     switch (name[16]) {
     case 'l':
@@ -521,6 +662,11 @@ LogFragmentType log_var_lookup_token(const char *name, size_t namelen) {
         return LogFragmentType::TLS_SESSION_REUSED;
       }
       break;
+    case 'y':
+      if (util::strieq_l("path_without_quer", name, 17)) {
+        return LogFragmentType::PATH_WITHOUT_QUERY;
+      }
+      break;
     }
     break;
   case 22:
@@ -761,6 +907,7 @@ struct UpstreamParams {
   bool tls;
   bool sni_fwd;
   bool proxyproto;
+  bool quic;
 };
 
 namespace {
@@ -795,6 +942,13 @@ int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) {
       out.alt_mode = UpstreamAltMode::HEALTHMON;
     } else if (util::strieq_l("proxyproto", param)) {
       out.proxyproto = true;
+    } else if (util::strieq_l("quic", param)) {
+#ifdef ENABLE_HTTP3
+      out.quic = true;
+#else  // !ENABLE_HTTP3
+      LOG(ERROR) << "quic: QUIC is disabled at compile time";
+      return -1;
+#endif // !ENABLE_HTTP3
     } else if (!param.empty()) {
       LOG(ERROR) << "frontend: " << param << ": unknown keyword";
       return -1;
@@ -827,6 +981,7 @@ struct DownstreamParams {
   bool dns;
   bool redirect_if_not_tls;
   bool upgrade_scheme;
+  bool dnf;
 };
 
 namespace {
@@ -1001,6 +1156,8 @@ int parse_downstream_params(DownstreamParams &out,
         return -1;
       }
       out.group_weight = n;
+    } else if (util::strieq_l("dnf", param)) {
+      out.dnf = true;
     } else if (!param.empty()) {
       LOG(ERROR) << "backend: " << param << ": unknown keyword";
       return -1;
@@ -1065,6 +1222,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
   addr.sni = make_string_ref(downstreamconf.balloc, params.sni);
   addr.dns = params.dns;
   addr.upgrade_scheme = params.upgrade_scheme;
+  addr.dnf = params.dnf;
 
   auto &routerconf = downstreamconf.router;
   auto &router = routerconf.router;
@@ -1085,9 +1243,9 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
       *p = '\0';
       pattern = StringRef{iov.base, p};
     } else {
-      auto path = http2::normalize_path(downstreamconf.balloc,
-                                        StringRef{slash, std::end(raw_pattern)},
-                                        StringRef{});
+      auto path = http2::normalize_path_colon(
+          downstreamconf.balloc, StringRef{slash, std::end(raw_pattern)},
+          StringRef{});
       auto iov = make_byte_ref(downstreamconf.balloc,
                                std::distance(std::begin(raw_pattern), slash) +
                                    path.size() + 1);
@@ -1165,6 +1323,14 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
           return -1;
         }
       }
+      // All backends in the same group must have the same dnf
+      // setting.  If some backends do not specify dnf, and there is
+      // at least one backend with dnf, it is used for all backends in
+      // the group.  In general, multiple backends are not necessary
+      // for dnf because there is no need for load balancing.
+      if (params.dnf) {
+        g.dnf = true;
+      }
 
       g.addrs.push_back(addr);
       continue;
@@ -1189,6 +1355,7 @@ int parse_mapping(Config *config, DownstreamAddrConfig &addr,
     g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby);
     g.timeout.read = params.read_timeout;
     g.timeout.write = params.write_timeout;
+    g.dnf = params.dnf;
 
     if (pattern[0] == '*') {
       // wildcard pattern
@@ -1771,6 +1938,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_SERVER_NAME;
       }
       break;
+    case 'f':
+      if (util::strieq_l("no-quic-bp", name, 10)) {
+        return SHRPX_OPTID_NO_QUIC_BPF;
+      }
+      break;
     case 'r':
       if (util::strieq_l("tls-sct-di", name, 10)) {
         return SHRPX_OPTID_TLS_SCT_DIR;
@@ -1814,6 +1986,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_BACKEND_IPV6;
       }
       break;
+    case 'c':
+      if (util::strieq_l("http2-altsv", name, 11)) {
+        return SHRPX_OPTID_HTTP2_ALTSVC;
+      }
+      break;
     case 'e':
       if (util::strieq_l("host-rewrit", name, 11)) {
         return SHRPX_OPTID_HOST_REWRITE;
@@ -1877,6 +2054,11 @@ int option_lookup_token(const char *name, size_t namelen) {
     break;
   case 14:
     switch (name[13]) {
+    case 'd':
+      if (util::strieq_l("quic-server-i", name, 13)) {
+        return SHRPX_OPTID_QUIC_SERVER_ID;
+      }
+      break;
     case 'e':
       if (util::strieq_l("accesslog-fil", name, 13)) {
         return SHRPX_OPTID_ACCESSLOG_FILE;
@@ -1887,6 +2069,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_NO_SERVER_PUSH;
       }
       break;
+    case 'k':
+      if (util::strieq_l("rlimit-memloc", name, 13)) {
+        return SHRPX_OPTID_RLIMIT_MEMLOCK;
+      }
+      break;
     case 'p':
       if (util::strieq_l("no-verify-ocs", name, 13)) {
         return SHRPX_OPTID_NO_VERIFY_OCSP;
@@ -2066,6 +2253,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       }
       break;
     case 's':
+      if (util::strieq_l("max-worker-processe", name, 19)) {
+        return SHRPX_OPTID_MAX_WORKER_PROCESSES;
+      }
       if (util::strieq_l("tls13-client-cipher", name, 19)) {
         return SHRPX_OPTID_TLS13_CLIENT_CIPHERS;
       }
@@ -2095,6 +2285,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_BACKEND_TLS_SNI_FIELD;
       }
       break;
+    case 'e':
+      if (util::strieq_l("quic-bpf-program-fil", name, 20)) {
+        return SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE;
+      }
+      break;
     case 'l':
       if (util::strieq_l("accept-proxy-protoco", name, 20)) {
         return SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL;
@@ -2144,6 +2339,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("backend-request-buffe", name, 21)) {
         return SHRPX_OPTID_BACKEND_REQUEST_BUFFER;
       }
+      if (util::strieq_l("frontend-quic-qlog-di", name, 21)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR;
+      }
       break;
     case 't':
       if (util::strieq_l("frontend-write-timeou", name, 21)) {
@@ -2167,6 +2365,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE;
       }
       break;
+    case 'g':
+      if (util::strieq_l("frontend-quic-debug-lo", name, 22)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG;
+      }
+      break;
     case 'r':
       if (util::strieq_l("backend-response-buffe", name, 22)) {
         return SHRPX_OPTID_BACKEND_RESPONSE_BUFFER;
@@ -2181,6 +2384,11 @@ int option_lookup_token(const char *name, size_t namelen) {
     break;
   case 24:
     switch (name[23]) {
+    case 'a':
+      if (util::strieq_l("frontend-quic-early-dat", name, 23)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA;
+      }
+      break;
     case 'd':
       if (util::strieq_l("strip-incoming-forwarde", name, 23)) {
         return SHRPX_OPTID_STRIP_INCOMING_FORWARDED;
@@ -2215,6 +2423,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("backend-http2-window-siz", name, 24)) {
         return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE;
       }
+      if (util::strieq_l("frontend-quic-secret-fil", name, 24)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE;
+      }
       break;
     case 'g':
       if (util::strieq_l("http2-no-cookie-crumblin", name, 24)) {
@@ -2229,6 +2440,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS;
       }
       break;
+    case 't':
+      if (util::strieq_l("frontend-quic-initial-rt", name, 24)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT;
+      }
+      break;
     }
     break;
   case 26:
@@ -2242,6 +2458,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("frontend-http2-window-siz", name, 25)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE;
       }
+      if (util::strieq_l("frontend-http3-window-siz", name, 25)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE;
+      }
       break;
     case 's':
       if (util::strieq_l("frontend-http2-window-bit", name, 25)) {
@@ -2255,9 +2474,15 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("backend-keep-alive-timeou", name, 25)) {
         return SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT;
       }
+      if (util::strieq_l("frontend-quic-idle-timeou", name, 25)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT;
+      }
       if (util::strieq_l("no-http2-cipher-black-lis", name, 25)) {
         return SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST;
       }
+      if (util::strieq_l("no-http2-cipher-block-lis", name, 25)) {
+        return SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST;
+      }
       break;
     }
     break;
@@ -2268,6 +2493,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED;
       }
       break;
+    case 'n':
+      if (util::strieq_l("frontend-quic-require-toke", name, 26)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN;
+      }
+      break;
     case 'r':
       if (util::strieq_l("request-header-field-buffe", name, 26)) {
         return SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER;
@@ -2282,6 +2512,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("frontend-http2-read-timeou", name, 26)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT;
       }
+      if (util::strieq_l("frontend-http3-read-timeou", name, 26)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT;
+      }
       if (util::strieq_l("frontend-keep-alive-timeou", name, 26)) {
         return SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT;
       }
@@ -2327,6 +2560,11 @@ int option_lookup_token(const char *name, size_t namelen) {
         return SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED;
       }
       break;
+    case 'e':
+      if (util::strieq_l("frontend-http3-max-window-siz", name, 29)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE;
+      }
+      break;
     case 'r':
       if (util::strieq_l("ignore-per-pattern-mruby-erro", name, 29)) {
         return SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR;
@@ -2379,6 +2617,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("client-no-http2-cipher-black-lis", name, 32)) {
         return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST;
       }
+      if (util::strieq_l("client-no-http2-cipher-block-lis", name, 32)) {
+        return SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST;
+      }
       break;
     }
     break;
@@ -2422,11 +2663,19 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("frontend-http2-dump-response-heade", name, 34)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER;
       }
+      if (util::strieq_l("frontend-quic-congestion-controlle", name, 34)) {
+        return SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER;
+      }
       break;
     }
     break;
   case 36:
     switch (name[35]) {
+    case 'd':
+      if (util::strieq_l("worker-process-grace-shutdown-perio", name, 35)) {
+        return SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD;
+      }
+      break;
     case 'e':
       if (util::strieq_l("backend-http2-connection-window-siz", name, 35)) {
         return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE;
@@ -2453,6 +2702,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("frontend-http2-connection-window-siz", name, 36)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE;
       }
+      if (util::strieq_l("frontend-http3-connection-window-siz", name, 36)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE;
+      }
       if (util::strieq_l("tls-session-cache-memcached-cert-fil", name, 36)) {
         return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE;
       }
@@ -2464,6 +2716,9 @@ int option_lookup_token(const char *name, size_t namelen) {
       if (util::strieq_l("frontend-http2-max-concurrent-stream", name, 36)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS;
       }
+      if (util::strieq_l("frontend-http3-max-concurrent-stream", name, 36)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS;
+      }
       break;
     }
     break;
@@ -2512,6 +2767,10 @@ int option_lookup_token(const char *name, size_t namelen) {
                          40)) {
         return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE;
       }
+      if (util::strieq_l("frontend-http3-max-connection-window-siz", name,
+                         40)) {
+        return SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE;
+      }
       if (util::strieq_l("tls-ticket-key-memcached-private-key-fil", name,
                          40)) {
         return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE;
@@ -2594,7 +2853,6 @@ int parse_config(Config *config, int optid, const StringRef &opt,
     return 0;
   }
   case SHRPX_OPTID_FRONTEND: {
-    auto &listenerconf = config->conn.listener;
     auto &apiconf = config->api;
 
     auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';');
@@ -2612,23 +2870,49 @@ int parse_config(Config *config, int optid, const StringRef &opt,
       return -1;
     }
 
+    if (params.quic) {
+      if (params.alt_mode != UpstreamAltMode::NONE) {
+        LOG(ERROR) << "frontend: api or healthmon cannot be used with quic";
+        return -1;
+      }
+
+      if (!params.tls) {
+        LOG(ERROR) << "frontend: quic requires TLS";
+        return -1;
+      }
+    }
+
     UpstreamAddr addr{};
     addr.fd = -1;
     addr.tls = params.tls;
     addr.sni_fwd = params.sni_fwd;
     addr.alt_mode = params.alt_mode;
     addr.accept_proxy_protocol = params.proxyproto;
+    addr.quic = params.quic;
 
     if (addr.alt_mode == UpstreamAltMode::API) {
       apiconf.enabled = true;
     }
 
+#ifdef ENABLE_HTTP3
+    auto &addrs = params.quic ? config->conn.quic_listener.addrs
+                              : config->conn.listener.addrs;
+#else  // !ENABLE_HTTP3
+    auto &addrs = config->conn.listener.addrs;
+#endif // !ENABLE_HTTP3
+
     if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) {
+      if (addr.quic) {
+        LOG(ERROR) << "frontend: quic cannot be used on UNIX domain socket";
+        return -1;
+      }
+
       auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size();
       addr.host = make_string_ref(config->balloc, StringRef{path, addr_end});
       addr.host_unix = true;
+      addr.index = addrs.size();
 
-      listenerconf.addrs.push_back(std::move(addr));
+      addrs.push_back(std::move(addr));
 
       return 0;
     }
@@ -2643,21 +2927,25 @@ int parse_config(Config *config, int optid, const StringRef &opt,
 
     if (util::numeric_host(host, AF_INET)) {
       addr.family = AF_INET;
-      listenerconf.addrs.push_back(std::move(addr));
+      addr.index = addrs.size();
+      addrs.push_back(std::move(addr));
       return 0;
     }
 
     if (util::numeric_host(host, AF_INET6)) {
       addr.family = AF_INET6;
-      listenerconf.addrs.push_back(std::move(addr));
+      addr.index = addrs.size();
+      addrs.push_back(std::move(addr));
       return 0;
     }
 
     addr.family = AF_INET;
-    listenerconf.addrs.push_back(addr);
+    addr.index = addrs.size();
+    addrs.push_back(addr);
 
     addr.family = AF_INET6;
-    listenerconf.addrs.push_back(std::move(addr));
+    addr.index = addrs.size();
+    addrs.push_back(std::move(addr));
 
     return 0;
   }
@@ -3106,45 +3394,10 @@ int parse_config(Config *config, int optid, const StringRef &opt,
   case SHRPX_OPTID_PADDING:
     return parse_uint(&config->padding, opt, optarg);
   case SHRPX_OPTID_ALTSVC: {
-    auto tokens = util::split_str(optarg, ',');
-
-    if (tokens.size() < 2) {
-      // Requires at least protocol_id and port
-      LOG(ERROR) << opt << ": too few parameters: " << optarg;
-      return -1;
-    }
-
-    if (tokens.size() > 4) {
-      // We only need protocol_id, port, host and origin
-      LOG(ERROR) << opt << ": too many parameters: " << optarg;
-      return -1;
-    }
-
-    int port;
-
-    if (parse_uint(&port, opt, tokens[1]) != 0) {
-      return -1;
-    }
-
-    if (port < 1 ||
-        port > static_cast<int>(std::numeric_limits<uint16_t>::max())) {
-      LOG(ERROR) << opt << ": port is invalid: " << tokens[1];
-      return -1;
-    }
-
     AltSvc altsvc{};
 
-    altsvc.protocol_id = make_string_ref(config->balloc, tokens[0]);
-
-    altsvc.port = port;
-    altsvc.service = make_string_ref(config->balloc, tokens[1]);
-
-    if (tokens.size() > 2) {
-      altsvc.host = make_string_ref(config->balloc, tokens[2]);
-
-      if (tokens.size() > 3) {
-        altsvc.origin = make_string_ref(config->balloc, tokens[3]);
-      }
+    if (parse_altsvc(altsvc, opt, optarg) != 0) {
+      return -1;
     }
 
     config->http.altsvcs.push_back(std::move(altsvc));
@@ -3463,8 +3716,11 @@ int parse_config(Config *config, int optid, const StringRef &opt,
     return 0;
   }
   case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST:
-    config->tls.no_http2_cipher_black_list = util::strieq_l("yes", optarg);
-
+    LOG(WARN) << opt << ": deprecated.  Use "
+              << SHRPX_OPT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+    // fall through
+  case SHRPX_OPTID_NO_HTTP2_CIPHER_BLOCK_LIST:
+    config->tls.no_http2_cipher_block_list = util::strieq_l("yes", optarg);
     return 0;
   case SHRPX_OPTID_BACKEND_HTTP1_TLS:
   case SHRPX_OPTID_BACKEND_TLS:
@@ -3666,7 +3922,11 @@ int parse_config(Config *config, int optid, const StringRef &opt,
     return 0;
 #endif // LIBRESSL_LEGACY_API
   case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLACK_LIST:
-    config->tls.client.no_http2_cipher_black_list =
+    LOG(WARN) << opt << ": deprecated.  Use "
+              << SHRPX_OPT_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST << " instead.";
+    // fall through
+  case SHRPX_OPTID_CLIENT_NO_HTTP2_CIPHER_BLOCK_LIST:
+    config->tls.client.no_http2_cipher_block_list =
         util::strieq_l("yes", optarg);
 
     return 0;
@@ -3694,7 +3954,7 @@ int parse_config(Config *config, int optid, const StringRef &opt,
                     "65535], inclusive";
       return -1;
     }
-    config->http.redirect_https_port = optarg;
+    config->http.redirect_https_port = make_string_ref(config->balloc, optarg);
     return 0;
   }
   case SHRPX_OPTID_FRONTEND_MAX_REQUESTS:
@@ -3742,6 +4002,168 @@ int parse_config(Config *config, int optid, const StringRef &opt,
     config->http.early_data.strip_incoming = !util::strieq_l("yes", optarg);
 
     return 0;
+  case SHRPX_OPTID_QUIC_BPF_PROGRAM_FILE:
+#ifdef ENABLE_HTTP3
+    config->quic.bpf.prog_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_NO_QUIC_BPF:
+#ifdef ENABLE_HTTP3
+    config->quic.bpf.disabled = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_HTTP2_ALTSVC: {
+    AltSvc altsvc{};
+
+    if (parse_altsvc(altsvc, opt, optarg) != 0) {
+      return -1;
+    }
+
+    config->http.http2_altsvcs.push_back(std::move(altsvc));
+
+    return 0;
+  }
+  case SHRPX_OPTID_FRONTEND_HTTP3_READ_TIMEOUT:
+#ifdef ENABLE_HTTP3
+    return parse_duration(&config->conn.upstream.timeout.http3_read, opt,
+                          optarg);
+#else  // !ENABLE_HTTP3
+    return 0;
+#endif // !ENABLE_HTTP3
+  case SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT:
+#ifdef ENABLE_HTTP3
+    return parse_duration(&config->quic.upstream.timeout.idle, opt, optarg);
+#else  // !ENABLE_HTTP3
+    return 0;
+#endif // !ENABLE_HTTP3
+  case SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG:
+#ifdef ENABLE_HTTP3
+    config->quic.upstream.debug.log = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_HTTP3_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+    if (parse_uint_with_unit(&config->http3.upstream.window_size, opt,
+                             optarg) != 0) {
+      return -1;
+    }
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_HTTP3_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+    if (parse_uint_with_unit(&config->http3.upstream.connection_window_size,
+                             opt, optarg) != 0) {
+      return -1;
+    }
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_HTTP3_MAX_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+    if (parse_uint_with_unit(&config->http3.upstream.max_window_size, opt,
+                             optarg) != 0) {
+      return -1;
+    }
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONNECTION_WINDOW_SIZE:
+#ifdef ENABLE_HTTP3
+    if (parse_uint_with_unit(&config->http3.upstream.max_connection_window_size,
+                             opt, optarg) != 0) {
+      return -1;
+    }
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS:
+#ifdef ENABLE_HTTP3
+    return parse_uint(&config->http3.upstream.max_concurrent_streams, opt,
+                      optarg);
+#else  // !ENABLE_HTTP3
+    return 0;
+#endif // !ENABLE_HTTP3
+  case SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA:
+#ifdef ENABLE_HTTP3
+    config->quic.upstream.early_data = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR:
+#ifdef ENABLE_HTTP3
+    config->quic.upstream.qlog.dir = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_QUIC_REQUIRE_TOKEN:
+#ifdef ENABLE_HTTP3
+    config->quic.upstream.require_token = util::strieq_l("yes", optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_QUIC_CONGESTION_CONTROLLER:
+#ifdef ENABLE_HTTP3
+    if (util::strieq_l("cubic", optarg)) {
+      config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_CUBIC;
+    } else if (util::strieq_l("bbr", optarg)) {
+      config->quic.upstream.congestion_controller = NGTCP2_CC_ALGO_BBR;
+    } else {
+      LOG(ERROR) << opt << ": must be either cubic or bbr";
+      return -1;
+    }
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_QUIC_SERVER_ID:
+#ifdef ENABLE_HTTP3
+    if (optarg.size() != config->quic.server_id.size() * 2 ||
+        !util::is_hex_string(optarg)) {
+      LOG(ERROR) << opt << ": must be a hex-string";
+      return -1;
+    }
+    util::decode_hex(std::begin(config->quic.server_id), optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_FRONTEND_QUIC_SECRET_FILE:
+#ifdef ENABLE_HTTP3
+    config->quic.upstream.secret_file = make_string_ref(config->balloc, optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  case SHRPX_OPTID_RLIMIT_MEMLOCK: {
+    int n;
+
+    if (parse_uint(&n, opt, optarg) != 0) {
+      return -1;
+    }
+
+    if (n < 0) {
+      LOG(ERROR) << opt << ": specify the integer more than or equal to 0";
+
+      return -1;
+    }
+
+    config->rlimit_memlock = n;
+
+    return 0;
+  }
+  case SHRPX_OPTID_MAX_WORKER_PROCESSES:
+    return parse_uint(&config->max_worker_processes, opt, optarg);
+  case SHRPX_OPTID_WORKER_PROCESS_GRACE_SHUTDOWN_PERIOD:
+    return parse_duration(&config->worker_process_grace_shutdown_period, opt,
+                          optarg);
+  case SHRPX_OPTID_FRONTEND_QUIC_INITIAL_RTT: {
+#ifdef ENABLE_HTTP3
+    return parse_duration(&config->quic.upstream.initial_rtt, opt, optarg);
+#endif // ENABLE_HTTP3
+
+    return 0;
+  }
   case SHRPX_OPTID_CONF:
     LOG(WARN) << "conf: ignored";
 
@@ -3930,6 +4352,8 @@ StringRef strproto(Proto proto) {
     return StringRef::from_lit("http/1.1");
   case Proto::HTTP2:
     return StringRef::from_lit("h2");
+  case Proto::HTTP3:
+    return StringRef::from_lit("h3");
   case Proto::MEMCACHED:
     return StringRef::from_lit("memcached");
   }
@@ -4074,6 +4498,8 @@ int configure_downstream_group(Config *config, bool http2_proxy,
 
   auto resolve_flags = numeric_addr_only ? AI_NUMERICHOST | AI_NUMERICSERV : 0;
 
+  std::array<char, util::max_hostport> hostport_buf;
+
   for (auto &g : addr_groups) {
     std::unordered_map<StringRef, uint32_t> wgchk;
     for (auto &addr : g.addrs) {
@@ -4119,7 +4545,7 @@ int configure_downstream_group(Config *config, bool http2_proxy,
           util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port);
 
       auto hostport =
-          util::make_hostport(downstreamconf.balloc, addr.host, addr.port);
+          util::make_hostport(std::begin(hostport_buf), addr.host, addr.port);
 
       if (!addr.dns) {
         if (resolve_hostname(&addr.addr, addr.host.c_str(), addr.port,