]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
[MEDIUM] session-counters: add HTTP req/err tracking
authorWilly Tarreau <w@1wt.eu>
Wed, 23 Jun 2010 09:44:09 +0000 (11:44 +0200)
committerWilly Tarreau <w@1wt.eu>
Tue, 10 Aug 2010 16:04:14 +0000 (18:04 +0200)
This patch adds support for the following session counters :
  - http_req_cnt : HTTP request count
  - http_req_rate: HTTP request rate
  - http_err_cnt : HTTP request error count
  - http_err_rate: HTTP request error rate

The equivalent ACLs have been added to check the tracked counters
for the current session or the counters of the current source.

include/proto/session.h
include/types/stick_table.h
src/proto_http.c
src/session.c
src/stick_table.c

index 133e6c9c883795b06e6a1dc28f7ab78549518011..a5dd881b1aaac97d178c15029f555bce81cef5c7 100644 (file)
@@ -98,6 +98,45 @@ static void inline trace_term(struct session *s, unsigned int code)
        s->term_trace |= code;
 }
 
+/* Increase the number of cumulated HTTP requests in the tracked counters */
+static void inline session_inc_http_req_ctr(struct session *s)
+{
+       if (s->tracked_counters) {
+               void *ptr;
+
+               ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_REQ_CNT);
+               if (ptr)
+                       stktable_data_cast(ptr, http_req_cnt)++;
+
+               ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_REQ_RATE);
+               if (ptr)
+                       update_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
+                                              s->tracked_table->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u, 1);
+       }
+}
+
+/* Increase the number of cumulated failed HTTP requests in the tracked
+ * counters. Only 4xx requests should be counted here so that we can
+ * distinguish between errors caused by client behaviour and other ones.
+ * Note that even 404 are interesting because they're generally caused by
+ * vulnerability scans.
+ */
+static void inline session_inc_http_err_ctr(struct session *s)
+{
+       if (s->tracked_counters) {
+               void *ptr;
+
+               ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_ERR_CNT);
+               if (ptr)
+                       stktable_data_cast(ptr, http_err_cnt)++;
+
+               ptr = stktable_data_ptr(s->tracked_table, s->tracked_counters, STKTABLE_DT_HTTP_ERR_RATE);
+               if (ptr)
+                       update_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate),
+                                              s->tracked_table->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u, 1);
+       }
+}
+
 #endif /* _PROTO_SESSION_H */
 
 /*
index a1bfc1acf98a76a0321a99f809715041f73ca858..45eac020aa8392cf4d62073a16937f41e401f4eb 100644 (file)
@@ -49,6 +49,10 @@ enum {
        STKTABLE_DT_CONN_CUR,     /* concurrent number of connections */
        STKTABLE_DT_SESS_CNT,     /* cumulated number of sessions (accepted connections) */
        STKTABLE_DT_SESS_RATE,    /* accepted sessions rate */
+       STKTABLE_DT_HTTP_REQ_CNT, /* cumulated number of incoming HTTP requests */
+       STKTABLE_DT_HTTP_REQ_RATE,/* incoming HTTP request rate */
+       STKTABLE_DT_HTTP_ERR_CNT, /* cumulated number of HTTP requests errors (4xx) */
+       STKTABLE_DT_HTTP_ERR_RATE,/* HTTP request error rate */
        STKTABLE_DT_BYTES_IN_CNT, /* cumulated bytes count from client to servers */
        STKTABLE_DT_BYTES_IN_RATE,/* bytes rate from client to servers */
        STKTABLE_DT_BYTES_OUT_CNT,/* cumulated bytes count from servers to client */
