diff options
Diffstat (limited to 'src/shrpx_config.cc')
-rw-r--r-- | src/shrpx_config.cc | 4694 |
1 files changed, 4694 insertions, 0 deletions
diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc new file mode 100644 index 0000000..d6d0d07 --- /dev/null +++ b/src/shrpx_config.cc @@ -0,0 +1,4694 @@ +/* + * 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_config.h" + +#ifdef HAVE_PWD_H +# include <pwd.h> +#endif // HAVE_PWD_H +#ifdef HAVE_NETDB_H +# include <netdb.h> +#endif // HAVE_NETDB_H +#ifdef HAVE_SYSLOG_H +# include <syslog.h> +#endif // HAVE_SYSLOG_H +#include <sys/types.h> +#include <sys/stat.h> +#ifdef HAVE_FCNTL_H +# include <fcntl.h> +#endif // HAVE_FCNTL_H +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif // HAVE_UNISTD_H +#include <dirent.h> + +#include <cstring> +#include <cerrno> +#include <limits> +#include <fstream> +#include <unordered_map> + +#include <nghttp2/nghttp2.h> + +#include "url-parser/url_parser.h" + +#include "shrpx_log.h" +#include "shrpx_tls.h" +#include "shrpx_http.h" +#ifdef HAVE_MRUBY +# include "shrpx_mruby.h" +#endif // HAVE_MRUBY +#include "util.h" +#include "base64.h" +#include "ssl_compat.h" +#include "xsi_strerror.h" + +namespace shrpx { + +namespace { +Config *config; +} // namespace + +constexpr auto SHRPX_UNIX_PATH_PREFIX = StringRef::from_lit("unix:"); + +const Config *get_config() { return config; } + +Config *mod_config() { return config; } + +std::unique_ptr<Config> replace_config(std::unique_ptr<Config> another) { + auto p = config; + config = another.release(); + return std::unique_ptr<Config>(p); +} + +void create_config() { config = new Config(); } + +Config::~Config() { + auto &upstreamconf = http2.upstream; + + nghttp2_option_del(upstreamconf.option); + nghttp2_option_del(upstreamconf.alt_mode_option); + nghttp2_session_callbacks_del(upstreamconf.callbacks); + + auto &downstreamconf = http2.downstream; + + nghttp2_option_del(downstreamconf.option); + nghttp2_session_callbacks_del(downstreamconf.callbacks); + + auto &dumpconf = http2.upstream.debug.dump; + + if (dumpconf.request_header) { + fclose(dumpconf.request_header); + } + + if (dumpconf.response_header) { + fclose(dumpconf.response_header); + } +} + +TicketKeys::~TicketKeys() { + /* Erase keys from memory */ + for (auto &key : keys) { + memset(&key, 0, sizeof(key)); + } +} + +namespace { +int split_host_port(char *host, size_t hostlen, uint16_t *port_ptr, + const StringRef &hostport, const StringRef &opt) { + // host and port in |hostport| is separated by single ','. + auto sep = std::find(std::begin(hostport), std::end(hostport), ','); + if (sep == std::end(hostport)) { + LOG(ERROR) << opt << ": Invalid host, port: " << hostport; + return -1; + } + size_t len = sep - std::begin(hostport); + if (hostlen < len + 1) { + LOG(ERROR) << opt << ": Hostname too long: " << hostport; + return -1; + } + std::copy(std::begin(hostport), sep, host); + host[len] = '\0'; + + auto portstr = StringRef{sep + 1, std::end(hostport)}; + auto d = util::parse_uint(portstr); + if (1 <= d && d <= std::numeric_limits<uint16_t>::max()) { + *port_ptr = d; + return 0; + } + + LOG(ERROR) << opt << ": Port is invalid: " << portstr; + return -1; +} +} // namespace + +namespace { +bool is_secure(const StringRef &filename) { + struct stat buf; + int rv = stat(filename.c_str(), &buf); + if (rv == 0) { + if ((buf.st_mode & S_IRWXU) && !(buf.st_mode & S_IRWXG) && + !(buf.st_mode & S_IRWXO)) { + return true; + } + } + + return false; +} +} // namespace + +std::unique_ptr<TicketKeys> +read_tls_ticket_key_file(const std::vector<StringRef> &files, + const EVP_CIPHER *cipher, const EVP_MD *hmac) { + auto ticket_keys = std::make_unique<TicketKeys>(); + auto &keys = ticket_keys->keys; + keys.resize(files.size()); + auto enc_keylen = EVP_CIPHER_key_length(cipher); + auto hmac_keylen = EVP_MD_size(hmac); + if (cipher == EVP_aes_128_cbc()) { + // backward compatibility, as a legacy of using same file format + // with nginx and apache. + hmac_keylen = 16; + } + auto expectedlen = keys[0].data.name.size() + enc_keylen + hmac_keylen; + char buf[256]; + assert(sizeof(buf) >= expectedlen); + + size_t i = 0; + for (auto &file : files) { + struct stat fst {}; + + if (stat(file.c_str(), &fst) == -1) { + auto error = errno; + LOG(ERROR) << "tls-ticket-key-file: could not stat file " << file + << ", errno=" << error; + return nullptr; + } + + if (static_cast<size_t>(fst.st_size) != expectedlen) { + LOG(ERROR) << "tls-ticket-key-file: the expected file size is " + << expectedlen << ", the actual file size is " << fst.st_size; + return nullptr; + } + + std::ifstream f(file.c_str()); + if (!f) { + LOG(ERROR) << "tls-ticket-key-file: could not open file " << file; + return nullptr; + } + + f.read(buf, expectedlen); + if (static_cast<size_t>(f.gcount()) != expectedlen) { + LOG(ERROR) << "tls-ticket-key-file: want to read " << expectedlen + << " bytes but only read " << f.gcount() << " bytes from " + << file; + return nullptr; + } + + auto &key = keys[i++]; + key.cipher = cipher; + key.hmac = hmac; + key.hmac_keylen = hmac_keylen; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "enc_keylen=" << enc_keylen + << ", hmac_keylen=" << key.hmac_keylen; + } + + auto p = buf; + std::copy_n(p, key.data.name.size(), std::begin(key.data.name)); + p += key.data.name.size(); + std::copy_n(p, enc_keylen, std::begin(key.data.enc_key)); + p += enc_keylen; + std::copy_n(p, hmac_keylen, std::begin(key.data.hmac_key)); + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "session ticket key: " << util::format_hex(key.data.name); + } + } + 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; + +#ifdef O_CLOEXEC + auto fd = open(filename, O_WRONLY | O_CLOEXEC | O_CREAT | O_TRUNC, + S_IRUSR | S_IWUSR); +#else + auto fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + + // We get race condition if execve is called at the same time. + if (fd != -1) { + util::make_socket_closeonexec(fd); + } +#endif + if (fd == -1) { + auto error = errno; + LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return nullptr; + } + auto f = fdopen(fd, "wb"); + if (f == nullptr) { + auto error = errno; + LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return nullptr; + } + + return f; +} + +namespace { +// Read passwd from |filename| +std::string read_passwd_from_file(const StringRef &opt, + const StringRef &filename) { + std::string line; + + if (!is_secure(filename)) { + LOG(ERROR) << opt << ": Private key passwd file " << filename + << " has insecure mode."; + return line; + } + + std::ifstream in(filename.c_str(), std::ios::binary); + if (!in) { + LOG(ERROR) << opt << ": Could not open key passwd file " << filename; + return line; + } + + std::getline(in, line); + return line; +} +} // namespace + +HeaderRefs::value_type parse_header(BlockAllocator &balloc, + const StringRef &optarg) { + auto colon = std::find(std::begin(optarg), std::end(optarg), ':'); + + if (colon == std::end(optarg) || colon == std::begin(optarg)) { + return {}; + } + + auto value = colon + 1; + for (; *value == '\t' || *value == ' '; ++value) + ; + + auto name_iov = + make_byte_ref(balloc, std::distance(std::begin(optarg), colon) + 1); + auto p = name_iov.base; + p = std::copy(std::begin(optarg), colon, p); + util::inp_strlower(name_iov.base, p); + *p = '\0'; + + auto nv = + HeaderRef(StringRef{name_iov.base, p}, + make_string_ref(balloc, StringRef{value, std::end(optarg)})); + + if (!nghttp2_check_header_name(nv.name.byte(), nv.name.size()) || + !nghttp2_check_header_value_rfc9113(nv.value.byte(), nv.value.size())) { + return {}; + } + + return nv; +} + +template <typename T> +int parse_uint(T *dest, const StringRef &opt, const StringRef &optarg) { + auto val = util::parse_uint(optarg); + if (val == -1) { + LOG(ERROR) << opt << ": bad value. Specify an integer >= 0."; + return -1; + } + + *dest = val; + + return 0; +} + +namespace { +template <typename T> +int parse_uint_with_unit(T *dest, const StringRef &opt, + const StringRef &optarg) { + auto n = util::parse_uint_with_unit(optarg); + if (n == -1) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + if (static_cast<uint64_t>(std::numeric_limits<T>::max()) < + static_cast<uint64_t>(n)) { + LOG(ERROR) << opt + << ": too large. The value should be less than or equal to " + << std::numeric_limits<T>::max(); + return -1; + } + + *dest = n; + + return 0; +} +} // namespace + +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) { + case 3: + switch (name[2]) { + case 'd': + if (util::strieq_l("pi", name, 2)) { + return LogFragmentType::PID; + } + break; + } + 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; + } + break; + } + 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; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'i': + if (util::strieq_l("tls_sn", name, 6)) { + return LogFragmentType::TLS_SNI; + } + break; + case 't': + if (util::strieq_l("reques", name, 6)) { + return LogFragmentType::REQUEST; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'l': + if (util::strieq_l("time_loca", name, 9)) { + return LogFragmentType::TIME_LOCAL; + } + break; + case 'r': + if (util::strieq_l("ssl_ciphe", name, 9)) { + return LogFragmentType::SSL_CIPHER; + } + if (util::strieq_l("tls_ciphe", name, 9)) { + return LogFragmentType::TLS_CIPHER; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'r': + if (util::strieq_l("remote_add", name, 10)) { + return LogFragmentType::REMOTE_ADDR; + } + break; + case 't': + if (util::strieq_l("remote_por", name, 10)) { + return LogFragmentType::REMOTE_PORT; + } + if (util::strieq_l("server_por", name, 10)) { + return LogFragmentType::SERVER_PORT; + } + break; + } + break; + case 12: + switch (name[11]) { + case '1': + if (util::strieq_l("time_iso860", name, 11)) { + return LogFragmentType::TIME_ISO8601; + } + break; + case 'e': + if (util::strieq_l("request_tim", name, 11)) { + return LogFragmentType::REQUEST_TIME; + } + break; + case 'l': + if (util::strieq_l("ssl_protoco", name, 11)) { + return LogFragmentType::SSL_PROTOCOL; + } + if (util::strieq_l("tls_protoco", name, 11)) { + return LogFragmentType::TLS_PROTOCOL; + } + break; + case 't': + if (util::strieq_l("backend_hos", name, 11)) { + return LogFragmentType::BACKEND_HOST; + } + if (util::strieq_l("backend_por", name, 11)) { + return LogFragmentType::BACKEND_PORT; + } + break; + } + break; + case 14: + switch (name[13]) { + case 'd': + if (util::strieq_l("ssl_session_i", name, 13)) { + return LogFragmentType::SSL_SESSION_ID; + } + if (util::strieq_l("tls_session_i", name, 13)) { + return LogFragmentType::TLS_SESSION_ID; + } + break; + } + break; + case 15: + switch (name[14]) { + case 't': + if (util::strieq_l("body_bytes_sen", name, 14)) { + return LogFragmentType::BODY_BYTES_SENT; + } + 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': + if (util::strieq_l("tls_client_seria", name, 16)) { + return LogFragmentType::TLS_CLIENT_SERIAL; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'd': + if (util::strieq_l("ssl_session_reuse", name, 17)) { + return LogFragmentType::SSL_SESSION_REUSED; + } + if (util::strieq_l("tls_session_reuse", name, 17)) { + 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: + switch (name[21]) { + case 'e': + if (util::strieq_l("tls_client_issuer_nam", name, 21)) { + return LogFragmentType::TLS_CLIENT_ISSUER_NAME; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'e': + if (util::strieq_l("tls_client_subject_nam", name, 22)) { + return LogFragmentType::TLS_CLIENT_SUBJECT_NAME; + } + break; + } + break; + case 27: + switch (name[26]) { + case '1': + if (util::strieq_l("tls_client_fingerprint_sha", name, 26)) { + return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA1; + } + break; + } + break; + case 29: + switch (name[28]) { + case '6': + if (util::strieq_l("tls_client_fingerprint_sha25", name, 28)) { + return LogFragmentType::TLS_CLIENT_FINGERPRINT_SHA256; + } + break; + } + break; + } + return LogFragmentType::NONE; +} +} // namespace + +namespace { +bool var_token(char c) { + return util::is_alpha(c) || util::is_digit(c) || c == '_'; +} +} // namespace + +std::vector<LogFragment> parse_log_format(BlockAllocator &balloc, + const StringRef &optarg) { + auto literal_start = std::begin(optarg); + auto p = literal_start; + auto eop = std::end(optarg); + + auto res = std::vector<LogFragment>(); + + for (; p != eop;) { + if (*p != '$') { + ++p; + continue; + } + + auto var_start = p; + + ++p; + + const char *var_name; + size_t var_namelen; + if (p != eop && *p == '{') { + var_name = ++p; + for (; p != eop && var_token(*p); ++p) + ; + + if (p == eop || *p != '}') { + LOG(WARN) << "Missing '}' after " << StringRef{var_start, p}; + continue; + } + + var_namelen = p - var_name; + ++p; + } else { + var_name = p; + for (; p != eop && var_token(*p); ++p) + ; + + var_namelen = p - var_name; + } + + const char *value = nullptr; + + auto type = log_var_lookup_token(var_name, var_namelen); + + if (type == LogFragmentType::NONE) { + if (util::istarts_with_l(StringRef{var_name, var_namelen}, "http_")) { + if (util::streq_l("host", StringRef{var_name + str_size("http_"), + var_namelen - str_size("http_")})) { + // Special handling of host header field. We will use + // :authority header field if host header is missing. This + // is a typical case in HTTP/2. + type = LogFragmentType::AUTHORITY; + } else { + type = LogFragmentType::HTTP; + value = var_name + str_size("http_"); + } + } else { + LOG(WARN) << "Unrecognized log format variable: " + << StringRef{var_name, var_namelen}; + continue; + } + } + + if (literal_start < var_start) { + res.emplace_back( + LogFragmentType::LITERAL, + make_string_ref(balloc, StringRef{literal_start, var_start})); + } + + literal_start = p; + + if (value == nullptr) { + res.emplace_back(type); + continue; + } + + { + auto iov = make_byte_ref( + balloc, std::distance(value, var_name + var_namelen) + 1); + auto p = iov.base; + p = std::copy(value, var_name + var_namelen, p); + for (auto cp = iov.base; cp != p; ++cp) { + if (*cp == '_') { + *cp = '-'; + } + } + *p = '\0'; + res.emplace_back(type, StringRef{iov.base, p}); + } + } + + if (literal_start != eop) { + res.emplace_back(LogFragmentType::LITERAL, + make_string_ref(balloc, StringRef{literal_start, eop})); + } + + return res; +} + +namespace { +int parse_address_family(int *dest, const StringRef &opt, + const StringRef &optarg) { + if (util::strieq_l("auto", optarg)) { + *dest = AF_UNSPEC; + return 0; + } + if (util::strieq_l("IPv4", optarg)) { + *dest = AF_INET; + return 0; + } + if (util::strieq_l("IPv6", optarg)) { + *dest = AF_INET6; + return 0; + } + + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; +} +} // namespace + +namespace { +int parse_duration(ev_tstamp *dest, const StringRef &opt, + const StringRef &optarg) { + auto t = util::parse_duration_with_unit(optarg); + if (t == std::numeric_limits<double>::infinity()) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + *dest = t; + + return 0; +} +} // namespace + +namespace { +int parse_tls_proto_version(int &dest, const StringRef &opt, + const StringRef &optarg) { + auto v = tls::proto_version_from_string(optarg); + if (v == -1) { + LOG(ERROR) << opt << ": invalid TLS protocol version: " << optarg; + return -1; + } + + dest = v; + + return 0; +} +} // namespace + +struct MemcachedConnectionParams { + bool tls; +}; + +namespace { +// Parses memcached connection configuration parameter |src_params|, +// and stores parsed results into |out|. This function returns 0 if +// it succeeds, or -1. +int parse_memcached_connection_params(MemcachedConnectionParams &out, + const StringRef &src_params, + const StringRef &opt) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (!param.empty()) { + LOG(ERROR) << opt << ": " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +struct UpstreamParams { + UpstreamAltMode alt_mode; + bool tls; + bool sni_fwd; + bool proxyproto; + bool quic; +}; + +namespace { +// Parses upstream configuration parameter |src_params|, and stores +// parsed results into |out|. This function returns 0 if it succeeds, +// or -1. +int parse_upstream_params(UpstreamParams &out, const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("sni-fwd", param)) { + out.sni_fwd = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (util::strieq_l("api", param)) { + if (out.alt_mode != UpstreamAltMode::NONE && + out.alt_mode != UpstreamAltMode::API) { + LOG(ERROR) << "frontend: api and healthmon are mutually exclusive"; + return -1; + } + out.alt_mode = UpstreamAltMode::API; + } else if (util::strieq_l("healthmon", param)) { + if (out.alt_mode != UpstreamAltMode::NONE && + out.alt_mode != UpstreamAltMode::HEALTHMON) { + LOG(ERROR) << "frontend: api and healthmon are mutually exclusive"; + return -1; + } + 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; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +struct DownstreamParams { + StringRef sni; + StringRef mruby; + StringRef group; + AffinityConfig affinity; + ev_tstamp read_timeout; + ev_tstamp write_timeout; + size_t fall; + size_t rise; + uint32_t weight; + uint32_t group_weight; + Proto proto; + bool tls; + bool dns; + bool redirect_if_not_tls; + bool upgrade_scheme; + bool dnf; +}; + +namespace { +// Parses |value| of parameter named |name| as duration. This +// function returns 0 if it succeeds and the parsed value is assigned +// to |dest|, or -1. +int parse_downstream_param_duration(ev_tstamp &dest, const StringRef &name, + const StringRef &value) { + auto t = util::parse_duration_with_unit(value); + if (t == std::numeric_limits<double>::infinity()) { + LOG(ERROR) << "backend: " << name << ": bad value: '" << value << "'"; + return -1; + } + dest = t; + return 0; +} +} // namespace + +namespace { +// Parses downstream configuration parameter |src_params|, and stores +// parsed results into |out|. This function returns 0 if it succeeds, +// or -1. +int parse_downstream_params(DownstreamParams &out, + const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::istarts_with_l(param, "proto=")) { + auto protostr = StringRef{first + str_size("proto="), end}; + if (protostr.empty()) { + LOG(ERROR) << "backend: proto: protocol is empty"; + return -1; + } + + if (util::streq_l("h2", std::begin(protostr), protostr.size())) { + out.proto = Proto::HTTP2; + } else if (util::streq_l("http/1.1", std::begin(protostr), + protostr.size())) { + out.proto = Proto::HTTP1; + } else { + LOG(ERROR) << "backend: proto: unknown protocol " << protostr; + return -1; + } + } else if (util::istarts_with_l(param, "fall=")) { + auto valstr = StringRef{first + str_size("fall="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: fall: non-negative integer is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n == -1) { + LOG(ERROR) << "backend: fall: non-negative integer is expected"; + return -1; + } + + out.fall = n; + } else if (util::istarts_with_l(param, "rise=")) { + auto valstr = StringRef{first + str_size("rise="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: rise: non-negative integer is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n == -1) { + LOG(ERROR) << "backend: rise: non-negative integer is expected"; + return -1; + } + + out.rise = n; + } else if (util::strieq_l("tls", param)) { + out.tls = true; + } else if (util::strieq_l("no-tls", param)) { + out.tls = false; + } else if (util::istarts_with_l(param, "sni=")) { + out.sni = StringRef{first + str_size("sni="), end}; + } else if (util::istarts_with_l(param, "affinity=")) { + auto valstr = StringRef{first + str_size("affinity="), end}; + if (util::strieq_l("none", valstr)) { + out.affinity.type = SessionAffinity::NONE; + } else if (util::strieq_l("ip", valstr)) { + out.affinity.type = SessionAffinity::IP; + } else if (util::strieq_l("cookie", valstr)) { + out.affinity.type = SessionAffinity::COOKIE; + } else { + LOG(ERROR) + << "backend: affinity: value must be one of none, ip, and cookie"; + return -1; + } + } else if (util::istarts_with_l(param, "affinity-cookie-name=")) { + auto val = StringRef{first + str_size("affinity-cookie-name="), end}; + if (val.empty()) { + LOG(ERROR) + << "backend: affinity-cookie-name: non empty string is expected"; + return -1; + } + out.affinity.cookie.name = val; + } else if (util::istarts_with_l(param, "affinity-cookie-path=")) { + out.affinity.cookie.path = + StringRef{first + str_size("affinity-cookie-path="), end}; + } else if (util::istarts_with_l(param, "affinity-cookie-secure=")) { + auto valstr = StringRef{first + str_size("affinity-cookie-secure="), end}; + if (util::strieq_l("auto", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::AUTO; + } else if (util::strieq_l("yes", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::YES; + } else if (util::strieq_l("no", valstr)) { + out.affinity.cookie.secure = SessionAffinityCookieSecure::NO; + } else { + LOG(ERROR) << "backend: affinity-cookie-secure: value must be one of " + "auto, yes, and no"; + return -1; + } + } else if (util::istarts_with_l(param, "affinity-cookie-stickiness=")) { + auto valstr = + StringRef{first + str_size("affinity-cookie-stickiness="), end}; + if (util::strieq_l("loose", valstr)) { + out.affinity.cookie.stickiness = SessionAffinityCookieStickiness::LOOSE; + } else if (util::strieq_l("strict", valstr)) { + out.affinity.cookie.stickiness = + SessionAffinityCookieStickiness::STRICT; + } else { + LOG(ERROR) << "backend: affinity-cookie-stickiness: value must be " + "either loose or strict"; + return -1; + } + } else if (util::strieq_l("dns", param)) { + out.dns = true; + } else if (util::strieq_l("redirect-if-not-tls", param)) { + out.redirect_if_not_tls = true; + } else if (util::strieq_l("upgrade-scheme", param)) { + out.upgrade_scheme = true; + } else if (util::istarts_with_l(param, "mruby=")) { + auto valstr = StringRef{first + str_size("mruby="), end}; + out.mruby = valstr; + } else if (util::istarts_with_l(param, "read-timeout=")) { + if (parse_downstream_param_duration( + out.read_timeout, StringRef::from_lit("read-timeout"), + StringRef{first + str_size("read-timeout="), end}) == -1) { + return -1; + } + } else if (util::istarts_with_l(param, "write-timeout=")) { + if (parse_downstream_param_duration( + out.write_timeout, StringRef::from_lit("write-timeout"), + StringRef{first + str_size("write-timeout="), end}) == -1) { + return -1; + } + } else if (util::istarts_with_l(param, "weight=")) { + auto valstr = StringRef{first + str_size("weight="), end}; + if (valstr.empty()) { + LOG(ERROR) + << "backend: weight: non-negative integer [1, 256] is expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n < 1 || n > 256) { + LOG(ERROR) + << "backend: weight: non-negative integer [1, 256] is expected"; + return -1; + } + out.weight = n; + } else if (util::istarts_with_l(param, "group=")) { + auto valstr = StringRef{first + str_size("group="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: group: empty string is not allowed"; + return -1; + } + out.group = valstr; + } else if (util::istarts_with_l(param, "group-weight=")) { + auto valstr = StringRef{first + str_size("group-weight="), end}; + if (valstr.empty()) { + LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is " + "expected"; + return -1; + } + + auto n = util::parse_uint(valstr); + if (n < 1 || n > 256) { + LOG(ERROR) << "backend: group-weight: non-negative integer [1, 256] is " + "expected"; + 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; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +namespace { +// Parses host-path mapping patterns in |src_pattern|, and stores +// mappings in config. We will store each host-path pattern found in +// |src| with |addr|. |addr| will be copied accordingly. Also we +// make a group based on the pattern. The "/" pattern is considered +// as catch-all. We also parse protocol specified in |src_proto|. +// +// This function returns 0 if it succeeds, or -1. +int parse_mapping(Config *config, DownstreamAddrConfig &addr, + std::map<StringRef, size_t> &pattern_addr_indexer, + const StringRef &src_pattern, const StringRef &src_params) { + // This returns at least 1 element (it could be empty string). We + // will append '/' to all patterns, so it becomes catch-all pattern. + auto mapping = util::split_str(src_pattern, ':'); + assert(!mapping.empty()); + auto &downstreamconf = *config->conn.downstream; + auto &addr_groups = downstreamconf.addr_groups; + + DownstreamParams params{}; + params.proto = Proto::HTTP1; + params.weight = 1; + + if (parse_downstream_params(params, src_params) != 0) { + return -1; + } + + if (addr.host_unix && params.dns) { + LOG(ERROR) << "backend: dns: cannot be used for UNIX domain socket"; + return -1; + } + + if (params.affinity.type == SessionAffinity::COOKIE && + params.affinity.cookie.name.empty()) { + LOG(ERROR) << "backend: affinity-cookie-name is mandatory if " + "affinity=cookie is specified"; + return -1; + } + + addr.fall = params.fall; + addr.rise = params.rise; + addr.weight = params.weight; + addr.group = make_string_ref(downstreamconf.balloc, params.group); + addr.group_weight = params.group_weight; + addr.proto = params.proto; + addr.tls = params.tls; + 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; + auto &rw_router = routerconf.rev_wildcard_router; + auto &wildcard_patterns = routerconf.wildcard_patterns; + + for (const auto &raw_pattern : mapping) { + StringRef pattern; + auto slash = std::find(std::begin(raw_pattern), std::end(raw_pattern), '/'); + if (slash == std::end(raw_pattern)) { + // This effectively makes empty pattern to "/". 2 for '/' and + // terminal NULL character. + auto iov = make_byte_ref(downstreamconf.balloc, raw_pattern.size() + 2); + auto p = iov.base; + p = std::copy(std::begin(raw_pattern), std::end(raw_pattern), p); + util::inp_strlower(iov.base, p); + *p++ = '/'; + *p = '\0'; + pattern = StringRef{iov.base, p}; + } else { + 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); + auto p = iov.base; + p = std::copy(std::begin(raw_pattern), slash, p); + util::inp_strlower(iov.base, p); + p = std::copy(std::begin(path), std::end(path), p); + *p = '\0'; + pattern = StringRef{iov.base, p}; + } + auto it = pattern_addr_indexer.find(pattern); + if (it != std::end(pattern_addr_indexer)) { + auto &g = addr_groups[(*it).second]; + // Last value wins if we have multiple different affinity + // value under one group. + if (params.affinity.type != SessionAffinity::NONE) { + if (g.affinity.type == SessionAffinity::NONE) { + g.affinity.type = params.affinity.type; + if (params.affinity.type == SessionAffinity::COOKIE) { + g.affinity.cookie.name = make_string_ref( + downstreamconf.balloc, params.affinity.cookie.name); + if (!params.affinity.cookie.path.empty()) { + g.affinity.cookie.path = make_string_ref( + downstreamconf.balloc, params.affinity.cookie.path); + } + g.affinity.cookie.secure = params.affinity.cookie.secure; + g.affinity.cookie.stickiness = params.affinity.cookie.stickiness; + } + } else if (g.affinity.type != params.affinity.type || + g.affinity.cookie.name != params.affinity.cookie.name || + g.affinity.cookie.path != params.affinity.cookie.path || + g.affinity.cookie.secure != params.affinity.cookie.secure || + g.affinity.cookie.stickiness != + params.affinity.cookie.stickiness) { + LOG(ERROR) << "backend: affinity: multiple different affinity " + "configurations found in a single group"; + return -1; + } + } + // If at least one backend requires frontend TLS connection, + // enable it for all backends sharing the same pattern. + if (params.redirect_if_not_tls) { + g.redirect_if_not_tls = true; + } + // All backends in the same group must have the same mruby path. + // If some backends do not specify mruby file, and there is at + // least one backend with mruby file, it is used for all + // backends in the group. + if (!params.mruby.empty()) { + if (g.mruby_file.empty()) { + g.mruby_file = make_string_ref(downstreamconf.balloc, params.mruby); + } else if (g.mruby_file != params.mruby) { + LOG(ERROR) << "backend: mruby: multiple different mruby file found " + "in a single group"; + return -1; + } + } + // All backends in the same group must have the same read/write + // timeout. If some backends do not specify read/write timeout, + // and there is at least one backend with read/write timeout, it + // is used for all backends in the group. + if (params.read_timeout > 1e-9) { + if (g.timeout.read < 1e-9) { + g.timeout.read = params.read_timeout; + } else if (fabs(g.timeout.read - params.read_timeout) > 1e-9) { + LOG(ERROR) + << "backend: read-timeout: multiple different read-timeout " + "found in a single group"; + return -1; + } + } + if (params.write_timeout > 1e-9) { + if (g.timeout.write < 1e-9) { + g.timeout.write = params.write_timeout; + } else if (fabs(g.timeout.write - params.write_timeout) > 1e-9) { + LOG(ERROR) << "backend: write-timeout: multiple different " + "write-timeout found in a single group"; + 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; + } + + auto idx = addr_groups.size(); + pattern_addr_indexer.emplace(pattern, idx); + addr_groups.emplace_back(pattern); + auto &g = addr_groups.back(); + g.addrs.push_back(addr); + g.affinity.type = params.affinity.type; + if (params.affinity.type == SessionAffinity::COOKIE) { + g.affinity.cookie.name = + make_string_ref(downstreamconf.balloc, params.affinity.cookie.name); + if (!params.affinity.cookie.path.empty()) { + g.affinity.cookie.path = + make_string_ref(downstreamconf.balloc, params.affinity.cookie.path); + } + g.affinity.cookie.secure = params.affinity.cookie.secure; + g.affinity.cookie.stickiness = params.affinity.cookie.stickiness; + } + g.redirect_if_not_tls = params.redirect_if_not_tls; + 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 + auto path_first = + std::find(std::begin(g.pattern), std::end(g.pattern), '/'); + + auto host = StringRef{std::begin(g.pattern) + 1, path_first}; + auto path = StringRef{path_first, std::end(g.pattern)}; + + auto path_is_wildcard = false; + if (path[path.size() - 1] == '*') { + path = StringRef{std::begin(path), std::begin(path) + path.size() - 1}; + path_is_wildcard = true; + } + + auto it = std::find_if( + std::begin(wildcard_patterns), std::end(wildcard_patterns), + [&host](const WildcardPattern &wp) { return wp.host == host; }); + + if (it == std::end(wildcard_patterns)) { + wildcard_patterns.emplace_back(host); + + auto &router = wildcard_patterns.back().router; + router.add_route(path, idx, path_is_wildcard); + + auto iov = make_byte_ref(downstreamconf.balloc, host.size() + 1); + auto p = iov.base; + p = std::reverse_copy(std::begin(host), std::end(host), p); + *p = '\0'; + auto rev_host = StringRef{iov.base, p}; + + rw_router.add_route(rev_host, wildcard_patterns.size() - 1); + } else { + (*it).router.add_route(path, idx, path_is_wildcard); + } + + continue; + } + + auto path_is_wildcard = false; + if (pattern[pattern.size() - 1] == '*') { + pattern = StringRef{std::begin(pattern), + std::begin(pattern) + pattern.size() - 1}; + path_is_wildcard = true; + } + + router.add_route(pattern, idx, path_is_wildcard); + } + return 0; +} +} // namespace + +namespace { +ForwardedNode parse_forwarded_node_type(const StringRef &optarg) { + if (util::strieq_l("obfuscated", optarg)) { + return ForwardedNode::OBFUSCATED; + } + + if (util::strieq_l("ip", optarg)) { + return ForwardedNode::IP; + } + + if (optarg.size() < 2 || optarg[0] != '_') { + return static_cast<ForwardedNode>(-1); + } + + if (std::find_if_not(std::begin(optarg), std::end(optarg), [](char c) { + return util::is_alpha(c) || util::is_digit(c) || c == '.' || c == '_' || + c == '-'; + }) != std::end(optarg)) { + return static_cast<ForwardedNode>(-1); + } + + return ForwardedNode::OBFUSCATED; +} +} // namespace + +namespace { +int parse_error_page(std::vector<ErrorPage> &error_pages, const StringRef &opt, + const StringRef &optarg) { + std::array<char, STRERROR_BUFSIZE> errbuf; + + auto eq = std::find(std::begin(optarg), std::end(optarg), '='); + if (eq == std::end(optarg) || eq + 1 == std::end(optarg)) { + LOG(ERROR) << opt << ": bad value: '" << optarg << "'"; + return -1; + } + + auto codestr = StringRef{std::begin(optarg), eq}; + unsigned int code; + + if (codestr == StringRef::from_lit("*")) { + code = 0; + } else { + auto n = util::parse_uint(codestr); + + if (n == -1 || n < 400 || n > 599) { + LOG(ERROR) << opt << ": bad code: '" << codestr << "'"; + return -1; + } + + code = static_cast<unsigned int>(n); + } + + auto path = StringRef{eq + 1, std::end(optarg)}; + + std::vector<uint8_t> content; + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << opt << ": " << optarg << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto fd_closer = defer(close, fd); + + std::array<uint8_t, 4096> buf; + for (;;) { + auto n = read(fd, buf.data(), buf.size()); + if (n == -1) { + auto error = errno; + LOG(ERROR) << opt << ": " << optarg << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + if (n == 0) { + break; + } + content.insert(std::end(content), std::begin(buf), std::begin(buf) + n); + } + + error_pages.push_back(ErrorPage{std::move(content), code}); + + return 0; +} +} // namespace + +namespace { +// Maximum size of SCT extension payload length. +constexpr size_t MAX_SCT_EXT_LEN = 16_k; +} // namespace + +struct SubcertParams { + StringRef sct_dir; +}; + +namespace { +// Parses subcert parameter |src_params|, and stores parsed results +// into |out|. This function returns 0 if it succeeds, or -1. +int parse_subcert_params(SubcertParams &out, const StringRef &src_params) { + auto last = std::end(src_params); + for (auto first = std::begin(src_params); first != last;) { + auto end = std::find(first, last, ';'); + auto param = StringRef{first, end}; + + if (util::istarts_with_l(param, "sct-dir=")) { +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + auto sct_dir = + StringRef{std::begin(param) + str_size("sct-dir="), std::end(param)}; + if (sct_dir.empty()) { + LOG(ERROR) << "subcert: " << param << ": empty sct-dir"; + return -1; + } + out.sct_dir = sct_dir; +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + LOG(WARN) << "subcert: sct-dir is ignored because underlying TLS library " + "does not support SCT"; +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + } else if (!param.empty()) { + LOG(ERROR) << "subcert: " << param << ": unknown keyword"; + return -1; + } + + if (end == last) { + break; + } + + first = end + 1; + } + + return 0; +} +} // namespace + +namespace { +// Reads *.sct files from directory denoted by |dir_path|. |dir_path| +// must be NULL-terminated string. +int read_tls_sct_from_dir(std::vector<uint8_t> &dst, const StringRef &opt, + const StringRef &dir_path) { + std::array<char, STRERROR_BUFSIZE> errbuf; + + auto dir = opendir(dir_path.c_str()); + if (dir == nullptr) { + auto error = errno; + LOG(ERROR) << opt << ": " << dir_path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto closer = defer(closedir, dir); + + // 2 bytes total length field + auto len_idx = std::distance(std::begin(dst), std::end(dst)); + dst.insert(std::end(dst), 2, 0); + + for (;;) { + errno = 0; + auto ent = readdir(dir); + if (ent == nullptr) { + if (errno != 0) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read directory " << dir_path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + break; + } + + auto name = StringRef{ent->d_name}; + + if (name[0] == '.' || !util::iends_with_l(name, ".sct")) { + continue; + } + + std::string path; + path.resize(dir_path.size() + 1 + name.size()); + { + auto p = std::begin(path); + p = std::copy(std::begin(dir_path), std::end(dir_path), p); + *p++ = '/'; + std::copy(std::begin(name), std::end(name), p); + } + + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read SCT from " << path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + auto closer = defer(close, fd); + + // 2 bytes length field for this SCT. + auto len_idx = std::distance(std::begin(dst), std::end(dst)); + dst.insert(std::end(dst), 2, 0); + + // *.sct file tends to be small; around 110+ bytes. + std::array<char, 256> buf; + for (;;) { + ssize_t nread; + while ((nread = read(fd, buf.data(), buf.size())) == -1 && errno == EINTR) + ; + + if (nread == -1) { + auto error = errno; + LOG(ERROR) << opt << ": failed to read SCT data from " << path << ": " + << xsi_strerror(error, errbuf.data(), errbuf.size()); + return -1; + } + + if (nread == 0) { + break; + } + + dst.insert(std::end(dst), std::begin(buf), std::begin(buf) + nread); + + if (dst.size() > MAX_SCT_EXT_LEN) { + LOG(ERROR) << opt << ": the concatenated SCT data from " << dir_path + << " is too large. Max " << MAX_SCT_EXT_LEN; + return -1; + } + } + + auto len = dst.size() - len_idx - 2; + + if (len == 0) { + dst.resize(dst.size() - 2); + continue; + } + + dst[len_idx] = len >> 8; + dst[len_idx + 1] = len; + } + + auto len = dst.size() - len_idx - 2; + + if (len == 0) { + dst.resize(dst.size() - 2); + return 0; + } + + dst[len_idx] = len >> 8; + dst[len_idx + 1] = len; + + return 0; +} +} // namespace + +#ifndef OPENSSL_NO_PSK +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.psk_secrets. This function +// returns 0 if it succeeds, or -1. +int parse_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": could not open file " << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": could not fine separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + auto identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + auto secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + auto rv = tlsconf.psk_secrets.emplace(identity, secret); + if (!rv.second) { + LOG(ERROR) << SHRPX_OPT_PSK_SECRETS + << ": identity has already been registered at line " << lineno; + return -1; + } + } + + return 0; +} +} // namespace +#endif // !OPENSSL_NO_PSK + +#ifndef OPENSSL_NO_PSK +namespace { +// Reads PSK secrets from path, and parses each line. The result is +// directly stored into config->tls.client.psk. This function returns +// 0 if it succeeds, or -1. +int parse_client_psk_secrets(Config *config, const StringRef &path) { + auto &tlsconf = config->tls; + + std::ifstream f(path.c_str(), std::ios::binary); + if (!f) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": could not open file " + << path; + return -1; + } + + size_t lineno = 0; + std::string line; + while (std::getline(f, line)) { + ++lineno; + if (line.empty() || line[0] == '#') { + continue; + } + + auto sep_it = std::find(std::begin(line), std::end(line), ':'); + if (sep_it == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": could not find separator at line " << lineno; + return -1; + } + + if (sep_it == std::begin(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty identity at line " + << lineno; + return -1; + } + + if (sep_it + 1 == std::end(line)) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS << ": empty secret at line " + << lineno; + return -1; + } + + if (!util::is_hex_string(StringRef{sep_it + 1, std::end(line)})) { + LOG(ERROR) << SHRPX_OPT_CLIENT_PSK_SECRETS + << ": secret must be hex string at line " << lineno; + return -1; + } + + tlsconf.client.psk.identity = + make_string_ref(config->balloc, StringRef{std::begin(line), sep_it}); + + tlsconf.client.psk.secret = + util::decode_hex(config->balloc, StringRef{sep_it + 1, std::end(line)}); + + return 0; + } + + return 0; +} +} // namespace +#endif // !OPENSSL_NO_PSK + +// generated by gennghttpxfun.py +int option_lookup_token(const char *name, size_t namelen) { + switch (namelen) { + case 4: + switch (name[3]) { + case 'f': + if (util::strieq_l("con", name, 3)) { + return SHRPX_OPTID_CONF; + } + break; + case 'r': + if (util::strieq_l("use", name, 3)) { + return SHRPX_OPTID_USER; + } + break; + } + break; + case 6: + switch (name[5]) { + case 'a': + if (util::strieq_l("no-vi", name, 5)) { + return SHRPX_OPTID_NO_VIA; + } + break; + case 'c': + if (util::strieq_l("altsv", name, 5)) { + return SHRPX_OPTID_ALTSVC; + } + break; + case 'n': + if (util::strieq_l("daemo", name, 5)) { + return SHRPX_OPTID_DAEMON; + } + break; + case 't': + if (util::strieq_l("cacer", name, 5)) { + return SHRPX_OPTID_CACERT; + } + if (util::strieq_l("clien", name, 5)) { + return SHRPX_OPTID_CLIENT; + } + break; + } + break; + case 7: + switch (name[6]) { + case 'd': + if (util::strieq_l("backen", name, 6)) { + return SHRPX_OPTID_BACKEND; + } + break; + case 'e': + if (util::strieq_l("includ", name, 6)) { + return SHRPX_OPTID_INCLUDE; + } + break; + case 'g': + if (util::strieq_l("backlo", name, 6)) { + return SHRPX_OPTID_BACKLOG; + } + if (util::strieq_l("paddin", name, 6)) { + return SHRPX_OPTID_PADDING; + } + break; + case 'p': + if (util::strieq_l("no-ocs", name, 6)) { + return SHRPX_OPTID_NO_OCSP; + } + break; + case 's': + if (util::strieq_l("cipher", name, 6)) { + return SHRPX_OPTID_CIPHERS; + } + if (util::strieq_l("worker", name, 6)) { + return SHRPX_OPTID_WORKERS; + } + break; + case 't': + if (util::strieq_l("subcer", name, 6)) { + return SHRPX_OPTID_SUBCERT; + } + break; + } + break; + case 8: + switch (name[7]) { + case 'd': + if (util::strieq_l("fronten", name, 7)) { + return SHRPX_OPTID_FRONTEND; + } + break; + case 'e': + if (util::strieq_l("insecur", name, 7)) { + return SHRPX_OPTID_INSECURE; + } + if (util::strieq_l("pid-fil", name, 7)) { + return SHRPX_OPTID_PID_FILE; + } + break; + case 'n': + if (util::strieq_l("fastope", name, 7)) { + return SHRPX_OPTID_FASTOPEN; + } + break; + case 's': + if (util::strieq_l("tls-ktl", name, 7)) { + return SHRPX_OPTID_TLS_KTLS; + } + break; + case 't': + if (util::strieq_l("npn-lis", name, 7)) { + return SHRPX_OPTID_NPN_LIST; + } + break; + } + break; + case 9: + switch (name[8]) { + case 'e': + if (util::strieq_l("no-kqueu", name, 8)) { + return SHRPX_OPTID_NO_KQUEUE; + } + if (util::strieq_l("read-rat", name, 8)) { + return SHRPX_OPTID_READ_RATE; + } + break; + case 'l': + if (util::strieq_l("log-leve", name, 8)) { + return SHRPX_OPTID_LOG_LEVEL; + } + break; + case 't': + if (util::strieq_l("alpn-lis", name, 8)) { + return SHRPX_OPTID_ALPN_LIST; + } + break; + } + break; + case 10: + switch (name[9]) { + case 'e': + if (util::strieq_l("error-pag", name, 9)) { + return SHRPX_OPTID_ERROR_PAGE; + } + if (util::strieq_l("mruby-fil", name, 9)) { + return SHRPX_OPTID_MRUBY_FILE; + } + if (util::strieq_l("write-rat", name, 9)) { + return SHRPX_OPTID_WRITE_RATE; + } + break; + case 't': + if (util::strieq_l("read-burs", name, 9)) { + return SHRPX_OPTID_READ_BURST; + } + break; + } + break; + case 11: + switch (name[10]) { + case 'e': + if (util::strieq_l("server-nam", name, 10)) { + 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; + } + break; + case 's': + if (util::strieq_l("backend-tl", name, 10)) { + return SHRPX_OPTID_BACKEND_TLS; + } + if (util::strieq_l("ecdh-curve", name, 10)) { + return SHRPX_OPTID_ECDH_CURVES; + } + if (util::strieq_l("psk-secret", name, 10)) { + return SHRPX_OPTID_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("write-burs", name, 10)) { + return SHRPX_OPTID_WRITE_BURST; + } + break; + case 'y': + if (util::strieq_l("dns-max-tr", name, 10)) { + return SHRPX_OPTID_DNS_MAX_TRY; + } + if (util::strieq_l("http2-prox", name, 10)) { + return SHRPX_OPTID_HTTP2_PROXY; + } + break; + } + break; + case 12: + switch (name[11]) { + case '4': + if (util::strieq_l("backend-ipv", name, 11)) { + return SHRPX_OPTID_BACKEND_IPV4; + } + break; + case '6': + if (util::strieq_l("backend-ipv", name, 11)) { + 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; + } + if (util::strieq_l("http2-bridg", name, 11)) { + return SHRPX_OPTID_HTTP2_BRIDGE; + } + break; + case 'p': + if (util::strieq_l("ocsp-startu", name, 11)) { + return SHRPX_OPTID_OCSP_STARTUP; + } + break; + case 'y': + if (util::strieq_l("client-prox", name, 11)) { + return SHRPX_OPTID_CLIENT_PROXY; + } + if (util::strieq_l("forwarded-b", name, 11)) { + return SHRPX_OPTID_FORWARDED_BY; + } + break; + } + break; + case 13: + switch (name[12]) { + case 'd': + if (util::strieq_l("add-forwarde", name, 12)) { + return SHRPX_OPTID_ADD_FORWARDED; + } + if (util::strieq_l("single-threa", name, 12)) { + return SHRPX_OPTID_SINGLE_THREAD; + } + break; + case 'e': + if (util::strieq_l("dh-param-fil", name, 12)) { + return SHRPX_OPTID_DH_PARAM_FILE; + } + if (util::strieq_l("errorlog-fil", name, 12)) { + return SHRPX_OPTID_ERRORLOG_FILE; + } + if (util::strieq_l("rlimit-nofil", name, 12)) { + return SHRPX_OPTID_RLIMIT_NOFILE; + } + break; + case 'r': + if (util::strieq_l("forwarded-fo", name, 12)) { + return SHRPX_OPTID_FORWARDED_FOR; + } + break; + case 's': + if (util::strieq_l("tls13-cipher", name, 12)) { + return SHRPX_OPTID_TLS13_CIPHERS; + } + break; + case 't': + if (util::strieq_l("verify-clien", name, 12)) { + return SHRPX_OPTID_VERIFY_CLIENT; + } + break; + } + 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; + } + break; + case 'h': + if (util::strieq_l("no-server-pus", name, 13)) { + 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; + } + break; + case 's': + if (util::strieq_l("backend-no-tl", name, 13)) { + return SHRPX_OPTID_BACKEND_NO_TLS; + } + if (util::strieq_l("client-cipher", name, 13)) { + return SHRPX_OPTID_CLIENT_CIPHERS; + } + if (util::strieq_l("single-proces", name, 13)) { + return SHRPX_OPTID_SINGLE_PROCESS; + } + break; + case 't': + if (util::strieq_l("tls-proto-lis", name, 13)) { + return SHRPX_OPTID_TLS_PROTO_LIST; + } + break; + } + break; + case 15: + switch (name[14]) { + case 'e': + if (util::strieq_l("no-host-rewrit", name, 14)) { + return SHRPX_OPTID_NO_HOST_REWRITE; + } + break; + case 'g': + if (util::strieq_l("errorlog-syslo", name, 14)) { + return SHRPX_OPTID_ERRORLOG_SYSLOG; + } + break; + case 's': + if (util::strieq_l("frontend-no-tl", name, 14)) { + return SHRPX_OPTID_FRONTEND_NO_TLS; + } + break; + case 'y': + if (util::strieq_l("syslog-facilit", name, 14)) { + return SHRPX_OPTID_SYSLOG_FACILITY; + } + break; + } + break; + case 16: + switch (name[15]) { + case 'e': + if (util::strieq_l("certificate-fil", name, 15)) { + return SHRPX_OPTID_CERTIFICATE_FILE; + } + if (util::strieq_l("client-cert-fil", name, 15)) { + return SHRPX_OPTID_CLIENT_CERT_FILE; + } + if (util::strieq_l("private-key-fil", name, 15)) { + return SHRPX_OPTID_PRIVATE_KEY_FILE; + } + if (util::strieq_l("worker-read-rat", name, 15)) { + return SHRPX_OPTID_WORKER_READ_RATE; + } + break; + case 'g': + if (util::strieq_l("accesslog-syslo", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_SYSLOG; + } + break; + case 't': + if (util::strieq_l("accesslog-forma", name, 15)) { + return SHRPX_OPTID_ACCESSLOG_FORMAT; + } + break; + } + break; + case 17: + switch (name[16]) { + case 'e': + if (util::strieq_l("no-server-rewrit", name, 16)) { + return SHRPX_OPTID_NO_SERVER_REWRITE; + } + if (util::strieq_l("worker-write-rat", name, 16)) { + return SHRPX_OPTID_WORKER_WRITE_RATE; + } + break; + case 's': + if (util::strieq_l("backend-http1-tl", name, 16)) { + return SHRPX_OPTID_BACKEND_HTTP1_TLS; + } + if (util::strieq_l("max-header-field", name, 16)) { + return SHRPX_OPTID_MAX_HEADER_FIELDS; + } + break; + case 't': + if (util::strieq_l("dns-cache-timeou", name, 16)) { + return SHRPX_OPTID_DNS_CACHE_TIMEOUT; + } + if (util::strieq_l("worker-read-burs", name, 16)) { + return SHRPX_OPTID_WORKER_READ_BURST; + } + break; + } + break; + case 18: + switch (name[17]) { + case 'a': + if (util::strieq_l("tls-max-early-dat", name, 17)) { + return SHRPX_OPTID_TLS_MAX_EARLY_DATA; + } + break; + case 'r': + if (util::strieq_l("add-request-heade", name, 17)) { + return SHRPX_OPTID_ADD_REQUEST_HEADER; + } + break; + case 's': + if (util::strieq_l("client-psk-secret", name, 17)) { + return SHRPX_OPTID_CLIENT_PSK_SECRETS; + } + break; + case 't': + if (util::strieq_l("dns-lookup-timeou", name, 17)) { + return SHRPX_OPTID_DNS_LOOKUP_TIMEOUT; + } + if (util::strieq_l("worker-write-burs", name, 17)) { + return SHRPX_OPTID_WORKER_WRITE_BURST; + } + break; + } + break; + case 19: + switch (name[18]) { + case 'e': + if (util::strieq_l("no-location-rewrit", name, 18)) { + return SHRPX_OPTID_NO_LOCATION_REWRITE; + } + if (util::strieq_l("require-http-schem", name, 18)) { + return SHRPX_OPTID_REQUIRE_HTTP_SCHEME; + } + if (util::strieq_l("tls-ticket-key-fil", name, 18)) { + return SHRPX_OPTID_TLS_TICKET_KEY_FILE; + } + break; + case 'f': + if (util::strieq_l("backend-max-backof", name, 18)) { + return SHRPX_OPTID_BACKEND_MAX_BACKOFF; + } + break; + case 'r': + if (util::strieq_l("add-response-heade", name, 18)) { + return SHRPX_OPTID_ADD_RESPONSE_HEADER; + } + if (util::strieq_l("add-x-forwarded-fo", name, 18)) { + return SHRPX_OPTID_ADD_X_FORWARDED_FOR; + } + if (util::strieq_l("header-field-buffe", name, 18)) { + return SHRPX_OPTID_HEADER_FIELD_BUFFER; + } + break; + case 't': + if (util::strieq_l("redirect-https-por", name, 18)) { + return SHRPX_OPTID_REDIRECT_HTTPS_PORT; + } + if (util::strieq_l("stream-read-timeou", name, 18)) { + return SHRPX_OPTID_STREAM_READ_TIMEOUT; + } + break; + } + break; + case 20: + switch (name[19]) { + case 'g': + if (util::strieq_l("frontend-frame-debu", name, 19)) { + return SHRPX_OPTID_FRONTEND_FRAME_DEBUG; + } + break; + case 'l': + if (util::strieq_l("ocsp-update-interva", name, 19)) { + return SHRPX_OPTID_OCSP_UPDATE_INTERVAL; + } + 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; + } + break; + case 't': + if (util::strieq_l("backend-read-timeou", name, 19)) { + return SHRPX_OPTID_BACKEND_READ_TIMEOUT; + } + if (util::strieq_l("stream-write-timeou", name, 19)) { + return SHRPX_OPTID_STREAM_WRITE_TIMEOUT; + } + if (util::strieq_l("verify-client-cacer", name, 19)) { + return SHRPX_OPTID_VERIFY_CLIENT_CACERT; + } + break; + case 'y': + if (util::strieq_l("api-max-request-bod", name, 19)) { + return SHRPX_OPTID_API_MAX_REQUEST_BODY; + } + break; + } + break; + case 21: + switch (name[20]) { + case 'd': + if (util::strieq_l("backend-tls-sni-fiel", name, 20)) { + 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; + } + break; + case 'n': + if (util::strieq_l("tls-max-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MAX_PROTO_VERSION; + } + if (util::strieq_l("tls-min-proto-versio", name, 20)) { + return SHRPX_OPTID_TLS_MIN_PROTO_VERSION; + } + break; + case 'r': + if (util::strieq_l("tls-ticket-key-ciphe", name, 20)) { + return SHRPX_OPTID_TLS_TICKET_KEY_CIPHER; + } + break; + case 's': + if (util::strieq_l("frontend-max-request", name, 20)) { + return SHRPX_OPTID_FRONTEND_MAX_REQUESTS; + } + break; + case 't': + if (util::strieq_l("backend-write-timeou", name, 20)) { + return SHRPX_OPTID_BACKEND_WRITE_TIMEOUT; + } + if (util::strieq_l("frontend-read-timeou", name, 20)) { + return SHRPX_OPTID_FRONTEND_READ_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("accesslog-write-earl", name, 20)) { + return SHRPX_OPTID_ACCESSLOG_WRITE_EARLY; + } + break; + } + break; + case 22: + switch (name[21]) { + case 'i': + if (util::strieq_l("backend-http-proxy-ur", name, 21)) { + return SHRPX_OPTID_BACKEND_HTTP_PROXY_URI; + } + break; + case 'r': + 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)) { + return SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT; + } + break; + case 'y': + if (util::strieq_l("backend-address-famil", name, 21)) { + return SHRPX_OPTID_BACKEND_ADDRESS_FAMILY; + } + break; + } + break; + case 23: + switch (name[22]) { + case 'e': + if (util::strieq_l("client-private-key-fil", name, 22)) { + return SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE; + } + if (util::strieq_l("private-key-passwd-fil", name, 22)) { + 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; + } + break; + case 't': + if (util::strieq_l("backend-connect-timeou", name, 22)) { + return SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT; + } + break; + } + 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; + } + if (util::strieq_l("tls-ticket-key-memcache", name, 23)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED; + } + break; + case 'e': + if (util::strieq_l("fetch-ocsp-response-fil", name, 23)) { + return SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE; + } + break; + case 'o': + if (util::strieq_l("no-add-x-forwarded-prot", name, 23)) { + return SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO; + } + break; + case 't': + if (util::strieq_l("listener-disable-timeou", name, 23)) { + return SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT; + } + if (util::strieq_l("tls-dyn-rec-idle-timeou", name, 23)) { + return SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT; + } + break; + } + break; + case 25: + switch (name[24]) { + case 'e': + 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)) { + return SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING; + } + break; + case 's': + if (util::strieq_l("backend-http2-window-bit", name, 24)) { + return SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-request-header-field", name, 24)) { + 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: + switch (name[25]) { + case 'a': + if (util::strieq_l("tls-no-postpone-early-dat", name, 25)) { + return SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA; + } + break; + case 'e': + 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)) { + return SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS; + } + if (util::strieq_l("max-response-header-field", name, 25)) { + return SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS; + } + break; + case 't': + 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; + case 27: + switch (name[26]) { + case 'd': + if (util::strieq_l("tls-session-cache-memcache", name, 26)) { + 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; + } + break; + case 's': + if (util::strieq_l("worker-frontend-connection", name, 26)) { + return SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS; + } + break; + case 't': + 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; + } + break; + } + break; + case 28: + switch (name[27]) { + case 'a': + if (util::strieq_l("no-strip-incoming-early-dat", name, 27)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA; + } + break; + case 'd': + if (util::strieq_l("tls-dyn-rec-warmup-threshol", name, 27)) { + return SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD; + } + break; + case 'r': + if (util::strieq_l("response-header-field-buffe", name, 27)) { + return SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER; + } + break; + case 's': + if (util::strieq_l("http2-max-concurrent-stream", name, 27)) { + return SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS; + } + if (util::strieq_l("tls-ticket-key-memcached-tl", name, 27)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("backend-connections-per-hos", name, 27)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST; + } + break; + } + break; + case 30: + switch (name[29]) { + case 'd': + if (util::strieq_l("verify-client-tolerate-expire", name, 29)) { + 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; + } + if (util::strieq_l("strip-incoming-x-forwarded-fo", name, 29)) { + return SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR; + } + break; + case 't': + if (util::strieq_l("backend-http2-settings-timeou", name, 29)) { + return SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 31: + switch (name[30]) { + case 's': + if (util::strieq_l("tls-session-cache-memcached-tl", name, 30)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS; + } + break; + case 't': + if (util::strieq_l("frontend-http2-settings-timeou", name, 30)) { + return SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT; + } + break; + } + break; + case 32: + switch (name[31]) { + case 'd': + if (util::strieq_l("backend-connections-per-fronten", name, 31)) { + return SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 33: + switch (name[32]) { + case 'l': + if (util::strieq_l("tls-ticket-key-memcached-interva", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL; + } + if (util::strieq_l("tls-ticket-key-memcached-max-fai", name, 32)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL; + } + break; + case 't': + 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; + case 34: + switch (name[33]) { + case 'e': + if (util::strieq_l("tls-ticket-key-memcached-cert-fil", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE; + } + break; + case 'r': + if (util::strieq_l("frontend-http2-dump-request-heade", name, 33)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER; + } + break; + case 't': + if (util::strieq_l("backend-http1-connections-per-hos", name, 33)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST; + } + break; + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-max-retr", name, 33)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY; + } + break; + } + break; + case 35: + switch (name[34]) { + case 'e': + if (util::strieq_l("frontend-http2-optimize-window-siz", name, 34)) { + return SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE; + } + break; + case 'o': + if (util::strieq_l("no-strip-incoming-x-forwarded-prot", name, 34)) { + return SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO; + } + break; + case 'r': + 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; + } + break; + case 'r': + if (util::strieq_l("backend-http2-connections-per-worke", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER; + } + break; + case 's': + if (util::strieq_l("backend-http2-connection-window-bit", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS; + } + if (util::strieq_l("backend-http2-max-concurrent-stream", name, 35)) { + return SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS; + } + break; + } + break; + case 37: + switch (name[36]) { + case 'e': + 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; + } + break; + case 's': + if (util::strieq_l("frontend-http2-connection-window-bit", name, 36)) { + return SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS; + } + 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; + case 38: + switch (name[37]) { + case 'd': + if (util::strieq_l("backend-http1-connections-per-fronten", name, 37)) { + return SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND; + } + break; + } + break; + case 39: + switch (name[38]) { + case 'y': + if (util::strieq_l("tls-ticket-key-memcached-address-famil", name, 38)) { + return SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 40: + switch (name[39]) { + case 'e': + if (util::strieq_l("backend-http2-decoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("backend-http2-encoder-dynamic-table-siz", name, 39)) { + return SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + break; + } + break; + case 41: + switch (name[40]) { + case 'e': + if (util::strieq_l("frontend-http2-decoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-encoder-dynamic-table-siz", name, + 40)) { + return SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE; + } + if (util::strieq_l("frontend-http2-optimize-write-buffer-siz", name, + 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; + } + break; + } + break; + case 42: + switch (name[41]) { + case 'y': + if (util::strieq_l("tls-session-cache-memcached-address-famil", name, + 41)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY; + } + break; + } + break; + case 44: + switch (name[43]) { + case 'e': + if (util::strieq_l("tls-session-cache-memcached-private-key-fil", name, + 43)) { + return SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE; + } + break; + } + break; + } + return -1; +} + +int parse_config(Config *config, const StringRef &opt, const StringRef &optarg, + std::set<StringRef> &included_set, + std::map<StringRef, size_t> &pattern_addr_indexer) { + auto optid = option_lookup_token(opt.c_str(), opt.size()); + return parse_config(config, optid, opt, optarg, included_set, + pattern_addr_indexer); +} + +int parse_config(Config *config, int optid, const StringRef &opt, + const StringRef &optarg, std::set<StringRef> &included_set, + std::map<StringRef, size_t> &pattern_addr_indexer) { + std::array<char, STRERROR_BUFSIZE> errbuf; + char host[NI_MAXHOST]; + uint16_t port; + + switch (optid) { + case SHRPX_OPTID_BACKEND: { + auto &downstreamconf = *config->conn.downstream; + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + + DownstreamAddrConfig addr{}; + if (util::istarts_with(optarg, SHRPX_UNIX_PATH_PREFIX)) { + auto path = std::begin(optarg) + SHRPX_UNIX_PATH_PREFIX.size(); + addr.host = + make_string_ref(downstreamconf.balloc, StringRef{path, addr_end}); + addr.host_unix = true; + } else { + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + addr.host = make_string_ref(downstreamconf.balloc, StringRef{host}); + addr.port = port; + } + + auto mapping = addr_end == std::end(optarg) ? addr_end : addr_end + 1; + auto mapping_end = std::find(mapping, std::end(optarg), ';'); + + auto params = + mapping_end == std::end(optarg) ? mapping_end : mapping_end + 1; + + if (parse_mapping(config, addr, pattern_addr_indexer, + StringRef{mapping, mapping_end}, + StringRef{params, std::end(optarg)}) != 0) { + return -1; + } + + return 0; + } + case SHRPX_OPTID_FRONTEND: { + auto &apiconf = config->api; + + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{addr_end, std::end(optarg)}; + + UpstreamParams params{}; + params.tls = true; + + if (parse_upstream_params(params, src_params) != 0) { + return -1; + } + + if (params.sni_fwd && !params.tls) { + LOG(ERROR) << "frontend: sni_fwd requires tls"; + 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(); + + addrs.push_back(std::move(addr)); + + return 0; + } + + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + addr.host = make_string_ref(config->balloc, StringRef{host}); + addr.port = port; + + if (util::numeric_host(host, AF_INET)) { + addr.family = AF_INET; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + return 0; + } + + if (util::numeric_host(host, AF_INET6)) { + addr.family = AF_INET6; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + return 0; + } + + addr.family = AF_INET; + addr.index = addrs.size(); + addrs.push_back(addr); + + addr.family = AF_INET6; + addr.index = addrs.size(); + addrs.push_back(std::move(addr)); + + return 0; + } + case SHRPX_OPTID_WORKERS: +#ifdef NOTHREADS + LOG(WARN) << "Threading disabled at build time, no threads created."; + return 0; +#else // !NOTHREADS + return parse_uint(&config->num_worker, opt, optarg); +#endif // !NOTHREADS + case SHRPX_OPTID_HTTP2_MAX_CONCURRENT_STREAMS: { + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS << " and " + << SHRPX_OPT_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS << " instead."; + size_t n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + auto &http2conf = config->http2; + http2conf.upstream.max_concurrent_streams = n; + http2conf.downstream.max_concurrent_streams = n; + + return 0; + } + case SHRPX_OPTID_LOG_LEVEL: { + auto level = Log::get_severity_level_by_name(optarg); + if (level == -1) { + LOG(ERROR) << opt << ": Invalid severity level: " << optarg; + return -1; + } + config->logging.severity = level; + + return 0; + } + case SHRPX_OPTID_DAEMON: + config->daemon = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HTTP2_PROXY: + config->http2_proxy = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HTTP2_BRIDGE: + LOG(ERROR) << opt + << ": deprecated. Use backend=<addr>,<port>;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_CLIENT_PROXY: + LOG(ERROR) + << opt + << ": deprecated. Use http2-proxy, frontend=<addr>,<port>;no-tls " + "and backend=<addr>,<port>;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_ADD_X_FORWARDED_FOR: + config->http.xff.add = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_STRIP_INCOMING_X_FORWARDED_FOR: + config->http.xff.strip_incoming = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_VIA: + config->http.no_via = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_READ_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.http2_read, opt, + optarg); + case SHRPX_OPTID_FRONTEND_READ_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.read, opt, optarg); + case SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.write, opt, optarg); + case SHRPX_OPTID_BACKEND_READ_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.read, opt, optarg); + case SHRPX_OPTID_BACKEND_WRITE_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.write, opt, optarg); + case SHRPX_OPTID_BACKEND_CONNECT_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.connect, opt, + optarg); + case SHRPX_OPTID_STREAM_READ_TIMEOUT: + return parse_duration(&config->http2.timeout.stream_read, opt, optarg); + case SHRPX_OPTID_STREAM_WRITE_TIMEOUT: + return parse_duration(&config->http2.timeout.stream_write, opt, optarg); + case SHRPX_OPTID_ACCESSLOG_FILE: + config->logging.access.file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_SYSLOG: + config->logging.access.syslog = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_FORMAT: + config->logging.access.format = parse_log_format(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ERRORLOG_FILE: + config->logging.error.file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ERRORLOG_SYSLOG: + config->logging.error.syslog = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FASTOPEN: + return parse_uint(&config->conn.listener.fastopen, opt, optarg); + case SHRPX_OPTID_BACKEND_KEEP_ALIVE_TIMEOUT: + return parse_duration(&config->conn.downstream->timeout.idle_read, opt, + optarg); + case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS: + case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_BITS: { + LOG(WARN) << opt << ": deprecated. Use " + << (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS + ? SHRPX_OPT_FRONTEND_HTTP2_WINDOW_SIZE + : SHRPX_OPT_BACKEND_HTTP2_WINDOW_SIZE); + int32_t *resp; + + if (optid == SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_BITS) { + resp = &config->http2.upstream.window_size; + } else { + resp = &config->http2.downstream.window_size; + } + + errno = 0; + + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n >= 31) { + LOG(ERROR) << opt + << ": specify the integer in the range [0, 30], inclusive"; + return -1; + } + + // Make 16 bits to the HTTP/2 default 64KiB - 1. This is the same + // behaviour of previous code. + *resp = (1 << n) - 1; + + return 0; + } + case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS: + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_BITS: { + LOG(WARN) << opt << ": deprecated. Use " + << (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS + ? SHRPX_OPT_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE + : SHRPX_OPT_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE); + int32_t *resp; + + if (optid == SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_BITS) { + resp = &config->http2.upstream.connection_window_size; + } else { + resp = &config->http2.downstream.connection_window_size; + } + + errno = 0; + + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n < 16 || n >= 31) { + LOG(ERROR) << opt + << ": specify the integer in the range [16, 30], inclusive"; + return -1; + } + + *resp = (1 << n) - 1; + + return 0; + } + case SHRPX_OPTID_FRONTEND_NO_TLS: + LOG(WARN) << opt << ": deprecated. Use no-tls keyword in " + << SHRPX_OPT_FRONTEND; + return 0; + case SHRPX_OPTID_BACKEND_NO_TLS: + LOG(WARN) << opt + << ": deprecated. backend connection is not encrypted by " + "default. See also " + << SHRPX_OPT_BACKEND_TLS; + return 0; + case SHRPX_OPTID_BACKEND_TLS_SNI_FIELD: + LOG(WARN) << opt + << ": deprecated. Use sni keyword in --backend option. " + "For now, all sni values of all backends are " + "overridden by the given value " + << optarg; + config->tls.backend_sni_name = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_PID_FILE: + config->pid_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_USER: { + auto pwd = getpwnam(optarg.c_str()); + if (!pwd) { + LOG(ERROR) << opt << ": failed to get uid from " << optarg << ": " + << xsi_strerror(errno, errbuf.data(), errbuf.size()); + return -1; + } + config->user = make_string_ref(config->balloc, StringRef{pwd->pw_name}); + config->uid = pwd->pw_uid; + config->gid = pwd->pw_gid; + + return 0; + } + case SHRPX_OPTID_PRIVATE_KEY_FILE: + config->tls.private_key_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_PRIVATE_KEY_PASSWD_FILE: { + auto passwd = read_passwd_from_file(opt, optarg); + if (passwd.empty()) { + LOG(ERROR) << opt << ": Couldn't read key file's passwd from " << optarg; + return -1; + } + config->tls.private_key_passwd = + make_string_ref(config->balloc, StringRef{passwd}); + + return 0; + } + case SHRPX_OPTID_CERTIFICATE_FILE: + config->tls.cert_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_DH_PARAM_FILE: + config->tls.dh_param_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_SUBCERT: { + auto end_keys = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{end_keys, std::end(optarg)}; + + SubcertParams params; + if (parse_subcert_params(params, src_params) != 0) { + return -1; + } + + std::vector<uint8_t> sct_data; + + if (!params.sct_dir.empty()) { + // Make sure that dir_path is NULL terminated string. + if (read_tls_sct_from_dir(sct_data, opt, + StringRef{params.sct_dir.str()}) != 0) { + return -1; + } + } + + // Private Key file and certificate file separated by ':'. + auto sp = std::find(std::begin(optarg), end_keys, ':'); + if (sp == end_keys) { + LOG(ERROR) << opt << ": missing ':' in " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + auto private_key_file = StringRef{std::begin(optarg), sp}; + + if (private_key_file.empty()) { + LOG(ERROR) << opt << ": missing private key file: " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + auto cert_file = StringRef{sp + 1, end_keys}; + + if (cert_file.empty()) { + LOG(ERROR) << opt << ": missing certificate file: " + << StringRef{std::begin(optarg), end_keys}; + return -1; + } + + config->tls.subcerts.emplace_back( + make_string_ref(config->balloc, private_key_file), + make_string_ref(config->balloc, cert_file), std::move(sct_data)); + + return 0; + } + case SHRPX_OPTID_SYSLOG_FACILITY: { + int facility = int_syslog_facility(optarg); + if (facility == -1) { + LOG(ERROR) << opt << ": Unknown syslog facility: " << optarg; + return -1; + } + config->logging.syslog_facility = facility; + + return 0; + } + case SHRPX_OPTID_BACKLOG: + return parse_uint(&config->conn.listener.backlog, opt, optarg); + case SHRPX_OPTID_CIPHERS: + config->tls.ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS13_CIPHERS: + config->tls.tls13_ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT: + LOG(ERROR) << opt + << ": deprecated. Use frontend=<addr>,<port>;no-tls, " + "backend=<addr>,<port>;;proto=h2;tls"; + return -1; + case SHRPX_OPTID_INSECURE: + config->tls.insecure = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_CACERT: + config->tls.cacert = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_BACKEND_IPV4: + LOG(WARN) << opt + << ": deprecated. Use backend-address-family=IPv4 instead."; + + config->conn.downstream->family = AF_INET; + + return 0; + case SHRPX_OPTID_BACKEND_IPV6: + LOG(WARN) << opt + << ": deprecated. Use backend-address-family=IPv6 instead."; + + config->conn.downstream->family = AF_INET6; + + return 0; + case SHRPX_OPTID_BACKEND_HTTP_PROXY_URI: { + auto &proxy = config->downstream_http_proxy; + // Reset here so that multiple option occurrence does not merge + // the results. + proxy = {}; + // parse URI and get hostname, port and optionally userinfo. + http_parser_url u{}; + int rv = http_parser_parse_url(optarg.c_str(), optarg.size(), 0, &u); + if (rv == 0) { + if (u.field_set & UF_USERINFO) { + auto uf = util::get_uri_field(optarg.c_str(), u, UF_USERINFO); + // Surprisingly, u.field_set & UF_USERINFO is nonzero even if + // userinfo component is empty string. + if (!uf.empty()) { + proxy.userinfo = util::percent_decode(config->balloc, uf); + } + } + if (u.field_set & UF_HOST) { + proxy.host = make_string_ref( + config->balloc, util::get_uri_field(optarg.c_str(), u, UF_HOST)); + } else { + LOG(ERROR) << opt << ": no hostname specified"; + return -1; + } + if (u.field_set & UF_PORT) { + proxy.port = u.port; + } else { + LOG(ERROR) << opt << ": no port specified"; + return -1; + } + } else { + LOG(ERROR) << opt << ": parse error"; + return -1; + } + + return 0; + } + case SHRPX_OPTID_READ_RATE: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.rate, opt, + optarg); + case SHRPX_OPTID_READ_BURST: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.read.burst, + opt, optarg); + case SHRPX_OPTID_WRITE_RATE: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.rate, + opt, optarg); + case SHRPX_OPTID_WRITE_BURST: + return parse_uint_with_unit(&config->conn.upstream.ratelimit.write.burst, + opt, optarg); + case SHRPX_OPTID_WORKER_READ_RATE: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_READ_BURST: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_WRITE_RATE: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_WORKER_WRITE_BURST: + LOG(WARN) << opt << ": not implemented yet"; + return 0; + case SHRPX_OPTID_TLS_PROTO_LIST: { + LOG(WARN) << opt + << ": deprecated. Use tls-min-proto-version and " + "tls-max-proto-version instead."; + auto list = util::split_str(optarg, ','); + config->tls.tls_proto_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.tls_proto_list[i] = make_string_ref(config->balloc, list[i]); + } + + return 0; + } + case SHRPX_OPTID_VERIFY_CLIENT: + config->tls.client_verify.enabled = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_VERIFY_CLIENT_CACERT: + config->tls.client_verify.cacert = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT_PRIVATE_KEY_FILE: + config->tls.client.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_CLIENT_CERT_FILE: + config->tls.client.cert_file = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_REQUEST_HEADER: + config->http2.upstream.debug.dump.request_header_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER: + config->http2.upstream.debug.dump.response_header_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_HTTP2_NO_COOKIE_CRUMBLING: + config->http2.no_cookie_crumbling = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_FRAME_DEBUG: + config->http2.upstream.debug.frame_debug = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_PADDING: + return parse_uint(&config->padding, opt, optarg); + case SHRPX_OPTID_ALTSVC: { + AltSvc altsvc{}; + + if (parse_altsvc(altsvc, opt, optarg) != 0) { + return -1; + } + + config->http.altsvcs.push_back(std::move(altsvc)); + + return 0; + } + case SHRPX_OPTID_ADD_REQUEST_HEADER: + case SHRPX_OPTID_ADD_RESPONSE_HEADER: { + auto p = parse_header(config->balloc, optarg); + if (p.name.empty()) { + LOG(ERROR) << opt << ": invalid header field: " << optarg; + return -1; + } + if (optid == SHRPX_OPTID_ADD_REQUEST_HEADER) { + config->http.add_request_headers.push_back(std::move(p)); + } else { + config->http.add_response_headers.push_back(std::move(p)); + } + return 0; + } + case SHRPX_OPTID_WORKER_FRONTEND_CONNECTIONS: + return parse_uint(&config->conn.upstream.worker_connections, opt, optarg); + case SHRPX_OPTID_NO_LOCATION_REWRITE: + config->http.no_location_rewrite = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_HOST_REWRITE: + LOG(WARN) << SHRPX_OPT_NO_HOST_REWRITE + << ": deprecated. :authority and host header fields are NOT " + "altered by default. To rewrite these headers, use " + "--host-rewrite option."; + + return 0; + case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_HOST: + LOG(WARN) << opt + << ": deprecated. Use backend-connections-per-host instead."; + // fall through + case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_HOST: { + int n; + + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n == 0) { + LOG(ERROR) << opt << ": specify an integer strictly more than 0"; + + return -1; + } + + config->conn.downstream->connections_per_host = n; + + return 0; + } + case SHRPX_OPTID_BACKEND_HTTP1_CONNECTIONS_PER_FRONTEND: + LOG(WARN) << opt << ": deprecated. Use " + << SHRPX_OPT_BACKEND_CONNECTIONS_PER_FRONTEND << " instead."; + // fall through + case SHRPX_OPTID_BACKEND_CONNECTIONS_PER_FRONTEND: + return parse_uint(&config->conn.downstream->connections_per_frontend, opt, + optarg); + case SHRPX_OPTID_LISTENER_DISABLE_TIMEOUT: + return parse_duration(&config->conn.listener.timeout.sleep, opt, optarg); + case SHRPX_OPTID_TLS_TICKET_KEY_FILE: + config->tls.ticket.files.emplace_back( + make_string_ref(config->balloc, optarg)); + return 0; + case SHRPX_OPTID_RLIMIT_NOFILE: { + 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_nofile = n; + + return 0; + } + case SHRPX_OPTID_BACKEND_REQUEST_BUFFER: + case SHRPX_OPTID_BACKEND_RESPONSE_BUFFER: { + size_t n; + if (parse_uint_with_unit(&n, opt, optarg) != 0) { + return -1; + } + + if (n == 0) { + LOG(ERROR) << opt << ": specify an integer strictly more than 0"; + + return -1; + } + + if (optid == SHRPX_OPTID_BACKEND_REQUEST_BUFFER) { + config->conn.downstream->request_buffer_size = n; + } else { + config->conn.downstream->response_buffer_size = n; + } + + return 0; + } + + case SHRPX_OPTID_NO_SERVER_PUSH: + config->http2.no_server_push = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTIONS_PER_WORKER: + LOG(WARN) << opt << ": deprecated."; + return 0; + case SHRPX_OPTID_FETCH_OCSP_RESPONSE_FILE: + config->tls.ocsp.fetch_ocsp_response_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_OCSP_UPDATE_INTERVAL: + return parse_duration(&config->tls.ocsp.update_interval, opt, optarg); + case SHRPX_OPTID_NO_OCSP: + config->tls.ocsp.disabled = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_HEADER_FIELD_BUFFER: + LOG(WARN) << opt + << ": deprecated. Use request-header-field-buffer instead."; + // fall through + case SHRPX_OPTID_REQUEST_HEADER_FIELD_BUFFER: + return parse_uint_with_unit(&config->http.request_header_field_buffer, opt, + optarg); + case SHRPX_OPTID_MAX_HEADER_FIELDS: + LOG(WARN) << opt << ": deprecated. Use max-request-header-fields instead."; + // fall through + case SHRPX_OPTID_MAX_REQUEST_HEADER_FIELDS: + return parse_uint(&config->http.max_request_header_fields, opt, optarg); + case SHRPX_OPTID_RESPONSE_HEADER_FIELD_BUFFER: + return parse_uint_with_unit(&config->http.response_header_field_buffer, opt, + optarg); + case SHRPX_OPTID_MAX_RESPONSE_HEADER_FIELDS: + return parse_uint(&config->http.max_response_header_fields, opt, optarg); + case SHRPX_OPTID_INCLUDE: { + if (included_set.count(optarg)) { + LOG(ERROR) << opt << ": " << optarg << " has already been included"; + return -1; + } + + included_set.insert(optarg); + auto rv = + load_config(config, optarg.c_str(), included_set, pattern_addr_indexer); + included_set.erase(optarg); + + if (rv != 0) { + return -1; + } + + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_CIPHER: + if (util::strieq_l("aes-128-cbc", optarg)) { + config->tls.ticket.cipher = EVP_aes_128_cbc(); + } else if (util::strieq_l("aes-256-cbc", optarg)) { + config->tls.ticket.cipher = EVP_aes_256_cbc(); + } else { + LOG(ERROR) << opt + << ": unsupported cipher for ticket encryption: " << optarg; + return -1; + } + config->tls.ticket.cipher_given = true; + + return 0; + case SHRPX_OPTID_HOST_REWRITE: + config->http.no_host_rewrite = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: { + auto addr_end = std::find(std::begin(optarg), std::end(optarg), ';'); + auto src_params = StringRef{addr_end, std::end(optarg)}; + + MemcachedConnectionParams params{}; + if (parse_memcached_connection_params(params, src_params, StringRef{opt}) != + 0) { + return -1; + } + + if (split_host_port(host, sizeof(host), &port, + StringRef{std::begin(optarg), addr_end}, opt) == -1) { + return -1; + } + + switch (optid) { + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED: { + auto &memcachedconf = config->tls.session_cache.memcached; + memcachedconf.host = make_string_ref(config->balloc, StringRef{host}); + memcachedconf.port = port; + memcachedconf.tls = params.tls; + break; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED: { + auto &memcachedconf = config->tls.ticket.memcached; + memcachedconf.host = make_string_ref(config->balloc, StringRef{host}); + memcachedconf.port = port; + memcachedconf.tls = params.tls; + break; + } + }; + + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_INTERVAL: + return parse_duration(&config->tls.ticket.memcached.interval, opt, optarg); + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_RETRY: { + int n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n > 30) { + LOG(ERROR) << opt << ": must be smaller than or equal to 30"; + return -1; + } + + config->tls.ticket.memcached.max_retry = n; + return 0; + } + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_MAX_FAIL: + return parse_uint(&config->tls.ticket.memcached.max_fail, opt, optarg); + case SHRPX_OPTID_TLS_DYN_REC_WARMUP_THRESHOLD: { + size_t n; + if (parse_uint_with_unit(&n, opt, optarg) != 0) { + return -1; + } + + config->tls.dyn_rec.warmup_threshold = n; + + return 0; + } + + case SHRPX_OPTID_TLS_DYN_REC_IDLE_TIMEOUT: + return parse_duration(&config->tls.dyn_rec.idle_timeout, opt, optarg); + + case SHRPX_OPTID_MRUBY_FILE: +#ifdef HAVE_MRUBY + config->mruby_file = make_string_ref(config->balloc, optarg); +#else // !HAVE_MRUBY + LOG(WARN) << opt + << ": ignored because mruby support is disabled at build time."; +#endif // !HAVE_MRUBY + return 0; + case SHRPX_OPTID_ACCEPT_PROXY_PROTOCOL: + LOG(WARN) << opt << ": deprecated. Use proxyproto keyword in " + << SHRPX_OPT_FRONTEND << " instead."; + config->conn.upstream.accept_proxy_protocol = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_ADD_FORWARDED: { + auto &fwdconf = config->http.forwarded; + fwdconf.params = FORWARDED_NONE; + for (const auto ¶m : util::split_str(optarg, ',')) { + if (util::strieq_l("by", param)) { + fwdconf.params |= FORWARDED_BY; + continue; + } + if (util::strieq_l("for", param)) { + fwdconf.params |= FORWARDED_FOR; + continue; + } + if (util::strieq_l("host", param)) { + fwdconf.params |= FORWARDED_HOST; + continue; + } + if (util::strieq_l("proto", param)) { + fwdconf.params |= FORWARDED_PROTO; + continue; + } + + LOG(ERROR) << opt << ": unknown parameter " << optarg; + + return -1; + } + + return 0; + } + case SHRPX_OPTID_STRIP_INCOMING_FORWARDED: + config->http.forwarded.strip_incoming = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FORWARDED_BY: + case SHRPX_OPTID_FORWARDED_FOR: { + auto type = parse_forwarded_node_type(optarg); + + if (type == static_cast<ForwardedNode>(-1) || + (optid == SHRPX_OPTID_FORWARDED_FOR && optarg[0] == '_')) { + LOG(ERROR) << opt << ": unknown node type or illegal obfuscated string " + << optarg; + return -1; + } + + auto &fwdconf = config->http.forwarded; + + switch (optid) { + case SHRPX_OPTID_FORWARDED_BY: + fwdconf.by_node_type = type; + if (optarg[0] == '_') { + fwdconf.by_obfuscated = make_string_ref(config->balloc, optarg); + } else { + fwdconf.by_obfuscated = StringRef::from_lit(""); + } + break; + case SHRPX_OPTID_FORWARDED_FOR: + fwdconf.for_node_type = type; + break; + } + + return 0; + } + case SHRPX_OPTID_NO_HTTP2_CIPHER_BLACK_LIST: + 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: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_BACKEND << " instead."; + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_TLS: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_TLS_SESSION_CACHE_MEMCACHED; + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_CERT_FILE: + config->tls.session_cache.memcached.cert_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_PRIVATE_KEY_FILE: + config->tls.session_cache.memcached.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_TLS: + LOG(WARN) << opt << ": deprecated. Use tls keyword in " + << SHRPX_OPT_TLS_TICKET_KEY_MEMCACHED; + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_CERT_FILE: + config->tls.ticket.memcached.cert_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_PRIVATE_KEY_FILE: + config->tls.ticket.memcached.private_key_file = + make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS_TICKET_KEY_MEMCACHED_ADDRESS_FAMILY: + return parse_address_family(&config->tls.ticket.memcached.family, opt, + optarg); + case SHRPX_OPTID_TLS_SESSION_CACHE_MEMCACHED_ADDRESS_FAMILY: + return parse_address_family(&config->tls.session_cache.memcached.family, + opt, optarg); + case SHRPX_OPTID_BACKEND_ADDRESS_FAMILY: + return parse_address_family(&config->conn.downstream->family, opt, optarg); + case SHRPX_OPTID_FRONTEND_HTTP2_MAX_CONCURRENT_STREAMS: + return parse_uint(&config->http2.upstream.max_concurrent_streams, opt, + optarg); + case SHRPX_OPTID_BACKEND_HTTP2_MAX_CONCURRENT_STREAMS: + return parse_uint(&config->http2.downstream.max_concurrent_streams, opt, + optarg); + case SHRPX_OPTID_ERROR_PAGE: + return parse_error_page(config->http.error_pages, opt, optarg); + case SHRPX_OPTID_NO_KQUEUE: + if ((ev_supported_backends() & EVBACKEND_KQUEUE) == 0) { + LOG(WARN) << opt << ": kqueue is not supported on this platform"; + return 0; + } + + config->ev_loop_flags = ev_recommended_backends() & ~EVBACKEND_KQUEUE; + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_SETTINGS_TIMEOUT: + return parse_duration(&config->http2.upstream.timeout.settings, opt, + optarg); + case SHRPX_OPTID_BACKEND_HTTP2_SETTINGS_TIMEOUT: + return parse_duration(&config->http2.downstream.timeout.settings, opt, + optarg); + case SHRPX_OPTID_API_MAX_REQUEST_BODY: + return parse_uint_with_unit(&config->api.max_request_body, opt, optarg); + case SHRPX_OPTID_BACKEND_MAX_BACKOFF: + return parse_duration(&config->conn.downstream->timeout.max_backoff, opt, + optarg); + case SHRPX_OPTID_SERVER_NAME: + config->http.server_name = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_NO_SERVER_REWRITE: + config->http.no_server_rewrite = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WRITE_BUFFER_SIZE: + config->http2.upstream.optimize_write_buffer_size = + util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_OPTIMIZE_WINDOW_SIZE: + config->http2.upstream.optimize_window_size = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.window_size, opt, + optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_CONNECTION_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.downstream.window_size, opt, + optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_CONNECTION_WINDOW_SIZE: + if (parse_uint_with_unit(&config->http2.downstream.connection_window_size, + opt, optarg) != 0) { + return -1; + } + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit(&config->http2.upstream.encoder_dynamic_table_size, + opt, optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.option, + config->http2.upstream.encoder_dynamic_table_size); + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.upstream.alt_mode_option, + config->http2.upstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_FRONTEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.upstream.decoder_dynamic_table_size, opt, optarg); + case SHRPX_OPTID_BACKEND_HTTP2_ENCODER_DYNAMIC_TABLE_SIZE: + if (parse_uint_with_unit( + &config->http2.downstream.encoder_dynamic_table_size, opt, + optarg) != 0) { + return -1; + } + + nghttp2_option_set_max_deflate_dynamic_table_size( + config->http2.downstream.option, + config->http2.downstream.encoder_dynamic_table_size); + + return 0; + case SHRPX_OPTID_BACKEND_HTTP2_DECODER_DYNAMIC_TABLE_SIZE: + return parse_uint_with_unit( + &config->http2.downstream.decoder_dynamic_table_size, opt, optarg); + case SHRPX_OPTID_ECDH_CURVES: + config->tls.ecdh_curves = make_string_ref(config->balloc, optarg); + return 0; + case SHRPX_OPTID_TLS_SCT_DIR: +#if defined(NGHTTP2_GENUINE_OPENSSL) || defined(NGHTTP2_OPENSSL_IS_BORINGSSL) + return read_tls_sct_from_dir(config->tls.sct_data, opt, optarg); +#else // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support SCT"; + return 0; +#endif // !NGHTTP2_GENUINE_OPENSSL && !NGHTTP2_OPENSSL_IS_BORINGSSL + case SHRPX_OPTID_DNS_CACHE_TIMEOUT: + return parse_duration(&config->dns.timeout.cache, opt, optarg); + case SHRPX_OPTID_DNS_LOOKUP_TIMEOUT: + return parse_duration(&config->dns.timeout.lookup, opt, optarg); + case SHRPX_OPTID_DNS_MAX_TRY: { + int n; + if (parse_uint(&n, opt, optarg) != 0) { + return -1; + } + + if (n > 5) { + LOG(ERROR) << opt << ": must be smaller than or equal to 5"; + return -1; + } + + config->dns.max_try = n; + return 0; + } + case SHRPX_OPTID_FRONTEND_KEEP_ALIVE_TIMEOUT: + return parse_duration(&config->conn.upstream.timeout.idle_read, opt, + optarg); + case SHRPX_OPTID_PSK_SECRETS: +#ifndef OPENSSL_NO_PSK + return parse_psk_secrets(config, optarg); +#else // OPENSSL_NO_PSK + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // OPENSSL_NO_PSK + case SHRPX_OPTID_CLIENT_PSK_SECRETS: +#ifndef OPENSSL_NO_PSK + return parse_client_psk_secrets(config, optarg); +#else // OPENSSL_NO_PSK + LOG(WARN) + << opt + << ": ignored because underlying TLS library does not support PSK"; + return 0; +#endif // OPENSSL_NO_PSK + case SHRPX_OPTID_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; + case SHRPX_OPTID_CLIENT_CIPHERS: + config->tls.client.ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_TLS13_CLIENT_CIPHERS: + config->tls.client.tls13_ciphers = make_string_ref(config->balloc, optarg); + + return 0; + case SHRPX_OPTID_ACCESSLOG_WRITE_EARLY: + config->logging.access.write_early = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MIN_PROTO_VERSION: + return parse_tls_proto_version(config->tls.min_proto_version, opt, optarg); + case SHRPX_OPTID_TLS_MAX_PROTO_VERSION: + return parse_tls_proto_version(config->tls.max_proto_version, opt, optarg); + case SHRPX_OPTID_REDIRECT_HTTPS_PORT: { + auto n = util::parse_uint(optarg); + if (n == -1 || n < 0 || n > 65535) { + LOG(ERROR) << opt + << ": bad value. Specify an integer in the range [0, " + "65535], inclusive"; + return -1; + } + config->http.redirect_https_port = make_string_ref(config->balloc, optarg); + return 0; + } + case SHRPX_OPTID_FRONTEND_MAX_REQUESTS: + return parse_uint(&config->http.max_requests, opt, optarg); + case SHRPX_OPTID_SINGLE_THREAD: + config->single_thread = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_SINGLE_PROCESS: + config->single_process = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_ADD_X_FORWARDED_PROTO: + config->http.xfp.add = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_STRIP_INCOMING_X_FORWARDED_PROTO: + config->http.xfp.strip_incoming = !util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_OCSP_STARTUP: + config->tls.ocsp.startup = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_NO_VERIFY_OCSP: + config->tls.ocsp.no_verify = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_VERIFY_CLIENT_TOLERATE_EXPIRED: + config->tls.client_verify.tolerate_expired = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_IGNORE_PER_PATTERN_MRUBY_ERROR: + config->ignore_per_pattern_mruby_error = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_NO_POSTPONE_EARLY_DATA: + config->tls.no_postpone_early_data = util::strieq_l("yes", optarg); + + return 0; + case SHRPX_OPTID_TLS_MAX_EARLY_DATA: { + return parse_uint_with_unit(&config->tls.max_early_data, opt, optarg); + } + case SHRPX_OPTID_NO_STRIP_INCOMING_EARLY_DATA: + 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_REQUIRE_HTTP_SCHEME: + config->http.require_http_scheme = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_TLS_KTLS: + config->tls.ktls = util::strieq_l("yes", optarg); + return 0; + case SHRPX_OPTID_NPN_LIST: + LOG(WARN) << opt << ": deprecated. Use alpn-list instead."; + // fall through + case SHRPX_OPTID_ALPN_LIST: { + auto list = util::split_str(optarg, ','); + config->tls.alpn_list.resize(list.size()); + for (size_t i = 0; i < list.size(); ++i) { + config->tls.alpn_list[i] = make_string_ref(config->balloc, list[i]); + } + + return 0; + } + case SHRPX_OPTID_CONF: + LOG(WARN) << "conf: ignored"; + + return 0; + } + + LOG(ERROR) << "Unknown option: " << opt; + + return -1; +} + +int load_config(Config *config, const char *filename, + std::set<StringRef> &include_set, + std::map<StringRef, size_t> &pattern_addr_indexer) { + std::ifstream in(filename, std::ios::binary); + if (!in) { + LOG(ERROR) << "Could not open config file " << filename; + return -1; + } + std::string line; + int linenum = 0; + while (std::getline(in, line)) { + ++linenum; + if (line.empty() || line[0] == '#') { + continue; + } + auto eq = std::find(std::begin(line), std::end(line), '='); + if (eq == std::end(line)) { + LOG(ERROR) << "Bad configuration format in " << filename << " at line " + << linenum; + return -1; + } + *eq = '\0'; + + if (parse_config(config, StringRef{std::begin(line), eq}, + StringRef{eq + 1, std::end(line)}, include_set, + pattern_addr_indexer) != 0) { + return -1; + } + } + return 0; +} + +StringRef str_syslog_facility(int facility) { + switch (facility) { + case (LOG_AUTH): + return StringRef::from_lit("auth"); +#ifdef LOG_AUTHPRIV + case (LOG_AUTHPRIV): + return StringRef::from_lit("authpriv"); +#endif // LOG_AUTHPRIV + case (LOG_CRON): + return StringRef::from_lit("cron"); + case (LOG_DAEMON): + return StringRef::from_lit("daemon"); +#ifdef LOG_FTP + case (LOG_FTP): + return StringRef::from_lit("ftp"); +#endif // LOG_FTP + case (LOG_KERN): + return StringRef::from_lit("kern"); + case (LOG_LOCAL0): + return StringRef::from_lit("local0"); + case (LOG_LOCAL1): + return StringRef::from_lit("local1"); + case (LOG_LOCAL2): + return StringRef::from_lit("local2"); + case (LOG_LOCAL3): + return StringRef::from_lit("local3"); + case (LOG_LOCAL4): + return StringRef::from_lit("local4"); + case (LOG_LOCAL5): + return StringRef::from_lit("local5"); + case (LOG_LOCAL6): + return StringRef::from_lit("local6"); + case (LOG_LOCAL7): + return StringRef::from_lit("local7"); + case (LOG_LPR): + return StringRef::from_lit("lpr"); + case (LOG_MAIL): + return StringRef::from_lit("mail"); + case (LOG_SYSLOG): + return StringRef::from_lit("syslog"); + case (LOG_USER): + return StringRef::from_lit("user"); + case (LOG_UUCP): + return StringRef::from_lit("uucp"); + default: + return StringRef::from_lit("(unknown)"); + } +} + +int int_syslog_facility(const StringRef &strfacility) { + if (util::strieq_l("auth", strfacility)) { + return LOG_AUTH; + } + +#ifdef LOG_AUTHPRIV + if (util::strieq_l("authpriv", strfacility)) { + return LOG_AUTHPRIV; + } +#endif // LOG_AUTHPRIV + + if (util::strieq_l("cron", strfacility)) { + return LOG_CRON; + } + + if (util::strieq_l("daemon", strfacility)) { + return LOG_DAEMON; + } + +#ifdef LOG_FTP + if (util::strieq_l("ftp", strfacility)) { + return LOG_FTP; + } +#endif // LOG_FTP + + if (util::strieq_l("kern", strfacility)) { + return LOG_KERN; + } + + if (util::strieq_l("local0", strfacility)) { + return LOG_LOCAL0; + } + + if (util::strieq_l("local1", strfacility)) { + return LOG_LOCAL1; + } + + if (util::strieq_l("local2", strfacility)) { + return LOG_LOCAL2; + } + + if (util::strieq_l("local3", strfacility)) { + return LOG_LOCAL3; + } + + if (util::strieq_l("local4", strfacility)) { + return LOG_LOCAL4; + } + + if (util::strieq_l("local5", strfacility)) { + return LOG_LOCAL5; + } + + if (util::strieq_l("local6", strfacility)) { + return LOG_LOCAL6; + } + + if (util::strieq_l("local7", strfacility)) { + return LOG_LOCAL7; + } + + if (util::strieq_l("lpr", strfacility)) { + return LOG_LPR; + } + + if (util::strieq_l("mail", strfacility)) { + return LOG_MAIL; + } + + if (util::strieq_l("news", strfacility)) { + return LOG_NEWS; + } + + if (util::strieq_l("syslog", strfacility)) { + return LOG_SYSLOG; + } + + if (util::strieq_l("user", strfacility)) { + return LOG_USER; + } + + if (util::strieq_l("uucp", strfacility)) { + return LOG_UUCP; + } + + return -1; +} + +StringRef strproto(Proto proto) { + switch (proto) { + case Proto::NONE: + return StringRef::from_lit("none"); + case Proto::HTTP1: + 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"); + } + + // gcc needs this. + assert(0); + abort(); +} + +namespace { +// Consistent hashing method described in +// https://github.com/RJ/ketama. Generate 160 32-bit hashes per |s|, +// which is usually backend address. The each hash is associated to +// index of backend address. When all hashes for every backend +// address are calculated, sort it in ascending order of hash. To +// choose the index, compute 32-bit hash based on client IP address, +// and do lower bound search in the array. The returned index is the +// backend to use. +int compute_affinity_hash(std::vector<AffinityHash> &res, size_t idx, + const StringRef &s) { + int rv; + std::array<uint8_t, 32> buf; + + for (auto i = 0; i < 20; ++i) { + auto t = s.str(); + t += i; + + rv = util::sha256(buf.data(), StringRef{t}); + if (rv != 0) { + return -1; + } + + for (int i = 0; i < 8; ++i) { + auto h = (static_cast<uint32_t>(buf[4 * i]) << 24) | + (static_cast<uint32_t>(buf[4 * i + 1]) << 16) | + (static_cast<uint32_t>(buf[4 * i + 2]) << 8) | + static_cast<uint32_t>(buf[4 * i + 3]); + + res.emplace_back(idx, h); + } + } + + return 0; +} +} // namespace + +// Configures the following member in |config|: +// conn.downstream_router, conn.downstream.addr_groups, +// conn.downstream.addr_group_catch_all. +int configure_downstream_group(Config *config, bool http2_proxy, + bool numeric_addr_only, + const TLSConfig &tlsconf) { + int rv; + + auto &downstreamconf = *config->conn.downstream; + auto &addr_groups = downstreamconf.addr_groups; + auto &routerconf = downstreamconf.router; + auto &router = routerconf.router; + + if (addr_groups.empty()) { + DownstreamAddrConfig addr{}; + addr.host = StringRef::from_lit(DEFAULT_DOWNSTREAM_HOST); + addr.port = DEFAULT_DOWNSTREAM_PORT; + addr.proto = Proto::HTTP1; + addr.weight = 1; + addr.group_weight = 1; + + DownstreamAddrGroupConfig g(StringRef::from_lit("/")); + g.addrs.push_back(std::move(addr)); + router.add_route(g.pattern, addr_groups.size()); + addr_groups.push_back(std::move(g)); + } + + // backward compatibility: override all SNI fields with the option + // value --backend-tls-sni-field + if (!tlsconf.backend_sni_name.empty()) { + auto &sni = tlsconf.backend_sni_name; + for (auto &addr_group : addr_groups) { + for (auto &addr : addr_group.addrs) { + addr.sni = sni; + } + } + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolving backend address"; + } + + ssize_t catch_all_group = -1; + for (size_t i = 0; i < addr_groups.size(); ++i) { + auto &g = addr_groups[i]; + if (g.pattern == StringRef::from_lit("/")) { + catch_all_group = i; + } + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Host-path pattern: group " << i << ": '" << g.pattern + << "'"; + for (auto &addr : g.addrs) { + LOG(INFO) << "group " << i << " -> " << addr.host.c_str() + << (addr.host_unix ? "" : ":" + util::utos(addr.port)) + << ", proto=" << strproto(addr.proto) + << (addr.tls ? ", tls" : ""); + } + } +#ifdef HAVE_MRUBY + // Try compile mruby script and catch compile error early. + if (!g.mruby_file.empty()) { + if (mruby::create_mruby_context(g.mruby_file) == nullptr) { + LOG(config->ignore_per_pattern_mruby_error ? ERROR : FATAL) + << "backend: Could not compile mruby file for pattern " + << g.pattern; + if (!config->ignore_per_pattern_mruby_error) { + return -1; + } + g.mruby_file = StringRef{}; + } + } +#endif // HAVE_MRUBY + } + +#ifdef HAVE_MRUBY + // Try compile mruby script (--mruby-file) here to catch compile + // error early. + if (!config->mruby_file.empty()) { + if (mruby::create_mruby_context(config->mruby_file) == nullptr) { + LOG(FATAL) << "mruby-file: Could not compile mruby file"; + return -1; + } + } +#endif // HAVE_MRUBY + + if (catch_all_group == -1) { + LOG(FATAL) << "backend: No catch-all backend address is configured"; + return -1; + } + + downstreamconf.addr_group_catch_all = catch_all_group; + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Catch-all pattern is group " << catch_all_group; + } + + 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) { + if (addr.group_weight) { + auto it = wgchk.find(addr.group); + if (it == std::end(wgchk)) { + wgchk.emplace(addr.group, addr.group_weight); + } else if ((*it).second != addr.group_weight) { + LOG(FATAL) << "backend: inconsistent group-weight for a single group"; + return -1; + } + } + + if (addr.host_unix) { + // for AF_UNIX socket, we use "localhost" as host for backend + // hostport. This is used as Host header field to backend and + // not going to be passed to any syscalls. + addr.hostport = StringRef::from_lit("localhost"); + + auto path = addr.host.c_str(); + auto pathlen = addr.host.size(); + + if (pathlen + 1 > sizeof(addr.addr.su.un.sun_path)) { + LOG(FATAL) << "UNIX domain socket path " << path << " is too long > " + << sizeof(addr.addr.su.un.sun_path); + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Use UNIX domain socket path " << path + << " for backend connection"; + } + + addr.addr.su.un.sun_family = AF_UNIX; + // copy path including terminal NULL + std::copy_n(path, pathlen + 1, addr.addr.su.un.sun_path); + addr.addr.len = sizeof(addr.addr.su.un); + + continue; + } + + addr.hostport = + util::make_http_hostport(downstreamconf.balloc, addr.host, addr.port); + + auto hostport = + 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, + downstreamconf.family, resolve_flags) == -1) { + LOG(FATAL) << "Resolving backend address failed: " << hostport; + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Resolved backend address: " << hostport << " -> " + << util::to_numeric_addr(&addr.addr); + } + } else { + LOG(INFO) << "Resolving backend address " << hostport + << " takes place dynamically"; + } + } + + for (auto &addr : g.addrs) { + if (addr.group_weight == 0) { + auto it = wgchk.find(addr.group); + if (it == std::end(wgchk)) { + addr.group_weight = 1; + } else { + addr.group_weight = (*it).second; + } + } + } + + if (g.affinity.type != SessionAffinity::NONE) { + size_t idx = 0; + for (auto &addr : g.addrs) { + StringRef key; + if (addr.dns) { + if (addr.host_unix) { + key = addr.host; + } else { + key = addr.hostport; + } + } else { + auto p = reinterpret_cast<uint8_t *>(&addr.addr.su); + key = StringRef{p, addr.addr.len}; + } + rv = compute_affinity_hash(g.affinity_hash, idx, key); + if (rv != 0) { + return -1; + } + + if (g.affinity.cookie.stickiness == + SessionAffinityCookieStickiness::STRICT) { + addr.affinity_hash = util::hash32(key); + g.affinity_hash_map.emplace(addr.affinity_hash, idx); + } + + ++idx; + } + + std::sort(std::begin(g.affinity_hash), std::end(g.affinity_hash), + [](const AffinityHash &lhs, const AffinityHash &rhs) { + return lhs.hash < rhs.hash; + }); + } + + auto &timeout = g.timeout; + if (timeout.read < 1e-9) { + timeout.read = downstreamconf.timeout.read; + } + if (timeout.write < 1e-9) { + timeout.write = downstreamconf.timeout.write; + } + } + + return 0; +} + +int resolve_hostname(Address *addr, const char *hostname, uint16_t port, + int family, int additional_flags) { + int rv; + + auto service = util::utos(port); + + addrinfo hints{}; + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= additional_flags; +#ifdef AI_ADDRCONFIG + hints.ai_flags |= AI_ADDRCONFIG; +#endif // AI_ADDRCONFIG + addrinfo *res; + + rv = getaddrinfo(hostname, service.c_str(), &hints, &res); +#ifdef AI_ADDRCONFIG + if (rv != 0) { + // Retry without AI_ADDRCONFIG + hints.ai_flags &= ~AI_ADDRCONFIG; + rv = getaddrinfo(hostname, service.c_str(), &hints, &res); + } +#endif // AI_ADDRCONFIG + if (rv != 0) { + LOG(FATAL) << "Unable to resolve address for " << hostname << ": " + << gai_strerror(rv); + return -1; + } + + auto res_d = defer(freeaddrinfo, res); + + char host[NI_MAXHOST]; + rv = getnameinfo(res->ai_addr, res->ai_addrlen, host, sizeof(host), nullptr, + 0, NI_NUMERICHOST); + if (rv != 0) { + LOG(FATAL) << "Address resolution for " << hostname + << " failed: " << gai_strerror(rv); + + return -1; + } + + if (LOG_ENABLED(INFO)) { + LOG(INFO) << "Address resolution for " << hostname + << " succeeded: " << host; + } + + memcpy(&addr->su, res->ai_addr, res->ai_addrlen); + addr->len = res->ai_addrlen; + + return 0; +} + +} // namespace shrpx |