nghttpx: Add qlog support

This commit is contained in:
Tatsuhiro Tsujikawa 2021-09-06 20:06:38 +09:00
parent fbb228050a
commit addd614e94
8 changed files with 176 additions and 1 deletions

View File

@ -189,6 +189,7 @@ OPTIONS = [
"frontend-http3-max-connection-window-size", "frontend-http3-max-connection-window-size",
"frontend-http3-max-concurrent-streams", "frontend-http3-max-concurrent-streams",
"frontend-quic-early-data", "frontend-quic-early-data",
"frontend-quic-qlog-dir",
] ]
LOGVARS = [ LOGVARS = [

View File

@ -3216,6 +3216,12 @@ HTTP/3 and QUIC:
request is received in early data and handshake has not request is received in early data and handshake has not
finished. All backend servers should deal with possibly finished. All backend servers should deal with possibly
replayed requests. replayed requests.
--frontend-quic-qlog-dir=<DIR>
Specify a directory where a qlog file is written for
frontend QUIC connections. A qlog file is created per
each QUIC connection. The file name is ISO8601 basic
format, followed by "-", server Source Connection ID and
".qlog".
--no-quic-bpf --no-quic-bpf
Disable eBPF. Disable eBPF.
--frontend-http3-window-size=<SIZE> --frontend-http3-window-size=<SIZE>
@ -4003,6 +4009,8 @@ int main(int argc, char **argv) {
{SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(), {SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS.c_str(),
required_argument, &flag, 179}, required_argument, &flag, 179},
{SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180}, {SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA.c_str(), no_argument, &flag, 180},
{SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR.c_str(), required_argument, &flag,
181},
{nullptr, 0, nullptr, 0}}; {nullptr, 0, nullptr, 0}};
int option_index = 0; int option_index = 0;
@ -4865,6 +4873,11 @@ int main(int argc, char **argv) {
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA, cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA,
StringRef::from_lit("yes")); StringRef::from_lit("yes"));
break; break;
case 181:
// --frontend-quic-qlog-dir
cmdcfgs.emplace_back(SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR,
StringRef{optarg});
break;
default: default:
break; break;
} }

View File

