diff --git a/doc/configuration.txt b/doc/configuration.txt index 75383f8b3..3ce29ca5d 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -13792,15 +13792,9 @@ sub() contain characters 'a-z', 'A-Z', '0-9', '.' and '_'. svarint - Converts a binary input sample of a protocol buffers signed "varints" ("sint32" + Converts a binary input sample of a protocol buffers signed "varint" ("sint32" and "sint64") to an integer. - More information may be found here about the protocol buffers message types: - https://developers.google.com/protocol-buffers/docs/encoding - -varint - Converts a binary input sample of a protocol buffers "varints", excepted - the signed ones "sint32" and "sint64", to an integer. - More information may be found here about the protocol buffers message types: + More information may be found here about the protocol buffers message field types: https://developers.google.com/protocol-buffers/docs/encoding table_bytes_in_rate() @@ -13973,6 +13967,39 @@ url_dec Takes an url-encoded string provided as input and returns the decoded version as output. The input and the output are of type string. +ungrpc() : binary + This extracts the protocol buffers message field in raw mode of an input binary + sample with as field number (dotted notation). + + Example: + // with such a protocol buffer .proto file content adapted from + // https://github.com/grpc/grpc/blob/master/examples/protos/route_guide.proto + + message Point { + int32 latitude = 1; + int32 longitude = 2; + } + + message PPoint { + Point point = 59; + } + + message Rectangle { + // One corner of the rectangle. + PPoint lo = 48; + // The other corner of the rectangle. + PPoint hi = 49; + } + + let's say a body request is made of a "Rectangle" object value (two PPoint + protocol buffers messages), the four protocol buffers fields could be + extracted with these "ungrpc" directives: + + req.body,ungrpc(48.59.1) # "latitude" of "lo" first PPoint + req.body,ungrpc(48.59.2) # "longitude" of "lo" first PPoint + req.body,ungrpc(49.59.1) # "latidude" of "hi" second PPoint + req.body,ungrpc(49.59.2) # "longitude" of "hi" second PPoint + unset-var() Unsets a variable if the input content is defined. The name of the variable starts with an indication about its scope. The scopes allowed are: @@ -13999,6 +14026,12 @@ utime([,]) # e.g. 20140710162350 127.0.0.1:57325 log-format %[date,utime(%Y%m%d%H%M%S)]\ %ci:%cp +varint + Converts a binary input sample of a protocol buffers "varint", excepted + the signed ones "sint32" and "sint64", to an integer. + More information may be found here about the protocol buffers message field types: + https://developers.google.com/protocol-buffers/docs/encoding + word(,[,]) Extracts the nth word counting from the beginning (positive index) or from the end (negative index) considering given delimiters from an input string. @@ -15976,38 +16009,6 @@ hdr_val([[,]]) : integer (deprecated) the first one. Negative values indicate positions relative to the last one, with -1 being the last one. A typical use is with the X-Forwarded-For header. -req.ungrpc() : binary - This extracts the protocol buffers message in raw mode of a gRPC request body - with as terminal field number (dotted notation). - - Example: - // with such a protocol buffer .proto file content adapted from - // https://github.com/grpc/grpc/blob/master/examples/protos/route_guide.proto - - message Point { - int32 latitude = 1; - int32 longitude = 2; - } - - message PPoint { - Point point = 59; - } - - message Rectangle { - // One corner of the rectangle. - PPoint lo = 48; - // The other corner of the rectangle. - PPoint hi = 49; - } - - Let's say a body requests is made of a "Rectangle" object value (two PPoint - protocol buffers messages), the four protocol buffers messages could be fetched - with this "req.ungrpc" sample fetch directives: - - req.ungrpc(48.59.1) # "latitude" of "lo" first PPoint - req.ungrpc(48.59.2) # "longitude" of "lo" first PPoint - req.ungrpc(49.59.1) # "latidude" of "hi" second PPoint - req.ungrpc(49.59.2) # "longitude" of "hi" second PPoint http_auth() : boolean diff --git a/src/http_fetch.c b/src/http_fetch.c index 8f88646ea..51f2ef13f 100644 --- a/src/http_fetch.c +++ b/src/http_fetch.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include @@ -1517,245 +1516,6 @@ static int smp_fetch_hdr_val(const struct arg *args, struct sample *smp, const c return ret; } -static inline struct buffer * -smp_fetch_body_buf(const struct arg *args, struct sample *smp) -{ - struct buffer *buf; - - if (IS_HTX_SMP(smp) || (smp->px->mode == PR_MODE_TCP)) { - /* HTX version */ - struct htx *htx = smp_prefetch_htx(smp, args); - int32_t pos; - - if (!htx) - return NULL; - - buf = get_trash_chunk(); - for (pos = htx_get_head(htx); pos != -1; pos = htx_get_next(htx, pos)) { - struct htx_blk *blk = htx_get_blk(htx, pos); - enum htx_blk_type type = htx_get_blk_type(blk); - - if (type == HTX_BLK_EOM || type == HTX_BLK_EOD) - break; - if (type == HTX_BLK_DATA) { - if (!htx_data_to_h1(htx_get_blk_value(htx, blk), buf, 0)) - return NULL; - } - } - } - else { - /* LEGACY version */ - struct http_msg *msg; - unsigned long len; - unsigned long block1; - char *body; - - if (smp_prefetch_http(smp->px, smp->strm, smp->opt, args, smp, 1) <= 0) - return NULL; - - if ((smp->opt & SMP_OPT_DIR) == SMP_OPT_DIR_REQ) - msg = &smp->strm->txn->req; - else - msg = &smp->strm->txn->rsp; - - len = http_body_bytes(msg); - body = c_ptr(msg->chn, -http_data_rewind(msg)); - - block1 = len; - if (block1 > b_wrap(&msg->chn->buf) - body) - block1 = b_wrap(&msg->chn->buf) - body; - - buf = get_trash_chunk(); - if (block1 == len) { - /* buffer is not wrapped (or empty) */ - memcpy(buf->area, body, len); - } - else { - /* buffer is wrapped, we need to defragment it */ - memcpy(buf->area, body, block1); - memcpy(buf->area + block1, b_orig(&msg->chn->buf), - len - block1); - } - buf->data = len; - } - - return buf; -} - -#define GRPC_MSG_COMPRESS_FLAG_SZ 1 /* 1 byte */ -#define GRPC_MSG_LENGTH_SZ 4 /* 4 bytes */ -#define GRPC_MSG_HEADER_SZ (GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ) - -/* - * Fetch a gRPC field value. Takes a mandatory argument: the field identifier - * (dotted notation) internally represented as an array of unsigned integers - * and its size. - * Return 1 if the field was found, 0 if not. - */ -static int smp_fetch_req_ungrpc(const struct arg *args, struct sample *smp, const char *kw, void *private) -{ - struct buffer *body; - unsigned char *pos; - size_t grpc_left; - unsigned int *fid; - size_t fid_sz; - - if (!smp->strm) - return 0; - - fid = args[0].data.fid.ids; - fid_sz = args[0].data.fid.sz; - - body = smp_fetch_body_buf(args, smp); - if (!body) - return 0; - - pos = (unsigned char *)body->area; - /* Remaining bytes in the body to be parsed. */ - grpc_left = body->data; - - while (grpc_left > GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ) { - int next_field, found; - size_t grpc_msg_len, left; - unsigned int wire_type, field_number; - uint64_t key, elen; - - grpc_msg_len = left = ntohl(*(uint32_t *)(pos + GRPC_MSG_COMPRESS_FLAG_SZ)); - - pos += GRPC_MSG_HEADER_SZ; - grpc_left -= GRPC_MSG_HEADER_SZ; - - if (grpc_left < left) - return 0; - - found = 1; - /* Length of the length-delimited messages if any. */ - elen = 0; - - /* Message decoding: there may be serveral key+value protobuf pairs by - * gRPC message. - */ - next_field = 0; - while (next_field < fid_sz) { - uint64_t sleft; - - if ((ssize_t)left <= 0) - return 0; - - /* Remaining bytes saving. */ - sleft = left; - - /* Key decoding */ - if (!protobuf_decode_varint(&key, &pos, &left)) - return 0; - - wire_type = key & 0x7; - field_number = key >> 3; - found = field_number == fid[next_field]; - - if (found && field_number != fid[next_field]) - found = 0; - - switch (wire_type) { - case PBUF_TYPE_VARINT: - { - if (!found) { - protobuf_skip_varint(&pos, &left); - } else if (next_field == fid_sz - 1) { - int varint_len; - unsigned char *spos = pos; - - varint_len = protobuf_varint_getlen(&pos, &left); - if (varint_len == -1) - return 0; - - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = (char *)spos; - smp->data.u.str.data = varint_len; - smp->flags = SMP_F_VOL_TEST; - return 1; - } - break; - } - - case PBUF_TYPE_64BIT: - { - if (!found) { - pos += sizeof(uint64_t); - left -= sizeof(uint64_t); - } else if (next_field == fid_sz - 1) { - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = (char *)pos; - smp->data.u.str.data = sizeof(uint64_t); - smp->flags = SMP_F_VOL_TEST; - return 1; - } - break; - } - - case PBUF_TYPE_LENGTH_DELIMITED: - { - /* Decode the length of this length-delimited field. */ - if (!protobuf_decode_varint(&elen, &pos, &left)) - return 0; - - if (elen > left) - return 0; - - /* The size of the current field is computed from here do skip - * the bytes to encode the previous lenght.* - */ - sleft = left; - if (!found) { - /* Skip the current length-delimited field. */ - pos += elen; - left -= elen; - break; - } else if (next_field == fid_sz - 1) { - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = (char *)pos; - smp->data.u.str.data = elen; - smp->flags = SMP_F_VOL_TEST; - return 1; - } - break; - } - - case PBUF_TYPE_32BIT: - { - if (!found) { - pos += sizeof(uint32_t); - left -= sizeof(uint32_t); - } else if (next_field == fid_sz - 1) { - smp->data.type = SMP_T_BIN; - smp->data.u.str.area = (char *)pos; - smp->data.u.str.data = sizeof(uint32_t); - smp->flags = SMP_F_VOL_TEST; - return 1; - } - break; - } - - default: - return 0; - } - - if ((ssize_t)(elen) > 0) - elen -= sleft - left; - - if (found) { - next_field++; - } - else if ((ssize_t)elen <= 0) { - next_field = 0; - } - } - grpc_left -= grpc_msg_len; - } - - return 0; -} - /* Fetch an HTTP header's IP value. takes a mandatory argument of type string * and an optional one of type int to designate a specific occurrence. * It returns an IPv4 or IPv6 address. @@ -3122,7 +2882,6 @@ static struct sample_fetch_kw_list sample_fetch_keywords = {ILH, { { "req.hdr_ip", smp_fetch_hdr_ip, ARG2(0,STR,SINT), val_hdr, SMP_T_IPV4, SMP_USE_HRQHV }, { "req.hdr_names", smp_fetch_hdr_names, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRQHV }, { "req.hdr_val", smp_fetch_hdr_val, ARG2(0,STR,SINT), val_hdr, SMP_T_SINT, SMP_USE_HRQHV }, - { "req.ungrpc", smp_fetch_req_ungrpc, ARG1(1, PBUF_FNUM), NULL, SMP_T_BIN, SMP_USE_HRQHV }, /* explicit req.{cook,hdr} are used to force the fetch direction to be response-only */ { "res.cook", smp_fetch_cookie, ARG1(0,STR), NULL, SMP_T_STR, SMP_USE_HRSHV }, diff --git a/src/sample.c b/src/sample.c index 1f323bdc7..9c20469c5 100644 --- a/src/sample.c +++ b/src/sample.c @@ -2776,6 +2776,175 @@ static int sample_conv_strcmp(const struct arg *arg_p, struct sample *smp, void return 1; } +#define GRPC_MSG_COMPRESS_FLAG_SZ 1 /* 1 byte */ +#define GRPC_MSG_LENGTH_SZ 4 /* 4 bytes */ +#define GRPC_MSG_HEADER_SZ (GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ) + +/* + * Extract the field value of an input binary sample. Takes a mandatory argument: + * the protocol buffers field identifier (dotted notation) internally represented + * as an array of unsigned integers and its size. + * Return 1 if the field was found, 0 if not. + */ +static int sample_conv_ungrpc(const struct arg *arg_p, struct sample *smp, void *private) +{ + unsigned char *pos; + size_t grpc_left; + unsigned int *fid; + size_t fid_sz; + + if (!smp->strm) + return 0; + + fid = arg_p[0].data.fid.ids; + fid_sz = arg_p[0].data.fid.sz; + + pos = (unsigned char *)smp->data.u.str.area; + /* Remaining bytes in the body to be parsed. */ + grpc_left = smp->data.u.str.data; + + while (grpc_left > GRPC_MSG_COMPRESS_FLAG_SZ + GRPC_MSG_LENGTH_SZ) { + int next_field, found; + size_t grpc_msg_len, left; + unsigned int wire_type, field_number; + uint64_t key, elen; + + grpc_msg_len = left = ntohl(*(uint32_t *)(pos + GRPC_MSG_COMPRESS_FLAG_SZ)); + + pos += GRPC_MSG_HEADER_SZ; + grpc_left -= GRPC_MSG_HEADER_SZ; + + if (grpc_left < left) + return 0; + + found = 1; + /* Length of the length-delimited messages if any. */ + elen = 0; + + /* Message decoding: there may be serveral key+value protobuf pairs by + * gRPC message. + */ + next_field = 0; + while (next_field < fid_sz) { + uint64_t sleft; + + if ((ssize_t)left <= 0) + return 0; + + /* Remaining bytes saving. */ + sleft = left; + + /* Key decoding */ + if (!protobuf_decode_varint(&key, &pos, &left)) + return 0; + + wire_type = key & 0x7; + field_number = key >> 3; + found = field_number == fid[next_field]; + + if (found && field_number != fid[next_field]) + found = 0; + + switch (wire_type) { + case PBUF_TYPE_VARINT: + { + if (!found) { + protobuf_skip_varint(&pos, &left); + } else if (next_field == fid_sz - 1) { + int varint_len; + unsigned char *spos = pos; + + varint_len = protobuf_varint_getlen(&pos, &left); + if (varint_len == -1) + return 0; + + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)spos; + smp->data.u.str.data = varint_len; + smp->flags = SMP_F_VOL_TEST; + return 1; + } + break; + } + + case PBUF_TYPE_64BIT: + { + if (!found) { + pos += sizeof(uint64_t); + left -= sizeof(uint64_t); + } else if (next_field == fid_sz - 1) { + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)pos; + smp->data.u.str.data = sizeof(uint64_t); + smp->flags = SMP_F_VOL_TEST; + return 1; + } + break; + } + + case PBUF_TYPE_LENGTH_DELIMITED: + { + /* Decode the length of this length-delimited field. */ + if (!protobuf_decode_varint(&elen, &pos, &left)) + return 0; + + if (elen > left) + return 0; + + /* The size of the current field is computed from here do skip + * the bytes to encode the previous lenght.* + */ + sleft = left; + if (!found) { + /* Skip the current length-delimited field. */ + pos += elen; + left -= elen; + break; + } else if (next_field == fid_sz - 1) { + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)pos; + smp->data.u.str.data = elen; + smp->flags = SMP_F_VOL_TEST; + return 1; + } + break; + } + + case PBUF_TYPE_32BIT: + { + if (!found) { + pos += sizeof(uint32_t); + left -= sizeof(uint32_t); + } else if (next_field == fid_sz - 1) { + smp->data.type = SMP_T_BIN; + smp->data.u.str.area = (char *)pos; + smp->data.u.str.data = sizeof(uint32_t); + smp->flags = SMP_F_VOL_TEST; + return 1; + } + break; + } + + default: + return 0; + } + + if ((ssize_t)(elen) > 0) + elen -= sleft - left; + + if (found) { + next_field++; + } + else if ((ssize_t)elen <= 0) { + next_field = 0; + } + } + grpc_left -= grpc_msg_len; + } + + return 0; +} + /* This function checks the "strcmp" converter's arguments and extracts the * variable name and its scope. */ @@ -3161,6 +3330,9 @@ static struct sample_conv_kw_list sample_conv_kws = {ILH, { { "sha1", sample_conv_sha1, 0, NULL, SMP_T_BIN, SMP_T_BIN }, { "concat", sample_conv_concat, ARG3(1,STR,STR,STR), smp_check_concat, SMP_T_STR, SMP_T_STR }, { "strcmp", sample_conv_strcmp, ARG1(1,STR), smp_check_strcmp, SMP_T_STR, SMP_T_SINT }, + + /* gRPC converters. */ + { "ungrpc", sample_conv_ungrpc, ARG1(1,PBUF_FNUM), NULL, SMP_T_BIN, SMP_T_BIN }, { "varint", sample_conv_varint, 0, NULL, SMP_T_BIN, SMP_T_SINT }, { "svarint", sample_conv_svarint, 0, NULL, SMP_T_BIN, SMP_T_SINT },