@@ -72,6 +76,10 @@ union stktable_data {
        unsigned int conn_cur;
        unsigned int sess_cnt;
        struct freq_ctr_period sess_rate;
+       unsigned int http_req_cnt;
+       struct freq_ctr_period http_req_rate;
+       unsigned int http_err_cnt;
+       struct freq_ctr_period http_err_rate;
        unsigned long long bytes_in_cnt;
        struct freq_ctr_period bytes_in_rate;
        unsigned long long bytes_out_cnt;
index 57ab14b43ca88b0a228fb9848a291b7670c64075..7e28762de5c584fb1175478ffcf6c8f2f077503c 100644 (file)
@@ -2439,6 +2439,10 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
         * we note the error in the session flags but don't set any state.
         * Since the error will be noted there, it will not be counted by
         * process_session() as a frontend error.
+        * Last, we may increase some tracked counters' http request errors on
+        * the cases that are deliberately the client's fault. For instance,
+        * a timeout or connection reset is not counted as an error. However
+        * a bad request is.
         */
 
        if (unlikely(msg->msg_state < HTTP_MSG_BODY)) {
@@ -2446,6 +2450,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
                 * First, let's catch bad requests.
                 */
                if (unlikely(msg->msg_state == HTTP_MSG_ERROR)) {
+                       session_inc_http_req_ctr(s);
+                       session_inc_http_err_ctr(s);
                        proxy_inc_fe_req_ctr(s->fe);
                        goto return_bad_req;
                }
@@ -2459,6 +2465,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
                        /* FIXME: check if URI is set and return Status
                         * 414 Request URI too long instead.
                         */
+                       session_inc_http_req_ctr(s);
+                       session_inc_http_err_ctr(s);
                        proxy_inc_fe_req_ctr(s->fe);
                        goto return_bad_req;
                }
@@ -2472,11 +2480,15 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
                                goto failed_keep_alive;
 
                        /* we cannot return any message on error */
-                       if (msg->err_pos >= 0)
+                       if (msg->err_pos >= 0) {
                                http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
+                               session_inc_http_err_ctr(s);
+                       }
+
                        msg->msg_state = HTTP_MSG_ERROR;
                        req->analysers = 0;
 
+                       session_inc_http_req_ctr(s);
                        proxy_inc_fe_req_ctr(s->fe);
                        s->fe->counters.failed_req++;
                        if (s->listener->counters)
@@ -2496,13 +2508,16 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
                                goto failed_keep_alive;
 
                        /* read timeout : give up with an error message. */
-                       if (msg->err_pos >= 0)
+                       if (msg->err_pos >= 0) {
                                http_capture_bad_message(&s->fe->invalid_req, s, req, msg, s->fe);
+                               session_inc_http_err_ctr(s);
+                       }
                        txn->status = 408;
                        stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_408));
                        msg->msg_state = HTTP_MSG_ERROR;
                        req->analysers = 0;
 
+                       session_inc_http_req_ctr(s);
                        proxy_inc_fe_req_ctr(s->fe);
                        s->fe->counters.failed_req++;
                        if (s->listener->counters)
@@ -2528,6 +2543,8 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
                        msg->msg_state = HTTP_MSG_ERROR;
                        req->analysers = 0;
 
+                       session_inc_http_err_ctr(s);
+                       session_inc_http_req_ctr(s);
                        proxy_inc_fe_req_ctr(s->fe);
                        s->fe->counters.failed_req++;
                        if (s->listener->counters)
@@ -2588,6 +2605,7 @@ int http_wait_for_request(struct session *s, struct buffer *req, int an_bit)
         * left uninitialized (for instance in the absence of headers).
         */
 
+       session_inc_http_req_ctr(s);
        proxy_inc_fe_req_ctr(s->fe); /* one more valid request for this FE */
 
        if (txn->flags & TX_WAIT_NEXT_RQ) {
@@ -2867,6 +2885,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
                        /* let's log the request time */
                        s->logs.tv_request = now;
                        stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
+                       session_inc_http_err_ctr(s);
                        goto return_prx_cond;
                }
        }
@@ -2898,6 +2917,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
                        txn->status = 403;
                        s->logs.tv_request = now;
                        stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
