diff --git a/doc/configuration.txt b/doc/configuration.txt index b1947b24d..e125f59c9 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -19760,6 +19760,24 @@ An optional table may be specified with the "sc*" form, in which case the currently tracked key will be looked up into this alternate table instead of the table currently being tracked. +accept_date([]) : integer + This is the exact date when the connection was received by HAProxy + (which might be very slightly different from the date observed on the + network if there was some queuing in the system's backlog). This is usually + the same date which may appear in any upstream firewall's log. When used in + HTTP mode, the accept_date field will be reset to the first moment the + connection is ready to receive a new request (end of previous response for + HTTP/1, immediately after previous request for HTTP/2). + + Returns a value in number of seconds since epoch. + + is facultative, and can be set to "s" for seconds (default behavior), + "ms" for milliseconds or "us" for microseconds. + If unit is set, return value is an integer reflecting either seconds, + milliseconds or microseconds since epoch. + It is useful when a time resolution of less than a second is needed. + + bc_dst : ip This is the destination ip address of the connection on the server side, which is the server address HAProxy connected to. It is of type IP and works @@ -21885,6 +21903,19 @@ hdr([[,]]) : string Note that contrary to the hdr() sample fetch method, the hdr_* ACL keywords unambiguously apply to the request headers. +request_date([]) : integer + This is the exact date when the first byte of the HTTP request was received + by HAProxy (log-format tag %tr). This is computed from accept_date + + handshake time (%Th) + idle time (%Ti). + + Returns a value in number of seconds since epoch. + + is facultative, and can be set to "s" for seconds (default behavior), + "ms" for milliseconds or "us" for microseconds. + If unit is set, return value is an integer reflecting either seconds, + milliseconds or microseconds since epoch. + It is useful when a time resolution of less than a second is needed. + req.fhdr([,]) : string This returns the full value of the last occurrence of header in an HTTP request. It differs from req.hdr() in that any commas present in the diff --git a/src/tcp_sample.c b/src/tcp_sample.c index 8096e987c..9fbf920a2 100644 --- a/src/tcp_sample.c +++ b/src/tcp_sample.c @@ -497,12 +497,91 @@ smp_fetch_fc_reordering(const struct arg *args, struct sample *smp, const char * #endif #endif // TCP_INFO +/* Validates the data unit argument passed to "accept_date" fetch. Argument 0 support an + * optional string representing the unit of the result: "s" for seconds, "ms" for + * milliseconds and "us" for microseconds. + * Returns 0 on error and non-zero if OK. + */ +int smp_check_accept_date_unit(struct arg *args, char **err) +{ + if (args[0].type == ARGT_STR) { + long long int unit; + + if (strcmp(args[0].data.str.area, "s") == 0) { + unit = TIME_UNIT_S; + } + else if (strcmp(args[0].data.str.area, "ms") == 0) { + unit = TIME_UNIT_MS; + } + else if (strcmp(args[0].data.str.area, "us") == 0) { + unit = TIME_UNIT_US; + } + else { + memprintf(err, "expects 's', 'ms' or 'us', got '%s'", + args[0].data.str.area); + return 0; + } + + chunk_destroy(&args[0].data.str); + args[0].type = ARGT_SINT; + args[0].data.sint = unit; + } + else if (args[0].type != ARGT_STOP) { + memprintf(err, "Unexpected arg type"); + return 0; + } + + return 1; +} + +/* retrieve the accept or request date in epoch time, converts it to milliseconds + * or microseconds if asked to in optional args[1] unit param */ +static int +smp_fetch_accept_date(const struct arg *args, struct sample *smp, const char *kw, void *private) +{ + struct strm_logs *logs; + struct timeval tv; + + if (!smp->strm) + return 0; + + logs = &smp->strm->logs; + + if (kw[0] == 'r') { /* request_date */ + tv_ms_add(&tv, &logs->accept_date, logs->t_idle >= 0 ? logs->t_idle + logs->t_handshake : 0); + } else { /* accept_date */ + tv.tv_sec = logs->accept_date.tv_sec; + tv.tv_usec = logs->accept_date.tv_usec; + } + + smp->data.u.sint = tv.tv_sec; + + /* report in milliseconds */ + if (args[0].type == ARGT_SINT && args[0].data.sint == TIME_UNIT_MS) { + smp->data.u.sint *= 1000; + smp->data.u.sint += tv.tv_usec / 1000; + } + /* report in microseconds */ + else if (args[0].type == ARGT_SINT && args[0].data.sint == TIME_UNIT_US) { + smp->data.u.sint *= 1000000; + smp->data.u.sint += tv.tv_usec; + } + + smp->data.type = SMP_T_SINT; + smp->flags |= SMP_F_VOL_TEST | SMP_F_MAY_CHANGE; + return 1; +} + /* Note: must not be declared as its list will be overwritten. * Note: fetches that may return multiple types should be declared using the * appropriate pseudo-type. If not available it must be declared as the lowest * common denominator, the type that can be casted into all other ones. */ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { + /* timestamps */ + { "accept_date", smp_fetch_accept_date, ARG1(0,STR), smp_check_accept_date_unit, SMP_T_SINT, SMP_USE_L4CLI }, + { "request_date", smp_fetch_accept_date, ARG1(0,STR), smp_check_accept_date_unit, SMP_T_SINT, SMP_USE_HRQHP }, + { "bc_dst", smp_fetch_dst, 0, NULL, SMP_T_ADDR, SMP_USE_L4SRV }, { "bc_dst_port", smp_fetch_dport, 0, NULL, SMP_T_SINT, SMP_USE_L4SRV }, { "bc_src", smp_fetch_src, 0, NULL, SMP_T_ADDR, SMP_USE_L4SRV },