@ -2255,6 +2255,9 @@ int option_lookup_token(const char *name, size_t namelen) {
if (util::strieq_l("backend-request-buffe", name, 21)) { if (util::strieq_l("backend-request-buffe", name, 21)) {
return SHRPX_OPTID_BACKEND_REQUEST_BUFFER; return SHRPX_OPTID_BACKEND_REQUEST_BUFFER;
} }
if (util::strieq_l("frontend-quic-qlog-di", name, 21)) {
return SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR;
}
break; break;
case 't': case 't':
if (util::strieq_l("frontend-write-timeou", name, 21)) { if (util::strieq_l("frontend-write-timeou", name, 21)) {
@ -3977,6 +3980,12 @@ int parse_config(Config *config, int optid, const StringRef &opt,
config->quic.upstream.early_data = util::strieq_l("yes", optarg); config->quic.upstream.early_data = util::strieq_l("yes", optarg);
#endif // ENABLE_HTTP3 #endif // ENABLE_HTTP3
return 0;
case SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR:
#ifdef ENABLE_HTTP3
config->quic.upstream.qlog.dir = optarg;
#endif // ENABLE_HTTP3
return 0; return 0;
case SHRPX_OPTID_CONF: case SHRPX_OPTID_CONF:
LOG(WARN) << "conf: ignored"; LOG(WARN) << "conf: ignored";

View File

@ -385,6 +385,8 @@ constexpr auto SHRPX_OPT_FRONTEND_HTTP3_MAX_CONCURRENT_STREAMS =
StringRef::from_lit("frontend-http3-max-concurrent-streams"); StringRef::from_lit("frontend-http3-max-concurrent-streams");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA = constexpr auto SHRPX_OPT_FRONTEND_QUIC_EARLY_DATA =
StringRef::from_lit("frontend-quic-early-data"); StringRef::from_lit("frontend-quic-early-data");
constexpr auto SHRPX_OPT_FRONTEND_QUIC_QLOG_DIR =
StringRef::from_lit("frontend-quic-qlog-dir");
constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8; constexpr size_t SHRPX_OBFUSCATED_NODE_LENGTH = 8;
@ -749,6 +751,9 @@ struct QUICConfig {
struct { struct {
bool log; bool log;
} debug; } debug;
struct {
StringRef dir;
} qlog;
bool early_data; bool early_data;
} upstream; } upstream;
struct { struct {
@ -1205,6 +1210,7 @@ enum {
SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG, SHRPX_OPTID_FRONTEND_QUIC_DEBUG_LOG,
SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA, SHRPX_OPTID_FRONTEND_QUIC_EARLY_DATA,
SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT, SHRPX_OPTID_FRONTEND_QUIC_IDLE_TIMEOUT,
SHRPX_OPTID_FRONTEND_QUIC_QLOG_DIR,
SHRPX_OPTID_FRONTEND_READ_TIMEOUT, SHRPX_OPTID_FRONTEND_READ_TIMEOUT,
SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT, SHRPX_OPTID_FRONTEND_WRITE_TIMEOUT,
SHRPX_OPTID_HEADER_FIELD_BUFFER, SHRPX_OPTID_HEADER_FIELD_BUFFER,

View File

@ -24,7 +24,11 @@
*/ */
#include "shrpx_http3_upstream.h" #include "shrpx_http3_upstream.h"
#include <stdio.h> #include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <cstdio>
#include <ngtcp2/ngtcp2_crypto.h> #include <ngtcp2/ngtcp2_crypto.h>
@ -112,6 +116,7 @@ size_t downstream_queue_size(Worker *worker) {
Http3Upstream::Http3Upstream(ClientHandler *handler) Http3Upstream::Http3Upstream(ClientHandler *handler)
: handler_{handler}, : handler_{handler},
qlog_fd_{-1},
conn_{nullptr}, conn_{nullptr},
tls_alert_{0}, tls_alert_{0},
httpconn_{nullptr}, httpconn_{nullptr},
@ -149,6 +154,10 @@ Http3Upstream::~Http3Upstream() {
if (conn_) { if (conn_) {
ngtcp2_conn_del(conn_); ngtcp2_conn_del(conn_);
} }
if (qlog_fd_ != -1) {
close(qlog_fd_);
}
} }
namespace { namespace {
@ -171,6 +180,29 @@ void log_printf(void *user_data, const char *fmt, ...) {
} }
} // namespace } // namespace
namespace {
void qlog_write(void *user_data, uint32_t flags, const void *data,
size_t datalen) {
auto upstream = static_cast<Http3Upstream *>(user_data);
upstream->qlog_write(data, datalen, flags & NGTCP2_QLOG_WRITE_FLAG_FIN);
}
} // namespace
void Http3Upstream::qlog_write(const void *data, size_t datalen, bool fin) {
assert(qlog_fd_ != -1);
ssize_t nwrite;
while ((nwrite = write(qlog_fd_, data, datalen)) == -1 && errno == EINTR)
;
if (fin) {
close(qlog_fd_);
qlog_fd_ = -1;
}
}
namespace { namespace {
void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) { void rand(uint8_t *dest, size_t destlen, const ngtcp2_rand_ctx *rand_ctx) {
util::random_bytes(dest, dest + destlen, util::random_bytes(dest, dest + destlen,
@ -526,6 +558,16 @@ int Http3Upstream::init(const UpstreamAddr *faddr, const Address &remote_addr,
if (quicconf.upstream.debug.log) { if (quicconf.upstream.debug.log) {
settings.log_printf = log_printf; settings.log_printf = log_printf;
} }
if (!quicconf.upstream.qlog.dir.empty()) {
auto fd = open_qlog_file(quicconf.upstream.qlog.dir, scid);
if (fd != -1) {
qlog_fd_ = fd;
settings.qlog.odcid = initial_hd.dcid;
settings.qlog.write = shrpx::qlog_write;
}
}
settings.initial_ts = quic_timestamp(); settings.initial_ts = quic_timestamp();
settings.cc_algo = NGTCP2_CC_ALGO_BBR; settings.cc_algo = NGTCP2_CC_ALGO_BBR;
settings.max_window = http3conf.upstream.max_connection_window_size; settings.max_window = http3conf.upstream.max_connection_window_size;
@ -2526,4 +2568,44 @@ int Http3Upstream::submit_goaway() {
void Http3Upstream::idle_close() { idle_close_ = true; } void Http3Upstream::idle_close() { idle_close_ = true; }
int Http3Upstream::open_qlog_file(const StringRef &dir,
const ngtcp2_cid &scid) const {
std::array<char, sizeof("20141115T125824.741+0900")> buf;
auto path = dir.str();
path += '/';
path +=
util::format_iso8601_basic(buf.data(), std::chrono::system_clock::now());
path += '-';
path += util::format_hex(scid.data, scid.datalen);
path += ".qlog";
int fd;
#ifdef O_CLOEXEC
while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC,
S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
errno == EINTR)
;
#else // !O_CLOEXEC
while ((fd = open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC,
S_IRUSR | S_IWUSR | S_IRGRP)) == -1 &&
errno == EINTR)
;
if (fd != -1) {
util::make_socket_closeonexec(fd);
}
#endif // !O_CLOEXEC
if (fd == -1) {
auto error = errno;
ULOG(ERROR, this) << "Failed to open qlog file " << path
<< ": errno=" << error;
return -1;
}
return fd;
}
} // namespace shrpx } // namespace shrpx

View File

@ -149,12 +149,16 @@ public:
int submit_goaway(); int submit_goaway();
void idle_close(); void idle_close();
void qlog_write(const void *data, size_t datalen, bool fin);
int open_qlog_file(const StringRef &dir, const ngtcp2_cid &scid) const;
private: private:
ClientHandler *handler_; ClientHandler *handler_;
ev_timer timer_; ev_timer timer_;
ev_timer idle_timer_; ev_timer idle_timer_;
ev_timer shutdown_timer_; ev_timer shutdown_timer_;
ev_prepare prep_; ev_prepare prep_;
int qlog_fd_;
ngtcp2_conn *conn_; ngtcp2_conn *conn_;
quic::Error last_error_; quic::Error last_error_;
uint8_t tls_alert_; uint8_t tls_alert_;

View File

@ -396,6 +396,47 @@ char *iso8601_date(char *res, int64_t ms) {
return p; return p;
} }
char *iso8601_basic_date(char *res, int64_t ms) {
time_t sec = ms / 1000;
tm tms;
if (localtime_r(&sec, &tms) == nullptr) {
return res;
}
auto p = res;
p = cpydig(p, tms.tm_year + 1900, 4);
p = cpydig(p, tms.tm_mon + 1, 2);
p = cpydig(p, tms.tm_mday, 2);
*p++ = 'T';
p = cpydig(p, tms.tm_hour, 2);
p = cpydig(p, tms.tm_min, 2);
p = cpydig(p, tms.tm_sec, 2);
*p++ = '.';
p = cpydig(p, ms % 1000, 3);
#ifdef HAVE_STRUCT_TM_TM_GMTOFF
auto gmtoff = tms.tm_gmtoff;
#else // !HAVE_STRUCT_TM_TM_GMTOFF
auto gmtoff = nghttp2_timegm(&tms) - sec;
#endif // !HAVE_STRUCT_TM_TM_GMTOFF
if (gmtoff == 0) {
*p++ = 'Z';
} else {
if (gmtoff > 0) {
*p++ = '+';
} else {
*p++ = '-';
gmtoff = -gmtoff;
}
p = cpydig(p, gmtoff / 3600, 2);
p = cpydig(p, (gmtoff % 3600) / 60, 2);
}
return p;
}
#ifdef _WIN32 #ifdef _WIN32
namespace bt = boost::posix_time; namespace bt = boost::posix_time;
// one-time definition of the locale that is used to parse UTC strings // one-time definition of the locale that is used to parse UTC strings

View File

@ -247,6 +247,11 @@ std::string iso8601_date(int64_t ms);
// function returns the one beyond the last position. // function returns the one beyond the last position.
char *iso8601_date(char *res, int64_t ms); char *iso8601_date(char *res, int64_t ms);
// Writes given time |t| from epoch in ISO 8601 basic format into the
// buffer pointed by |res|. The buffer must be at least 24 bytes
// long. This function returns the one beyond the last position.
char *iso8601_basic_date(char *res, int64_t ms);
time_t parse_http_date(const StringRef &s); time_t parse_http_date(const StringRef &s);
// Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3 // Parses time formatted as "MMM DD HH:MM:SS YYYY [GMT]" (e.g., Feb 3
@ -657,6 +662,20 @@ template <typename T> StringRef format_iso8601(char *out, const T &tp) {
return StringRef{out, p}; return StringRef{out, p};
} }
// Writes given time |tp| in ISO 8601 basic format (e.g.,
// 20141115T125824.741Z or 20141115T125824.741+0900) in buffer pointed
// by |out|. The buffer must be at least 25 bytes, including terminal
// NULL byte. Expected type of |tp| is std::chrono::time_point. This
// function returns StringRef wrapping the buffer pointed by |out|,
// and this string is terminated by NULL.
template <typename T> StringRef format_iso8601_basic(char *out, const T &tp) {
auto t = std::chrono::duration_cast<std::chrono::milliseconds>(
tp.time_since_epoch());
auto p = iso8601_basic_date(out, t.count());
*p = '\0';
return StringRef{out, p};
}
// Writes given time |tp| in HTTP Date format (e.g., Mon, 10 Oct 2016 // Writes given time |tp| in HTTP Date format (e.g., Mon, 10 Oct 2016
// 10:25:58 GMT) in buffer pointed by |out|. The buffer must be at // 10:25:58 GMT) in buffer pointed by |out|. The buffer must be at
// least 30 bytes, including terminal NULL byte. Expected type of // least 30 bytes, including terminal NULL byte. Expected type of