+                       session_inc_http_err_ctr(s);
                        goto return_prx_cond;
        }
 
@@ -2913,6 +2933,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
                        /* let's log the request time */
                        s->logs.tv_request = now;
                        stream_int_retnclose(req->prod, error_message(s, HTTP_ERR_403));
+                       session_inc_http_err_ctr(s);
                        goto return_prx_cond;
                }
 
@@ -2932,6 +2953,7 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
                        req->analyse_exp = tick_add_ifset(now_ms,  s->be->timeout.tarpit);
                        if (!req->analyse_exp)
                                req->analyse_exp = tick_add(now_ms, 0);
+                       session_inc_http_err_ctr(s);
                        return 1;
                }
        }
@@ -3014,6 +3036,11 @@ int http_process_req_common(struct session *s, struct buffer *req, int an_bit, s
                chunk_initlen(&msg, trash, sizeof(trash), strlen(trash));
                txn->status = 401;
                stream_int_retnclose(req->prod, &msg);
+               /* on 401 we still count one error, because normal browsing
+                * won't significantly increase the counter but brute force
+                * attempts will.
+                */
+               session_inc_http_err_ctr(s);
                goto return_prx_cond;
        }
 
@@ -3578,8 +3605,10 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
 
                if (!ret)
                        goto missing_data;
-               else if (ret < 0)
+               else if (ret < 0) {
+                       session_inc_http_err_ctr(s);
                        goto return_bad_req;
+               }
        }
 
        /* Now we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state.
@@ -3597,8 +3626,10 @@ int http_process_request_body(struct session *s, struct buffer *req, int an_bit)
 
  missing_data:
        /* we get here if we need to wait for more data */
-       if (req->flags & BF_FULL)
+       if (req->flags & BF_FULL) {
+               session_inc_http_err_ctr(s);
                goto return_bad_req;
+       }
 
        if ((req->flags & BF_READ_TIMEOUT) || tick_is_expired(req->analyse_exp, now_ms)) {
                txn->status = 408;
@@ -4178,8 +4209,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
 
                        if (!ret)
                                goto missing_data;
-                       else if (ret < 0)
+                       else if (ret < 0) {
+                               session_inc_http_err_ctr(s);
                                goto return_bad_req;
+                       }
                        /* otherwise we're in HTTP_MSG_DATA or HTTP_MSG_TRAILERS state */
                }
                else if (msg->msg_state == HTTP_MSG_DATA_CRLF) {
@@ -4194,8 +4227,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
 
                        if (ret == 0)
                                goto missing_data;
-                       else if (ret < 0)
+                       else if (ret < 0) {
+                               session_inc_http_err_ctr(s);
                                goto return_bad_req;
+                       }
                        /* we're in MSG_CHUNK_SIZE now */
                }
                else if (msg->msg_state == HTTP_MSG_TRAILERS) {
@@ -4203,8 +4238,10 @@ int http_request_forward_body(struct session *s, struct buffer *req, int an_bit)
 
                        if (ret == 0)
                                goto missing_data;
-                       else if (ret < 0)
+                       else if (ret < 0) {
+                               session_inc_http_err_ctr(s);
                                goto return_bad_req;
+                       }
                        /* we're in HTTP_MSG_DONE now */
                }
                else {
@@ -4512,6 +4549,14 @@ int http_wait_for_response(struct session *s, struct buffer *rep, int an_bit)
        n = msg->sol[msg->sl.st.c] - '0';
        if (n < 1 || n > 5)
                n = 0;
+       /* when the client triggers a 4xx from the server, it's most often due
+        * to a missing object or permission. These events should be tracked
+        * because if they happen often, it may indicate a brute force or a
+        * vulnerability scan.
+        */
+       if (n == 4)
+               session_inc_http_err_ctr(s);
+
        if (s->srv)
                s->srv->counters.p.http.rsp[n]++;
 
index aaa0622b88572a460d9bc1f9da4162211981a945..1266935092818bf45b88db1bf2e48ede00bc2453 100644 (file)
@@ -2483,6 +2483,204 @@ acl_fetch_src_sess_rate(struct proxy *px, struct session *l4, void *l7, int dir,
        return acl_fetch_sess_rate(&px->table, test, stktable_lookup_key(&px->table, key));
 }
 
+/* set test->i to the cumulated number of sessions in the stksess entry <ts> */
+static int
+acl_fetch_http_req_cnt(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+       test->flags = ACL_TEST_F_VOL_TEST;
+       test->i = 0;
+       if (ts != NULL) {
+               void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_REQ_CNT);
+               if (!ptr)
+                       return 0; /* parameter not stored */
+               test->i = stktable_data_cast(ptr, http_req_cnt);
+       }
+       return 1;
+}
+
+/* set test->i to the cumulated number of sessions from the session's tracked counters */
+static int
+acl_fetch_trk_http_req_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
+                      struct acl_expr *expr, struct acl_test *test)
+{
+       if (!l4->tracked_counters)
+               return 0;
+
+       return acl_fetch_http_req_cnt(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* set test->i to the cumulated number of session from the session's source
+ * address in the table pointed to by expr.
+ */
+static int
+acl_fetch_src_http_req_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
+                      struct acl_expr *expr, struct acl_test *test)
+{
+       struct stktable_key *key;
+
+       key = tcpv4_src_to_stktable_key(l4);
+       if (!key)
+               return 0; /* only TCPv4 is supported right now */
+
+       if (expr->arg_len)
+               px = find_stktable(expr->arg.str);
+
+       if (!px)
+               return 0; /* table not found */
+
+       return acl_fetch_http_req_cnt(&px->table, test, stktable_lookup_key(&px->table, key));
+}
+
+/* set test->i to the session rate in the stksess entry <ts> over the configured period */
+static int
+acl_fetch_http_req_rate(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+       test->flags = ACL_TEST_F_VOL_TEST;
+       test->i = 0;
+       if (ts != NULL) {
+               void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_REQ_RATE);
+               if (!ptr)
+                       return 0; /* parameter not stored */
+               test->i = read_freq_ctr_period(&stktable_data_cast(ptr, http_req_rate),
+                                              table->data_arg[STKTABLE_DT_HTTP_REQ_RATE].u);
+       }
+       return 1;
+}
+
+/* set test->i to the session rate from the session's tracked counters over
+ * the configured period.
+ */
+static int
+acl_fetch_trk_http_req_rate(struct proxy *px, struct session *l4, void *l7, int dir,
+                       struct acl_expr *expr, struct acl_test *test)
+{
+       if (!l4->tracked_counters)
+               return 0;
+
+       return acl_fetch_http_req_rate(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* set test->i to the session rate from the session's source address in the
+ * table pointed to by expr, over the configured period.
+ */
+static int
+acl_fetch_src_http_req_rate(struct proxy *px, struct session *l4, void *l7, int dir,
+                       struct acl_expr *expr, struct acl_test *test)
+{
+       struct stktable_key *key;
+
+       key = tcpv4_src_to_stktable_key(l4);
+       if (!key)
+               return 0; /* only TCPv4 is supported right now */
+
+       if (expr->arg_len)
+               px = find_stktable(expr->arg.str);
+
+       if (!px)
+               return 0; /* table not found */
+
+       return acl_fetch_http_req_rate(&px->table, test, stktable_lookup_key(&px->table, key));
+}
+
+/* set test->i to the cumulated number of sessions in the stksess entry <ts> */
+static int
+acl_fetch_http_err_cnt(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+       test->flags = ACL_TEST_F_VOL_TEST;
+       test->i = 0;
+       if (ts != NULL) {
+               void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_ERR_CNT);
+               if (!ptr)
+                       return 0; /* parameter not stored */
+               test->i = stktable_data_cast(ptr, http_err_cnt);
+       }
+       return 1;
+}
+
+/* set test->i to the cumulated number of sessions from the session's tracked counters */
+static int
+acl_fetch_trk_http_err_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
+                      struct acl_expr *expr, struct acl_test *test)
+{
+       if (!l4->tracked_counters)
+               return 0;
+
+       return acl_fetch_http_err_cnt(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* set test->i to the cumulated number of session from the session's source
+ * address in the table pointed to by expr.
+ */
+static int
+acl_fetch_src_http_err_cnt(struct proxy *px, struct session *l4, void *l7, int dir,
+                      struct acl_expr *expr, struct acl_test *test)
+{
+       struct stktable_key *key;
+
+       key = tcpv4_src_to_stktable_key(l4);
+       if (!key)
+               return 0; /* only TCPv4 is supported right now */
+
+       if (expr->arg_len)
+               px = find_stktable(expr->arg.str);
+
+       if (!px)
+               return 0; /* table not found */
+
+       return acl_fetch_http_err_cnt(&px->table, test, stktable_lookup_key(&px->table, key));
+}
+
+/* set test->i to the session rate in the stksess entry <ts> over the configured period */
+static int
+acl_fetch_http_err_rate(struct stktable *table, struct acl_test *test, struct stksess *ts)
+{
+       test->flags = ACL_TEST_F_VOL_TEST;
+       test->i = 0;
+       if (ts != NULL) {
+               void *ptr = stktable_data_ptr(table, ts, STKTABLE_DT_HTTP_ERR_RATE);
+               if (!ptr)
+                       return 0; /* parameter not stored */
+               test->i = read_freq_ctr_period(&stktable_data_cast(ptr, http_err_rate),
+                                              table->data_arg[STKTABLE_DT_HTTP_ERR_RATE].u);
+       }
+       return 1;
+}
+
+/* set test->i to the session rate from the session's tracked counters over
+ * the configured period.
+ */
+static int
+acl_fetch_trk_http_err_rate(struct proxy *px, struct session *l4, void *l7, int dir,
+                       struct acl_expr *expr, struct acl_test *test)
+{
+       if (!l4->tracked_counters)
+               return 0;
+
+       return acl_fetch_http_err_rate(l4->tracked_table, test, l4->tracked_counters);
+}
+
+/* set test->i to the session rate from the session's source address in the
+ * table pointed to by expr, over the configured period.
+ */
+static int
+acl_fetch_src_http_err_rate(struct proxy *px, struct session *l4, void *l7, int dir,
+                       struct acl_expr *expr, struct acl_test *test)
+{
+       struct stktable_key *key;
+
+       key = tcpv4_src_to_stktable_key(l4);
+       if (!key)
+               return 0; /* only TCPv4 is supported right now */
+
+       if (expr->arg_len)
+               px = find_stktable(expr->arg.str);
+
+       if (!px)
+               return 0; /* table not found */
+
+       return acl_fetch_http_err_rate(&px->table, test, stktable_lookup_key(&px->table, key));
+}
+
 /* set test->i to the number of kbytes received from clients matching the stksess entry <ts> */
 static int
 acl_fetch_kbytes_in(struct stktable *table, struct acl_test *test, struct stksess *ts)
@@ -2709,6 +2907,14 @@ static struct acl_kw_list acl_kws = {{ },{
        { "src_sess_cnt",       acl_parse_int,   acl_fetch_src_sess_cnt,      acl_match_int, ACL_USE_TCP4_VOLATILE },
        { "trk_sess_rate",      acl_parse_int,   acl_fetch_trk_sess_rate,     acl_match_int, ACL_USE_NOTHING },
        { "src_sess_rate",      acl_parse_int,   acl_fetch_src_sess_rate,     acl_match_int, ACL_USE_TCP4_VOLATILE },
+       { "trk_http_req_cnt",   acl_parse_int,   acl_fetch_trk_http_req_cnt,  acl_match_int, ACL_USE_NOTHING },
+       { "src_http_req_cnt",   acl_parse_int,   acl_fetch_src_http_req_cnt,  acl_match_int, ACL_USE_TCP4_VOLATILE },
+       { "trk_http_req_rate",  acl_parse_int,   acl_fetch_trk_http_req_rate, acl_match_int, ACL_USE_NOTHING },
+       { "src_http_req_rate",  acl_parse_int,   acl_fetch_src_http_req_rate, acl_match_int, ACL_USE_TCP4_VOLATILE },
+       { "trk_http_err_cnt",   acl_parse_int,   acl_fetch_trk_http_err_cnt,  acl_match_int, ACL_USE_NOTHING },
+       { "src_http_err_cnt",   acl_parse_int,   acl_fetch_src_http_err_cnt,  acl_match_int, ACL_USE_TCP4_VOLATILE },
+       { "trk_http_err_rate",  acl_parse_int,   acl_fetch_trk_http_err_rate, acl_match_int, ACL_USE_NOTHING },
+       { "src_http_err_rate",  acl_parse_int,   acl_fetch_src_http_err_rate, acl_match_int, ACL_USE_TCP4_VOLATILE },
        { "trk_kbytes_in",      acl_parse_int,   acl_fetch_trk_kbytes_in,     acl_match_int, ACL_USE_TCP4_VOLATILE },
        { "src_kbytes_in",      acl_parse_int,   acl_fetch_src_kbytes_in,     acl_match_int, ACL_USE_TCP4_VOLATILE },
        { "trk_bytes_in_rate",  acl_parse_int,   acl_fetch_trk_bytes_in_rate, acl_match_int, ACL_USE_NOTHING },
index 8b216aededc397794bfc89239e977fb028786e65..46d5e1e391518573a3288145d479b66f79340cac 100644 (file)
@@ -552,6 +552,10 @@ struct stktable_data_type stktable_data_types[STKTABLE_DATA_TYPES] = {
        [STKTABLE_DT_CONN_CUR]  = { .name = "conn_cur",  .data_length = stktable_data_size(conn_cur)  },
        [STKTABLE_DT_SESS_CNT]  = { .name = "sess_cnt",  .data_length = stktable_data_size(sess_cnt)  },
        [STKTABLE_DT_SESS_RATE] = { .name = "sess_rate", .data_length = stktable_data_size(sess_rate), .arg_type = ARG_T_DELAY  },
+       [STKTABLE_DT_HTTP_REQ_CNT]  = { .name = "http_req_cnt",  .data_length = stktable_data_size(http_req_cnt)  },
+       [STKTABLE_DT_HTTP_REQ_RATE] = { .name = "http_req_rate", .data_length = stktable_data_size(http_req_rate), .arg_type = ARG_T_DELAY  },
+       [STKTABLE_DT_HTTP_ERR_CNT]  = { .name = "http_err_cnt",  .data_length = stktable_data_size(http_err_cnt)  },
+       [STKTABLE_DT_HTTP_ERR_RATE] = { .name = "http_err_rate", .data_length = stktable_data_size(http_err_rate), .arg_type = ARG_T_DELAY  },
        [STKTABLE_DT_BYTES_IN_CNT]  = { .name = "bytes_in_cnt",  .data_length = stktable_data_size(bytes_in_cnt)  },
        [STKTABLE_DT_BYTES_IN_RATE] = { .name = "bytes_in_rate", .data_length = stktable_data_size(bytes_in_rate), .arg_type = ARG_T_DELAY },
        [STKTABLE_DT_BYTES_OUT_CNT] = { .name = "bytes_out_cnt", .data_length = stktable_data_size(bytes_out_cnt) },