get:
Show a patch.

patch:
Update a patch.

put:
Update a patch.

GET /api/patches/128508/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 128508,
    "url": "https://patches.dpdk.org/api/patches/128508/?format=api",
    "web_url": "https://patches.dpdk.org/project/dpdk/patch/20230612113110.72345-1-kumaraparamesh92@gmail.com/",
    "project": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/projects/1/?format=api",
        "name": "DPDK",
        "link_name": "dpdk",
        "list_id": "dev.dpdk.org",
        "list_email": "dev@dpdk.org",
        "web_url": "http://core.dpdk.org",
        "scm_url": "git://dpdk.org/dpdk",
        "webscm_url": "http://git.dpdk.org/dpdk",
        "list_archive_url": "https://inbox.dpdk.org/dev",
        "list_archive_url_format": "https://inbox.dpdk.org/dev/{}",
        "commit_url_format": ""
    },
    "msgid": "<20230612113110.72345-1-kumaraparamesh92@gmail.com>",
    "list_archive_url": "https://inbox.dpdk.org/dev/20230612113110.72345-1-kumaraparamesh92@gmail.com",
    "date": "2023-06-12T11:31:10",
    "name": "[v7] gro : ipv6 changes to support GRO for TCP/ipv6",
    "commit_ref": null,
    "pull_url": null,
    "state": "superseded",
    "archived": true,
    "hash": "d33e5ecfc1f5c98ac84cd0e5cdb28fa9f0c4ff38",
    "submitter": {
        "id": 2391,
        "url": "https://patches.dpdk.org/api/people/2391/?format=api",
        "name": "Kumara Parameshwaran",
        "email": "kumaraparamesh92@gmail.com"
    },
    "delegate": {
        "id": 1,
        "url": "https://patches.dpdk.org/api/users/1/?format=api",
        "username": "tmonjalo",
        "first_name": "Thomas",
        "last_name": "Monjalon",
        "email": "thomas@monjalon.net"
    },
    "mbox": "https://patches.dpdk.org/project/dpdk/patch/20230612113110.72345-1-kumaraparamesh92@gmail.com/mbox/",
    "series": [
        {
            "id": 28455,
            "url": "https://patches.dpdk.org/api/series/28455/?format=api",
            "web_url": "https://patches.dpdk.org/project/dpdk/list/?series=28455",
            "date": "2023-06-12T11:31:10",
            "name": "[v7] gro : ipv6 changes to support GRO for TCP/ipv6",
            "version": 7,
            "mbox": "https://patches.dpdk.org/series/28455/mbox/"
        }
    ],
    "comments": "https://patches.dpdk.org/api/patches/128508/comments/",
    "check": "fail",
    "checks": "https://patches.dpdk.org/api/patches/128508/checks/",
    "tags": {},
    "related": [],
    "headers": {
        "Return-Path": "<dev-bounces@dpdk.org>",
        "X-Original-To": "patchwork@inbox.dpdk.org",
        "Delivered-To": "patchwork@inbox.dpdk.org",
        "Received": [
            "from mails.dpdk.org (mails.dpdk.org [217.70.189.124])\n\tby inbox.dpdk.org (Postfix) with ESMTP id 662A442C97;\n\tMon, 12 Jun 2023 13:31:20 +0200 (CEST)",
            "from mails.dpdk.org (localhost [127.0.0.1])\n\tby mails.dpdk.org (Postfix) with ESMTP id E459740689;\n\tMon, 12 Jun 2023 13:31:19 +0200 (CEST)",
            "from mail-pl1-f173.google.com (mail-pl1-f173.google.com\n [209.85.214.173])\n by mails.dpdk.org (Postfix) with ESMTP id CC8164014F\n for <dev@dpdk.org>; Mon, 12 Jun 2023 13:31:18 +0200 (CEST)",
            "by mail-pl1-f173.google.com with SMTP id\n d9443c01a7336-1b3c5389fa2so6530725ad.0\n for <dev@dpdk.org>; Mon, 12 Jun 2023 04:31:18 -0700 (PDT)",
            "from kparameshw7KFWX.vmware.com.com ([49.207.243.222])\n by smtp.gmail.com with ESMTPSA id\n c18-20020a170902b69200b001ae59169f05sm8058144pls.182.2023.06.12.04.31.15\n (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256);\n Mon, 12 Jun 2023 04:31:16 -0700 (PDT)"
        ],
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20221208; t=1686569478; x=1689161478;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:from:to:cc:subject:date\n :message-id:reply-to;\n bh=nklccYA34y58tRFmtbcGFbFarqIGh6POELTZEGgKi/k=;\n b=kfhTT+5NMIoBPbi08t2AiEZXal90+/AcTieKnaEQrGFWwMuwKFcHqlNTfKIQfI0Irn\n WxFytaUZLhErjtIepguVquwFfFJizbKbFAKbQ+pqvm6vJmiLS/5W7YQE76Qydcp9aH/U\n m/8GjLEIWlPzilMNpmAkR3zrcVKxhasMhHVGiPrFRPHzBnho20Y4vYGQid/D/MIbf4WZ\n CGJwTXVo+kn7gGtAND8pqr7eGcLyauIHxtUyGdSAxch5zN+2Ye1nmzTehVxQR/YyFU7P\n Vu98JOW2kE1dU4B3CJxyeojiDZ3Nk3s4YLFvGmxuANfV/JVHU8fEKNrxH1eHT4msbYI1\n yn+Q==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20221208; t=1686569478; x=1689161478;\n h=content-transfer-encoding:mime-version:references:in-reply-to\n :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n :subject:date:message-id:reply-to;\n bh=nklccYA34y58tRFmtbcGFbFarqIGh6POELTZEGgKi/k=;\n b=iRtSpVVOatbN8G/kpK03cgEjoll7eomsbncj4LQwlWdOPQpeITLQlRSom/AFvYEnZE\n +K8JgIGvbyn2AKp0Pn7oDxdXrhbOnVLwnhfwlFG6s4WUapGIk2NwckD04i86d4dg8bYU\n eJZjC/WYHb16nnL5OK83bonbUcVUnrVCH34jxGZzt/WH9uvQz9115q02SSPgST3A4AZ5\n LLx8/RDYK++ahiWCJnnOJ32yFf0fiyu6Ejr1bQ0ii02yR/XVtyF7UDWZkRwW4zx3NSDW\n TgTefIFDFp0tQ6XMAhhsYEjoxZ0Ey5kJQJQChwahu445Uxr3U4Cx/kT//FWmXXJuP4Zb\n a7CA==",
        "X-Gm-Message-State": "AC+VfDyOzzDZb+gquV3ZFwMXBNuNLJGU4S+SrwDSxlKJJbBGdyso3SfC\n cckR74HoYBNEmlw96CVL1qM=",
        "X-Google-Smtp-Source": "\n ACHHUZ5KHbucXpdkWhEf7GHjuzSJczuJsFMuo4NVR+5HwEvZ2W8hdEnET4/IQBq6PqXgIUL8sfMewA==",
        "X-Received": "by 2002:a17:902:e802:b0:1b3:de47:fafc with SMTP id\n u2-20020a170902e80200b001b3de47fafcmr364483plg.7.1686569477030;\n Mon, 12 Jun 2023 04:31:17 -0700 (PDT)",
        "From": "Kumara Parameshwaran <kumaraparamesh92@gmail.com>",
        "To": "jiayu.hu@intel.com",
        "Cc": "dev@dpdk.org,\n\tKumara Parameshwaran <kumaraparamesh92@gmail.com>",
        "Subject": "[PATCH v7] gro : ipv6 changes to support GRO for TCP/ipv6",
        "Date": "Mon, 12 Jun 2023 17:01:10 +0530",
        "Message-Id": "<20230612113110.72345-1-kumaraparamesh92@gmail.com>",
        "X-Mailer": "git-send-email 2.32.1 (Apple Git-133)",
        "In-Reply-To": "<20221020181425.48006-1-kumaraparmesh92@gmail.com>",
        "References": "<20221020181425.48006-1-kumaraparmesh92@gmail.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "X-BeenThere": "dev@dpdk.org",
        "X-Mailman-Version": "2.1.29",
        "Precedence": "list",
        "List-Id": "DPDK patches and discussions <dev.dpdk.org>",
        "List-Unsubscribe": "<https://mails.dpdk.org/options/dev>,\n <mailto:dev-request@dpdk.org?subject=unsubscribe>",
        "List-Archive": "<http://mails.dpdk.org/archives/dev/>",
        "List-Post": "<mailto:dev@dpdk.org>",
        "List-Help": "<mailto:dev-request@dpdk.org?subject=help>",
        "List-Subscribe": "<https://mails.dpdk.org/listinfo/dev>,\n <mailto:dev-request@dpdk.org?subject=subscribe>",
        "Errors-To": "dev-bounces@dpdk.org"
    },
    "content": "The patch adds GRO support for TCP/ipv6 packets. This does not\ninclude the support for vxlan, udp ipv6 packets.\n\nSigned-off-by: Kumara Parameshwaran <kumaraparamesh92@gmail.com>\n---\nv1:\n\t* Changes to support GRO for TCP/ipv6 packets. This does not include\n\t  vxlan changes. \n\t* The GRO is performed only for ipv6 packets that does not contain \n\t extension headers. \n\t* The logic for the TCP coalescing remains the same, in ipv6 header \n\t  the source address, destination address, flow label, version fields \n\t  are expected to be the same. \n\t* Re-organised the code to reuse certain tcp functions for both ipv4 and \n\t  ipv6 flows.\nv2:\n\t* Fix comments in gro_tcp6.h header file. \n\nv3:\n\t* Adderess review comments to fix code duplication for v4 and v6\n\nv4:\n\t* Addresses review comments for v3, do not use callbacks \n\nv5:\n\t* Address review comments\n\nv6:\n\t* Fix warning and coding style issues\n\nv7:\n\t* Fix build compilation issue\n lib/gro/gro_tcp4.c         | 178 ++++++-------------------\n lib/gro/gro_tcp4.h         | 170 +----------------------\n lib/gro/gro_tcp6.c         | 267 +++++++++++++++++++++++++++++++++++++\n lib/gro/gro_tcp6.h         | 161 ++++++++++++++++++++++\n lib/gro/gro_tcp_internal.c | 128 ++++++++++++++++++\n lib/gro/gro_tcp_internal.h | 212 +++++++++++++++++++++++++++++\n lib/gro/gro_vxlan_tcp4.c   |  23 ++--\n lib/gro/gro_vxlan_tcp4.h   |   3 +-\n lib/gro/meson.build        |   2 +\n lib/gro/rte_gro.c          |  83 ++++++++++--\n lib/gro/rte_gro.h          |   3 +\n 11 files changed, 897 insertions(+), 333 deletions(-)\n create mode 100644 lib/gro/gro_tcp6.c\n create mode 100644 lib/gro/gro_tcp6.h\n create mode 100644 lib/gro/gro_tcp_internal.c\n create mode 100644 lib/gro/gro_tcp_internal.h",
    "diff": "diff --git a/lib/gro/gro_tcp4.c b/lib/gro/gro_tcp4.c\nindex 0014096e63..42fee78f30 100644\n--- a/lib/gro/gro_tcp4.c\n+++ b/lib/gro/gro_tcp4.c\n@@ -30,7 +30,7 @@ gro_tcp4_tbl_create(uint16_t socket_id,\n \tif (tbl == NULL)\n \t\treturn NULL;\n \n-\tsize = sizeof(struct gro_tcp4_item) * entries_num;\n+\tsize = sizeof(struct gro_tcp_item) * entries_num;\n \ttbl->items = rte_zmalloc_socket(__func__,\n \t\t\tsize,\n \t\t\tRTE_CACHE_LINE_SIZE,\n@@ -71,18 +71,6 @@ gro_tcp4_tbl_destroy(void *tbl)\n \trte_free(tcp_tbl);\n }\n \n-static inline uint32_t\n-find_an_empty_item(struct gro_tcp4_tbl *tbl)\n-{\n-\tuint32_t i;\n-\tuint32_t max_item_num = tbl->max_item_num;\n-\n-\tfor (i = 0; i < max_item_num; i++)\n-\t\tif (tbl->items[i].firstseg == NULL)\n-\t\t\treturn i;\n-\treturn INVALID_ARRAY_INDEX;\n-}\n-\n static inline uint32_t\n find_an_empty_flow(struct gro_tcp4_tbl *tbl)\n {\n@@ -95,56 +83,6 @@ find_an_empty_flow(struct gro_tcp4_tbl *tbl)\n \treturn INVALID_ARRAY_INDEX;\n }\n \n-static inline uint32_t\n-insert_new_item(struct gro_tcp4_tbl *tbl,\n-\t\tstruct rte_mbuf *pkt,\n-\t\tuint64_t start_time,\n-\t\tuint32_t prev_idx,\n-\t\tuint32_t sent_seq,\n-\t\tuint16_t ip_id,\n-\t\tuint8_t is_atomic)\n-{\n-\tuint32_t item_idx;\n-\n-\titem_idx = find_an_empty_item(tbl);\n-\tif (item_idx == INVALID_ARRAY_INDEX)\n-\t\treturn INVALID_ARRAY_INDEX;\n-\n-\ttbl->items[item_idx].firstseg = pkt;\n-\ttbl->items[item_idx].lastseg = rte_pktmbuf_lastseg(pkt);\n-\ttbl->items[item_idx].start_time = start_time;\n-\ttbl->items[item_idx].next_pkt_idx = INVALID_ARRAY_INDEX;\n-\ttbl->items[item_idx].sent_seq = sent_seq;\n-\ttbl->items[item_idx].ip_id = ip_id;\n-\ttbl->items[item_idx].nb_merged = 1;\n-\ttbl->items[item_idx].is_atomic = is_atomic;\n-\ttbl->item_num++;\n-\n-\t/* if the previous packet exists, chain them together. */\n-\tif (prev_idx != INVALID_ARRAY_INDEX) {\n-\t\ttbl->items[item_idx].next_pkt_idx =\n-\t\t\ttbl->items[prev_idx].next_pkt_idx;\n-\t\ttbl->items[prev_idx].next_pkt_idx = item_idx;\n-\t}\n-\n-\treturn item_idx;\n-}\n-\n-static inline uint32_t\n-delete_item(struct gro_tcp4_tbl *tbl, uint32_t item_idx,\n-\t\tuint32_t prev_item_idx)\n-{\n-\tuint32_t next_idx = tbl->items[item_idx].next_pkt_idx;\n-\n-\t/* NULL indicates an empty item */\n-\ttbl->items[item_idx].firstseg = NULL;\n-\ttbl->item_num--;\n-\tif (prev_item_idx != INVALID_ARRAY_INDEX)\n-\t\ttbl->items[prev_item_idx].next_pkt_idx = next_idx;\n-\n-\treturn next_idx;\n-}\n-\n static inline uint32_t\n insert_new_flow(struct gro_tcp4_tbl *tbl,\n \t\tstruct tcp4_flow_key *src,\n@@ -159,13 +97,10 @@ insert_new_flow(struct gro_tcp4_tbl *tbl,\n \n \tdst = &(tbl->flows[flow_idx].key);\n \n-\trte_ether_addr_copy(&(src->eth_saddr), &(dst->eth_saddr));\n-\trte_ether_addr_copy(&(src->eth_daddr), &(dst->eth_daddr));\n+\tASSIGN_COMMON_TCP_KEY((&src->cmn_key), (&dst->cmn_key));\n+\n \tdst->ip_src_addr = src->ip_src_addr;\n \tdst->ip_dst_addr = src->ip_dst_addr;\n-\tdst->recv_ack = src->recv_ack;\n-\tdst->src_port = src->src_port;\n-\tdst->dst_port = src->dst_port;\n \n \ttbl->flows[flow_idx].start_index = item_idx;\n \ttbl->flow_num++;\n@@ -173,21 +108,6 @@ insert_new_flow(struct gro_tcp4_tbl *tbl,\n \treturn flow_idx;\n }\n \n-/*\n- * update the packet length for the flushed packet.\n- */\n-static inline void\n-update_header(struct gro_tcp4_item *item)\n-{\n-\tstruct rte_ipv4_hdr *ipv4_hdr;\n-\tstruct rte_mbuf *pkt = item->firstseg;\n-\n-\tipv4_hdr = (struct rte_ipv4_hdr *)(rte_pktmbuf_mtod(pkt, char *) +\n-\t\t\tpkt->l2_len);\n-\tipv4_hdr->total_length = rte_cpu_to_be_16(pkt->pkt_len -\n-\t\t\tpkt->l2_len);\n-}\n-\n int32_t\n gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \t\tstruct gro_tcp4_tbl *tbl,\n@@ -202,9 +122,8 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \tuint8_t is_atomic;\n \n \tstruct tcp4_flow_key key;\n-\tuint32_t cur_idx, prev_idx, item_idx;\n+\tuint32_t item_idx;\n \tuint32_t i, max_flow_num, remaining_flow_num;\n-\tint cmp;\n \tuint8_t find;\n \n \t/*\n@@ -216,7 +135,7 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \n \teth_hdr = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *);\n \tipv4_hdr = (struct rte_ipv4_hdr *)((char *)eth_hdr + pkt->l2_len);\n-\ttcp_hdr = (struct rte_tcp_hdr *)((char *)ipv4_hdr + pkt->l3_len);\n+\ttcp_hdr = rte_pktmbuf_mtod_offset(pkt, struct rte_tcp_hdr *, pkt->l2_len + pkt->l3_len);\n \thdr_len = pkt->l2_len + pkt->l3_len + pkt->l4_len;\n \n \t/*\n@@ -230,7 +149,6 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \tip_tlen = rte_be_to_cpu_16(ipv4_hdr->total_length);\n \tif (pkt->pkt_len > (uint32_t)(ip_tlen + pkt->l2_len))\n \t\trte_pktmbuf_trim(pkt, pkt->pkt_len - ip_tlen - pkt->l2_len);\n-\n \t/*\n \t * Don't process the packet whose payload length is less than or\n \t * equal to 0.\n@@ -239,6 +157,13 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \tif (tcp_dl <= 0)\n \t\treturn -1;\n \n+\trte_ether_addr_copy(&(eth_hdr->src_addr), &(key.cmn_key.eth_saddr));\n+\trte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.cmn_key.eth_daddr));\n+\tkey.ip_src_addr = ipv4_hdr->src_addr;\n+\tkey.ip_dst_addr = ipv4_hdr->dst_addr;\n+\tkey.cmn_key.src_port = tcp_hdr->src_port;\n+\tkey.cmn_key.dst_port = tcp_hdr->dst_port;\n+\tkey.cmn_key.recv_ack = tcp_hdr->recv_ack;\n \t/*\n \t * Save IPv4 ID for the packet whose DF bit is 0. For the packet\n \t * whose DF bit is 1, IPv4 ID is ignored.\n@@ -246,15 +171,6 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \tfrag_off = rte_be_to_cpu_16(ipv4_hdr->fragment_offset);\n \tis_atomic = (frag_off & RTE_IPV4_HDR_DF_FLAG) == RTE_IPV4_HDR_DF_FLAG;\n \tip_id = is_atomic ? 0 : rte_be_to_cpu_16(ipv4_hdr->packet_id);\n-\tsent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);\n-\n-\trte_ether_addr_copy(&(eth_hdr->src_addr), &(key.eth_saddr));\n-\trte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.eth_daddr));\n-\tkey.ip_src_addr = ipv4_hdr->src_addr;\n-\tkey.ip_dst_addr = ipv4_hdr->dst_addr;\n-\tkey.src_port = tcp_hdr->src_port;\n-\tkey.dst_port = tcp_hdr->dst_port;\n-\tkey.recv_ack = tcp_hdr->recv_ack;\n \n \t/* Search for a matched flow. */\n \tmax_flow_num = tbl->max_flow_num;\n@@ -270,63 +186,44 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,\n \t\t}\n \t}\n \n-\t/*\n-\t * Fail to find a matched flow. Insert a new flow and store the\n-\t * packet into the flow.\n-\t */\n \tif (find == 0) {\n-\t\titem_idx = insert_new_item(tbl, pkt, start_time,\n-\t\t\t\tINVALID_ARRAY_INDEX, sent_seq, ip_id,\n-\t\t\t\tis_atomic);\n+\t\tsent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);\n+\t\titem_idx = insert_new_tcp_item(pkt, tbl->items, &tbl->item_num,\n+\t\t\t\t\t\ttbl->max_item_num, start_time,\n+\t\t\t\t\t\tINVALID_ARRAY_INDEX, sent_seq, ip_id,\n+\t\t\t\t\t\tis_atomic);\n \t\tif (item_idx == INVALID_ARRAY_INDEX)\n \t\t\treturn -1;\n \t\tif (insert_new_flow(tbl, &key, item_idx) ==\n-\t\t\t\tINVALID_ARRAY_INDEX) {\n+\t\t\tINVALID_ARRAY_INDEX) {\n \t\t\t/*\n \t\t\t * Fail to insert a new flow, so delete the\n \t\t\t * stored packet.\n-\t\t\t */\n-\t\t\tdelete_item(tbl, item_idx, INVALID_ARRAY_INDEX);\n+\t\t\t*/\n+\t\t\tdelete_tcp_item(tbl->items, item_idx, &tbl->item_num, INVALID_ARRAY_INDEX);\n \t\t\treturn -1;\n \t\t}\n \t\treturn 0;\n \t}\n \n-\t/*\n-\t * Check all packets in the flow and try to find a neighbor for\n-\t * the input packet.\n-\t */\n-\tcur_idx = tbl->flows[i].start_index;\n-\tprev_idx = cur_idx;\n-\tdo {\n-\t\tcmp = check_seq_option(&(tbl->items[cur_idx]), tcp_hdr,\n-\t\t\t\tsent_seq, ip_id, pkt->l4_len, tcp_dl, 0,\n-\t\t\t\tis_atomic);\n-\t\tif (cmp) {\n-\t\t\tif (merge_two_tcp4_packets(&(tbl->items[cur_idx]),\n-\t\t\t\t\t\tpkt, cmp, sent_seq, ip_id, 0))\n-\t\t\t\treturn 1;\n-\t\t\t/*\n-\t\t\t * Fail to merge the two packets, as the packet\n-\t\t\t * length is greater than the max value. Store\n-\t\t\t * the packet into the flow.\n-\t\t\t */\n-\t\t\tif (insert_new_item(tbl, pkt, start_time, cur_idx,\n-\t\t\t\t\t\tsent_seq, ip_id, is_atomic) ==\n-\t\t\t\t\tINVALID_ARRAY_INDEX)\n-\t\t\t\treturn -1;\n-\t\t\treturn 0;\n-\t\t}\n-\t\tprev_idx = cur_idx;\n-\t\tcur_idx = tbl->items[cur_idx].next_pkt_idx;\n-\t} while (cur_idx != INVALID_ARRAY_INDEX);\n+\treturn process_tcp_item(pkt, tcp_hdr, tcp_dl, tbl->items, tbl->flows[i].start_index,\n+\t\t\t\t\t\t&tbl->item_num, tbl->max_item_num,\n+\t\t\t\t\t\tip_id, is_atomic, start_time);\n+}\n \n-\t/* Fail to find a neighbor, so store the packet into the flow. */\n-\tif (insert_new_item(tbl, pkt, start_time, prev_idx, sent_seq,\n-\t\t\t\tip_id, is_atomic) == INVALID_ARRAY_INDEX)\n-\t\treturn -1;\n+/*\n+ * update the packet length for the flushed packet.\n+ */\n+static inline void\n+update_header(struct gro_tcp_item *item)\n+{\n+\tstruct rte_ipv4_hdr *ipv4_hdr;\n+\tstruct rte_mbuf *pkt = item->firstseg;\n \n-\treturn 0;\n+\tipv4_hdr = (struct rte_ipv4_hdr *)(rte_pktmbuf_mtod(pkt, char *) +\n+\t\t\tpkt->l2_len);\n+\tipv4_hdr->total_length = rte_cpu_to_be_16(pkt->pkt_len -\n+\t\t\tpkt->l2_len);\n }\n \n uint16_t\n@@ -353,7 +250,8 @@ gro_tcp4_tbl_timeout_flush(struct gro_tcp4_tbl *tbl,\n \t\t\t\t * Delete the packet and get the next\n \t\t\t\t * packet in the flow.\n \t\t\t\t */\n-\t\t\t\tj = delete_item(tbl, j, INVALID_ARRAY_INDEX);\n+\t\t\t\tj = delete_tcp_item(tbl->items, j,\n+\t\t\t\t\t\t\t&tbl->item_num, INVALID_ARRAY_INDEX);\n \t\t\t\ttbl->flows[i].start_index = j;\n \t\t\t\tif (j == INVALID_ARRAY_INDEX)\n \t\t\t\t\ttbl->flow_num--;\ndiff --git a/lib/gro/gro_tcp4.h b/lib/gro/gro_tcp4.h\nindex 212f97a042..c0154afa24 100644\n--- a/lib/gro/gro_tcp4.h\n+++ b/lib/gro/gro_tcp4.h\n@@ -5,32 +5,15 @@\n #ifndef _GRO_TCP4_H_\n #define _GRO_TCP4_H_\n \n-#include <rte_tcp.h>\n+#include <gro_tcp_internal.h>\n \n-#define INVALID_ARRAY_INDEX 0xffffffffUL\n #define GRO_TCP4_TBL_MAX_ITEM_NUM (1024UL * 1024UL)\n \n-/*\n- * The max length of a IPv4 packet, which includes the length of the L3\n- * header, the L4 header and the data payload.\n- */\n-#define MAX_IPV4_PKT_LENGTH UINT16_MAX\n-\n-/* The maximum TCP header length */\n-#define MAX_TCP_HLEN 60\n-#define INVALID_TCP_HDRLEN(len) \\\n-\t(((len) < sizeof(struct rte_tcp_hdr)) || ((len) > MAX_TCP_HLEN))\n-\n-/* Header fields representing a TCP/IPv4 flow */\n+/* Header fields representing common fields in TCP flow */\n struct tcp4_flow_key {\n-\tstruct rte_ether_addr eth_saddr;\n-\tstruct rte_ether_addr eth_daddr;\n+\tstruct cmn_tcp_key cmn_key;\n \tuint32_t ip_src_addr;\n \tuint32_t ip_dst_addr;\n-\n-\tuint32_t recv_ack;\n-\tuint16_t src_port;\n-\tuint16_t dst_port;\n };\n \n struct gro_tcp4_flow {\n@@ -42,42 +25,12 @@ struct gro_tcp4_flow {\n \tuint32_t start_index;\n };\n \n-struct gro_tcp4_item {\n-\t/*\n-\t * The first MBUF segment of the packet. If the value\n-\t * is NULL, it means the item is empty.\n-\t */\n-\tstruct rte_mbuf *firstseg;\n-\t/* The last MBUF segment of the packet */\n-\tstruct rte_mbuf *lastseg;\n-\t/*\n-\t * The time when the first packet is inserted into the table.\n-\t * This value won't be updated, even if the packet is merged\n-\t * with other packets.\n-\t */\n-\tuint64_t start_time;\n-\t/*\n-\t * next_pkt_idx is used to chain the packets that\n-\t * are in the same flow but can't be merged together\n-\t * (e.g. caused by packet reordering).\n-\t */\n-\tuint32_t next_pkt_idx;\n-\t/* TCP sequence number of the packet */\n-\tuint32_t sent_seq;\n-\t/* IPv4 ID of the packet */\n-\tuint16_t ip_id;\n-\t/* the number of merged packets */\n-\tuint16_t nb_merged;\n-\t/* Indicate if IPv4 ID can be ignored */\n-\tuint8_t is_atomic;\n-};\n-\n /*\n  * TCP/IPv4 reassembly table structure.\n  */\n struct gro_tcp4_tbl {\n \t/* item array */\n-\tstruct gro_tcp4_item *items;\n+\tstruct gro_tcp_item *items;\n \t/* flow array */\n \tstruct gro_tcp4_flow *flows;\n \t/* current item number */\n@@ -186,120 +139,9 @@ uint32_t gro_tcp4_tbl_pkt_count(void *tbl);\n static inline int\n is_same_tcp4_flow(struct tcp4_flow_key k1, struct tcp4_flow_key k2)\n {\n-\treturn (rte_is_same_ether_addr(&k1.eth_saddr, &k2.eth_saddr) &&\n-\t\t\trte_is_same_ether_addr(&k1.eth_daddr, &k2.eth_daddr) &&\n-\t\t\t(k1.ip_src_addr == k2.ip_src_addr) &&\n+\treturn ((k1.ip_src_addr == k2.ip_src_addr) &&\n \t\t\t(k1.ip_dst_addr == k2.ip_dst_addr) &&\n-\t\t\t(k1.recv_ack == k2.recv_ack) &&\n-\t\t\t(k1.src_port == k2.src_port) &&\n-\t\t\t(k1.dst_port == k2.dst_port));\n+\t\t\tis_common_tcp_key(&k1.cmn_key, &k2.cmn_key));\n }\n \n-/*\n- * Merge two TCP/IPv4 packets without updating checksums.\n- * If cmp is larger than 0, append the new packet to the\n- * original packet. Otherwise, pre-pend the new packet to\n- * the original packet.\n- */\n-static inline int\n-merge_two_tcp4_packets(struct gro_tcp4_item *item,\n-\t\tstruct rte_mbuf *pkt,\n-\t\tint cmp,\n-\t\tuint32_t sent_seq,\n-\t\tuint16_t ip_id,\n-\t\tuint16_t l2_offset)\n-{\n-\tstruct rte_mbuf *pkt_head, *pkt_tail, *lastseg;\n-\tuint16_t hdr_len, l2_len;\n-\n-\tif (cmp > 0) {\n-\t\tpkt_head = item->firstseg;\n-\t\tpkt_tail = pkt;\n-\t} else {\n-\t\tpkt_head = pkt;\n-\t\tpkt_tail = item->firstseg;\n-\t}\n-\n-\t/* check if the IPv4 packet length is greater than the max value */\n-\thdr_len = l2_offset + pkt_head->l2_len + pkt_head->l3_len +\n-\t\tpkt_head->l4_len;\n-\tl2_len = l2_offset > 0 ? pkt_head->outer_l2_len : pkt_head->l2_len;\n-\tif (unlikely(pkt_head->pkt_len - l2_len + pkt_tail->pkt_len -\n-\t\t\t\thdr_len > MAX_IPV4_PKT_LENGTH))\n-\t\treturn 0;\n-\n-\t/* remove the packet header for the tail packet */\n-\trte_pktmbuf_adj(pkt_tail, hdr_len);\n-\n-\t/* chain two packets together */\n-\tif (cmp > 0) {\n-\t\titem->lastseg->next = pkt;\n-\t\titem->lastseg = rte_pktmbuf_lastseg(pkt);\n-\t\t/* update IP ID to the larger value */\n-\t\titem->ip_id = ip_id;\n-\t} else {\n-\t\tlastseg = rte_pktmbuf_lastseg(pkt);\n-\t\tlastseg->next = item->firstseg;\n-\t\titem->firstseg = pkt;\n-\t\t/* update sent_seq to the smaller value */\n-\t\titem->sent_seq = sent_seq;\n-\t\titem->ip_id = ip_id;\n-\t}\n-\titem->nb_merged++;\n-\n-\t/* update MBUF metadata for the merged packet */\n-\tpkt_head->nb_segs += pkt_tail->nb_segs;\n-\tpkt_head->pkt_len += pkt_tail->pkt_len;\n-\n-\treturn 1;\n-}\n-\n-/*\n- * Check if two TCP/IPv4 packets are neighbors.\n- */\n-static inline int\n-check_seq_option(struct gro_tcp4_item *item,\n-\t\tstruct rte_tcp_hdr *tcph,\n-\t\tuint32_t sent_seq,\n-\t\tuint16_t ip_id,\n-\t\tuint16_t tcp_hl,\n-\t\tuint16_t tcp_dl,\n-\t\tuint16_t l2_offset,\n-\t\tuint8_t is_atomic)\n-{\n-\tstruct rte_mbuf *pkt_orig = item->firstseg;\n-\tstruct rte_ipv4_hdr *iph_orig;\n-\tstruct rte_tcp_hdr *tcph_orig;\n-\tuint16_t len, tcp_hl_orig;\n-\n-\tiph_orig = (struct rte_ipv4_hdr *)(rte_pktmbuf_mtod(pkt_orig, char *) +\n-\t\t\tl2_offset + pkt_orig->l2_len);\n-\ttcph_orig = (struct rte_tcp_hdr *)((char *)iph_orig + pkt_orig->l3_len);\n-\ttcp_hl_orig = pkt_orig->l4_len;\n-\n-\t/* Check if TCP option fields equal */\n-\tlen = RTE_MAX(tcp_hl, tcp_hl_orig) - sizeof(struct rte_tcp_hdr);\n-\tif ((tcp_hl != tcp_hl_orig) || ((len > 0) &&\n-\t\t\t\t(memcmp(tcph + 1, tcph_orig + 1,\n-\t\t\t\t\tlen) != 0)))\n-\t\treturn 0;\n-\n-\t/* Don't merge packets whose DF bits are different */\n-\tif (unlikely(item->is_atomic ^ is_atomic))\n-\t\treturn 0;\n-\n-\t/* check if the two packets are neighbors */\n-\tlen = pkt_orig->pkt_len - l2_offset - pkt_orig->l2_len -\n-\t\tpkt_orig->l3_len - tcp_hl_orig;\n-\tif ((sent_seq == item->sent_seq + len) && (is_atomic ||\n-\t\t\t\t(ip_id == item->ip_id + 1)))\n-\t\t/* append the new packet */\n-\t\treturn 1;\n-\telse if ((sent_seq + tcp_dl == item->sent_seq) && (is_atomic ||\n-\t\t\t\t(ip_id + item->nb_merged == item->ip_id)))\n-\t\t/* pre-pend the new packet */\n-\t\treturn -1;\n-\n-\treturn 0;\n-}\n #endif\ndiff --git a/lib/gro/gro_tcp6.c b/lib/gro/gro_tcp6.c\nnew file mode 100644\nindex 0000000000..0ea73741c1\n--- /dev/null\n+++ b/lib/gro/gro_tcp6.c\n@@ -0,0 +1,267 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2017 Intel Corporation\n+ */\n+\n+#include <rte_malloc.h>\n+#include <rte_mbuf.h>\n+#include <rte_ethdev.h>\n+\n+#include \"gro_tcp6.h\"\n+\n+void *\n+gro_tcp6_tbl_create(uint16_t socket_id,\n+\t\tuint16_t max_flow_num,\n+\t\tuint16_t max_item_per_flow)\n+{\n+\tstruct gro_tcp6_tbl *tbl;\n+\tsize_t size;\n+\tuint32_t entries_num, i;\n+\n+\tentries_num = max_flow_num * max_item_per_flow;\n+\tentries_num = RTE_MIN(entries_num, GRO_TCP6_TBL_MAX_ITEM_NUM);\n+\n+\tif (entries_num == 0)\n+\t\treturn NULL;\n+\n+\ttbl = rte_zmalloc_socket(__func__,\n+\t\t\tsizeof(struct gro_tcp6_tbl),\n+\t\t\tRTE_CACHE_LINE_SIZE,\n+\t\t\tsocket_id);\n+\tif (tbl == NULL)\n+\t\treturn NULL;\n+\n+\tsize = sizeof(struct gro_tcp_item) * entries_num;\n+\ttbl->items = rte_zmalloc_socket(__func__,\n+\t\t\tsize,\n+\t\t\tRTE_CACHE_LINE_SIZE,\n+\t\t\tsocket_id);\n+\tif (tbl->items == NULL) {\n+\t\trte_free(tbl);\n+\t\treturn NULL;\n+\t}\n+\ttbl->max_item_num = entries_num;\n+\n+\tsize = sizeof(struct gro_tcp6_flow) * entries_num;\n+\ttbl->flows = rte_zmalloc_socket(__func__,\n+\t\t\tsize,\n+\t\t\tRTE_CACHE_LINE_SIZE,\n+\t\t\tsocket_id);\n+\tif (tbl->flows == NULL) {\n+\t\trte_free(tbl->items);\n+\t\trte_free(tbl);\n+\t\treturn NULL;\n+\t}\n+\t/* INVALID_ARRAY_INDEX indicates an empty flow */\n+\tfor (i = 0; i < entries_num; i++)\n+\t\ttbl->flows[i].start_index = INVALID_ARRAY_INDEX;\n+\ttbl->max_flow_num = entries_num;\n+\n+\treturn tbl;\n+}\n+\n+void\n+gro_tcp6_tbl_destroy(void *tbl)\n+{\n+\tstruct gro_tcp6_tbl *tcp_tbl = tbl;\n+\n+\tif (tcp_tbl) {\n+\t\trte_free(tcp_tbl->items);\n+\t\trte_free(tcp_tbl->flows);\n+\t}\n+\trte_free(tcp_tbl);\n+}\n+\n+static inline uint32_t\n+find_an_empty_flow(struct gro_tcp6_tbl *tbl)\n+{\n+\tuint32_t i;\n+\tuint32_t max_flow_num = tbl->max_flow_num;\n+\n+\tfor (i = 0; i < max_flow_num; i++)\n+\t\tif (tbl->flows[i].start_index == INVALID_ARRAY_INDEX)\n+\t\t\treturn i;\n+\treturn INVALID_ARRAY_INDEX;\n+}\n+\n+static inline uint32_t\n+insert_new_flow(struct gro_tcp6_tbl *tbl,\n+\t\tstruct tcp6_flow_key *src,\n+\t\tuint32_t item_idx)\n+{\n+\tstruct tcp6_flow_key *dst;\n+\tuint32_t flow_idx;\n+\n+\tflow_idx = find_an_empty_flow(tbl);\n+\tif (unlikely(flow_idx == INVALID_ARRAY_INDEX))\n+\t\treturn INVALID_ARRAY_INDEX;\n+\n+\tdst = &(tbl->flows[flow_idx].key);\n+\n+\tASSIGN_COMMON_TCP_KEY((&src->cmn_key), (&dst->cmn_key));\n+\tmemcpy(&dst->src_addr[0], &src->src_addr[0], sizeof(dst->src_addr));\n+\tmemcpy(&dst->dst_addr[0], &src->dst_addr[0], sizeof(dst->dst_addr));\n+\tdst->vtc_flow = src->vtc_flow;\n+\n+\ttbl->flows[flow_idx].start_index = item_idx;\n+\ttbl->flow_num++;\n+\n+\treturn flow_idx;\n+}\n+\n+/*\n+ * update the packet length for the flushed packet.\n+ */\n+static inline void\n+update_header(struct gro_tcp_item *item)\n+{\n+\tstruct rte_ipv6_hdr *ipv6_hdr;\n+\tstruct rte_mbuf *pkt = item->firstseg;\n+\n+\tipv6_hdr = (struct rte_ipv6_hdr *)(rte_pktmbuf_mtod(pkt, char *) +\n+\t\t\tpkt->l2_len);\n+\tipv6_hdr->payload_len = rte_cpu_to_be_16(pkt->pkt_len -\n+\t\t\tpkt->l2_len - pkt->l3_len);\n+}\n+\n+int32_t\n+gro_tcp6_reassemble(struct rte_mbuf *pkt,\n+\t\tstruct gro_tcp6_tbl *tbl,\n+\t\tuint64_t start_time)\n+{\n+\tstruct rte_ether_hdr *eth_hdr;\n+\tstruct rte_ipv6_hdr *ipv6_hdr;\n+\tint32_t tcp_dl;\n+\tuint16_t ip_tlen;\n+\tstruct tcp6_flow_key key;\n+\tuint32_t i, max_flow_num, remaining_flow_num;\n+\tuint32_t sent_seq;\n+\tstruct rte_tcp_hdr *tcp_hdr;\n+\tuint8_t find;\n+\tuint32_t item_idx;\n+\t/*\n+\t * Don't process the packet whose TCP header length is greater\n+\t * than 60 bytes or less than 20 bytes.\n+\t */\n+\tif (unlikely(INVALID_TCP_HDRLEN(pkt->l4_len)))\n+\t\treturn -1;\n+\n+\teth_hdr = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *);\n+\tipv6_hdr = (struct rte_ipv6_hdr *)((char *)eth_hdr + pkt->l2_len);\n+\ttcp_hdr = rte_pktmbuf_mtod_offset(pkt, struct rte_tcp_hdr *, pkt->l2_len + pkt->l3_len);\n+\n+\t/*\n+\t * Don't process the packet which has FIN, SYN, RST, PSH, URG, ECE\n+\t * or CWR set.\n+\t */\n+\tif (tcp_hdr->tcp_flags != RTE_TCP_ACK_FLAG)\n+\t\treturn -1;\n+\n+\tip_tlen = rte_be_to_cpu_16(ipv6_hdr->payload_len);\n+\t/*\n+\t * Don't process the packet whose payload length is less than or\n+\t * equal to 0.\n+\t */\n+\ttcp_dl = ip_tlen - pkt->l4_len;\n+\tif (tcp_dl <= 0)\n+\t\treturn -1;\n+\n+\trte_ether_addr_copy(&(eth_hdr->src_addr), &(key.cmn_key.eth_saddr));\n+\trte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.cmn_key.eth_daddr));\n+\tmemcpy(&key.src_addr[0], &ipv6_hdr->src_addr, sizeof(key.src_addr));\n+\tmemcpy(&key.dst_addr[0], &ipv6_hdr->dst_addr, sizeof(key.dst_addr));\n+\tkey.cmn_key.src_port = tcp_hdr->src_port;\n+\tkey.cmn_key.dst_port = tcp_hdr->dst_port;\n+\tkey.cmn_key.recv_ack = tcp_hdr->recv_ack;\n+\tkey.vtc_flow = ipv6_hdr->vtc_flow;\n+\n+\t/* Search for a matched flow. */\n+\tmax_flow_num = tbl->max_flow_num;\n+\tremaining_flow_num = tbl->flow_num;\n+\tfind = 0;\n+\tfor (i = 0; i < max_flow_num && remaining_flow_num; i++) {\n+\t\tif (tbl->flows[i].start_index != INVALID_ARRAY_INDEX) {\n+\t\t\tif (is_same_tcp6_flow(&tbl->flows[i].key, &key)) {\n+\t\t\t\tfind = 1;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tremaining_flow_num--;\n+\t\t}\n+\t}\n+\n+\tif (find == 0) {\n+\t\tsent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);\n+\t\titem_idx = insert_new_tcp_item(pkt, tbl->items, &tbl->item_num,\n+\t\t\t\t\t\ttbl->max_item_num, start_time,\n+\t\t\t\t\t\tINVALID_ARRAY_INDEX, sent_seq, 0, true);\n+\t\tif (item_idx == INVALID_ARRAY_INDEX)\n+\t\t\treturn -1;\n+\t\tif (insert_new_flow(tbl, &key, item_idx) ==\n+\t\t\tINVALID_ARRAY_INDEX) {\n+\t\t\t/*\n+\t\t\t * Fail to insert a new flow, so delete the\n+\t\t\t * stored packet.\n+\t\t\t */\n+\t\t\tdelete_tcp_item(tbl->items, item_idx, &tbl->item_num, INVALID_ARRAY_INDEX);\n+\t\t\treturn -1;\n+\t\t}\n+\t\treturn 0;\n+\t}\n+\n+\treturn process_tcp_item(pkt, tcp_hdr, tcp_dl, tbl->items, tbl->flows[i].start_index,\n+\t\t\t\t\t\t&tbl->item_num, tbl->max_item_num,\n+\t\t\t\t\t\t0, true, start_time);\n+}\n+\n+uint16_t\n+gro_tcp6_tbl_timeout_flush(struct gro_tcp6_tbl *tbl,\n+\t\tuint64_t flush_timestamp,\n+\t\tstruct rte_mbuf **out,\n+\t\tuint16_t nb_out)\n+{\n+\tuint16_t k = 0;\n+\tuint32_t i, j;\n+\tuint32_t max_flow_num = tbl->max_flow_num;\n+\n+\tfor (i = 0; i < max_flow_num; i++) {\n+\t\tif (unlikely(tbl->flow_num == 0))\n+\t\t\treturn k;\n+\n+\t\tj = tbl->flows[i].start_index;\n+\t\twhile (j != INVALID_ARRAY_INDEX) {\n+\t\t\tif (tbl->items[j].start_time <= flush_timestamp) {\n+\t\t\t\tout[k++] = tbl->items[j].firstseg;\n+\t\t\t\tif (tbl->items[j].nb_merged > 1)\n+\t\t\t\t\tupdate_header(&(tbl->items[j]));\n+\t\t\t\t/*\n+\t\t\t\t * Delete the packet and get the next\n+\t\t\t\t * packet in the flow.\n+\t\t\t\t */\n+\t\t\t\tj = delete_tcp_item(tbl->items, j,\n+\t\t\t\t\t\t&tbl->item_num, INVALID_ARRAY_INDEX);\n+\t\t\t\ttbl->flows[i].start_index = j;\n+\t\t\t\tif (j == INVALID_ARRAY_INDEX)\n+\t\t\t\t\ttbl->flow_num--;\n+\n+\t\t\t\tif (unlikely(k == nb_out))\n+\t\t\t\t\treturn k;\n+\t\t\t} else\n+\t\t\t\t/*\n+\t\t\t\t * The left packets in this flow won't be\n+\t\t\t\t * timeout. Go to check other flows.\n+\t\t\t\t */\n+\t\t\t\tbreak;\n+\t\t}\n+\t}\n+\treturn k;\n+}\n+\n+uint32_t\n+gro_tcp6_tbl_pkt_count(void *tbl)\n+{\n+\tstruct gro_tcp6_tbl *gro_tbl = tbl;\n+\n+\tif (gro_tbl)\n+\t\treturn gro_tbl->item_num;\n+\n+\treturn 0;\n+}\ndiff --git a/lib/gro/gro_tcp6.h b/lib/gro/gro_tcp6.h\nnew file mode 100644\nindex 0000000000..cc02b0004a\n--- /dev/null\n+++ b/lib/gro/gro_tcp6.h\n@@ -0,0 +1,161 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2017 Intel Corporation\n+ */\n+\n+#ifndef _GRO_TCP6_H_\n+#define _GRO_TCP6_H_\n+\n+#include <gro_tcp_internal.h>\n+\n+#define INVALID_ARRAY_INDEX 0xffffffffUL\n+#define GRO_TCP6_TBL_MAX_ITEM_NUM (1024UL * 1024UL)\n+\n+/* Header fields representing a TCP/IPv6 flow */\n+struct tcp6_flow_key {\n+\tstruct cmn_tcp_key cmn_key;\n+\tuint8_t  src_addr[16];\n+\tuint8_t  dst_addr[16];\n+\trte_be32_t vtc_flow;\n+};\n+\n+struct gro_tcp6_flow {\n+\tstruct tcp6_flow_key key;\n+\t/*\n+\t * The index of the first packet in the flow.\n+\t * INVALID_ARRAY_INDEX indicates an empty flow.\n+\t */\n+\tuint32_t start_index;\n+};\n+\n+/*\n+ * TCP/IPv6 reassembly table structure.\n+ */\n+struct gro_tcp6_tbl {\n+\t/* item array */\n+\tstruct gro_tcp_item *items;\n+\t/* flow array */\n+\tstruct gro_tcp6_flow *flows;\n+\t/* current item number */\n+\tuint32_t item_num;\n+\t/* current flow num */\n+\tuint32_t flow_num;\n+\t/* item array size */\n+\tuint32_t max_item_num;\n+\t/* flow array size */\n+\tuint32_t max_flow_num;\n+};\n+\n+/**\n+ * This function creates a TCP/IPv6 reassembly table.\n+ *\n+ * @param socket_id\n+ *  Socket index for allocating the TCP/IPv6 reassemble table\n+ * @param max_flow_num\n+ *  The maximum number of flows in the TCP/IPv6 GRO table\n+ * @param max_item_per_flow\n+ *  The maximum number of packets per flow\n+ *\n+ * @return\n+ *  - Return the table pointer on success.\n+ *  - Return NULL on failure.\n+ */\n+void *gro_tcp6_tbl_create(uint16_t socket_id,\n+\t\tuint16_t max_flow_num,\n+\t\tuint16_t max_item_per_flow);\n+\n+/**\n+ * This function destroys a TCP/IPv6 reassembly table.\n+ *\n+ * @param tbl\n+ *  Pointer pointing to the TCP/IPv6 reassembly table.\n+ */\n+void gro_tcp6_tbl_destroy(void *tbl);\n+\n+/**\n+ * This function merges a TCP/IPv6 packet. It doesn't process the packet,\n+ * which has SYN, FIN, RST, PSH, CWR, ECE or URG set, or doesn't have\n+ * payload.\n+ *\n+ * This function doesn't check if the packet has correct checksums and\n+ * doesn't re-calculate checksums for the merged packet. Additionally,\n+ * it assumes the packets are complete (i.e., MF==0 && frag_off==0),\n+ * when IP fragmentation is possible (i.e., DF==0). It returns the\n+ * packet, if the packet has invalid parameters (e.g. SYN bit is set)\n+ * or there is no available space in the table.\n+ *\n+ * @param pkt\n+ *  Packet to reassemble\n+ * @param tbl\n+ *  Pointer pointing to the TCP/IPv6 reassembly table\n+ * @start_time\n+ *  The time when the packet is inserted into the table\n+ *\n+ * @return\n+ *  - Return a positive value if the packet is merged.\n+ *  - Return zero if the packet isn't merged but stored in the table.\n+ *  - Return a negative value for invalid parameters or no available\n+ *    space in the table.\n+ */\n+int32_t gro_tcp6_reassemble(struct rte_mbuf *pkt,\n+\t\tstruct gro_tcp6_tbl *tbl,\n+\t\tuint64_t start_time);\n+\n+/**\n+ * This function flushes timeout packets in a TCP/IPv6 reassembly table,\n+ * and without updating checksums.\n+ *\n+ * @param tbl\n+ *  TCP/IPv6 reassembly table pointer\n+ * @param flush_timestamp\n+ *  Flush packets which are inserted into the table before or at the\n+ *  flush_timestamp.\n+ * @param out\n+ *  Pointer array used to keep flushed packets\n+ * @param nb_out\n+ *  The element number in 'out'. It also determines the maximum number of\n+ *  packets that can be flushed finally.\n+ *\n+ * @return\n+ *  The number of flushed packets\n+ */\n+uint16_t gro_tcp6_tbl_timeout_flush(struct gro_tcp6_tbl *tbl,\n+\t\tuint64_t flush_timestamp,\n+\t\tstruct rte_mbuf **out,\n+\t\tuint16_t nb_out);\n+\n+/**\n+ * This function returns the number of the packets in a TCP/IPv6\n+ * reassembly table.\n+ *\n+ * @param tbl\n+ *  TCP/IPv6 reassembly table pointer\n+ *\n+ * @return\n+ *  The number of packets in the table\n+ */\n+uint32_t gro_tcp6_tbl_pkt_count(void *tbl);\n+\n+/*\n+ * Check if two TCP/IPv6 packets belong to the same flow.\n+ */\n+static inline int\n+is_same_tcp6_flow(struct tcp6_flow_key *k1, struct tcp6_flow_key *k2)\n+{\n+\trte_be32_t vtc_flow_diff;\n+\n+\tif (memcmp(&k1->src_addr, &k2->src_addr, 16))\n+\t\treturn 0;\n+\tif (memcmp(&k1->dst_addr, &k2->dst_addr, 16))\n+\t\treturn 0;\n+\t/*\n+\t * IP version (4) Traffic Class (8) Flow Label (20)\n+\t * All fields except Traffic class should be same\n+\t */\n+\tvtc_flow_diff = (k1->vtc_flow ^ k2->vtc_flow);\n+\tif (vtc_flow_diff & htonl(0xF00FFFFF))\n+\t\treturn 0;\n+\n+\treturn is_common_tcp_key(&k1->cmn_key, &k2->cmn_key);\n+}\n+\n+#endif\ndiff --git a/lib/gro/gro_tcp_internal.c b/lib/gro/gro_tcp_internal.c\nnew file mode 100644\nindex 0000000000..5a21bca7f8\n--- /dev/null\n+++ b/lib/gro/gro_tcp_internal.c\n@@ -0,0 +1,128 @@\n+/* SPDX-License-Identifier: BSD-3-Clause\n+ * Copyright(c) 2017 Intel Corporation\n+ */\n+#include <rte_malloc.h>\n+#include <rte_mbuf.h>\n+#include <rte_ethdev.h>\n+\n+#include \"gro_tcp_internal.h\"\n+\n+static inline uint32_t\n+find_an_empty_item(struct gro_tcp_item *items,\n+\tuint32_t max_item_num)\n+{\n+\tuint32_t i;\n+\n+\tfor (i = 0; i < max_item_num; i++)\n+\t\tif (items[i].firstseg == NULL)\n+\t\t\treturn i;\n+\treturn INVALID_ARRAY_INDEX;\n+}\n+\n+inline uint32_t\n+insert_new_tcp_item(struct rte_mbuf *pkt,\n+\t\tstruct gro_tcp_item *items,\n+\t\tuint32_t *item_num,\n+\t\tuint32_t max_item_num,\n+\t\tuint64_t start_time,\n+\t\tuint32_t prev_idx,\n+\t\tuint32_t sent_seq,\n+\t\tuint16_t ip_id,\n+\t\tuint8_t is_atomic)\n+{\n+\tuint32_t item_idx;\n+\n+\titem_idx = find_an_empty_item(items, max_item_num);\n+\tif (item_idx == INVALID_ARRAY_INDEX)\n+\t\treturn INVALID_ARRAY_INDEX;\n+\n+\titems[item_idx].firstseg = pkt;\n+\titems[item_idx].lastseg = rte_pktmbuf_lastseg(pkt);\n+\titems[item_idx].start_time = start_time;\n+\titems[item_idx].next_pkt_idx = INVALID_ARRAY_INDEX;\n+\titems[item_idx].sent_seq = sent_seq;\n+\titems[item_idx].l3.ip_id = ip_id;\n+\titems[item_idx].nb_merged = 1;\n+\titems[item_idx].is_atomic = is_atomic;\n+\t(*item_num) += 1;\n+\n+\t/* if the previous packet exists, chain them together. */\n+\tif (prev_idx != INVALID_ARRAY_INDEX) {\n+\t\titems[item_idx].next_pkt_idx =\n+\t\t\titems[prev_idx].next_pkt_idx;\n+\t\titems[prev_idx].next_pkt_idx = item_idx;\n+\t}\n+\n+\treturn item_idx;\n+}\n+\n+inline uint32_t\n+delete_tcp_item(struct gro_tcp_item *items, uint32_t item_idx,\n+\t\tuint32_t *item_num,\n+\t\tuint32_t prev_item_idx)\n+{\n+\tuint32_t next_idx = items[item_idx].next_pkt_idx;\n+\n+\t/* NULL indicates an empty item */\n+\titems[item_idx].firstseg = NULL;\n+\t(*item_num) -= 1;\n+\tif (prev_item_idx != INVALID_ARRAY_INDEX)\n+\t\titems[prev_item_idx].next_pkt_idx = next_idx;\n+\n+\treturn next_idx;\n+}\n+\n+int32_t\n+process_tcp_item(struct rte_mbuf *pkt,\n+\tstruct rte_tcp_hdr *tcp_hdr,\n+\tint32_t tcp_dl,\n+\tstruct gro_tcp_item *items,\n+\tuint32_t item_idx,\n+\tuint32_t *item_num,\n+\tuint32_t max_item_num,\n+\tuint16_t ip_id,\n+\tuint8_t is_atomic,\n+\tuint64_t start_time)\n+{\n+\tuint32_t cur_idx;\n+\tuint32_t prev_idx;\n+\tint cmp;\n+\tuint32_t sent_seq;\n+\n+\tsent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);\n+\t/*\n+\t * Check all packets in the flow and try to find a neighbor for\n+\t * the input packet.\n+\t */\n+\tcur_idx = item_idx;\n+\tprev_idx = cur_idx;\n+\tdo {\n+\t\tcmp = check_seq_option(&items[cur_idx], tcp_hdr,\n+\t\t\t\tsent_seq, ip_id, pkt->l4_len, tcp_dl, 0,\n+\t\t\t\tis_atomic);\n+\t\tif (cmp) {\n+\t\t\tif (merge_two_tcp_packets(&items[cur_idx],\n+\t\t\t\t\t\tpkt, cmp, sent_seq, ip_id, 0))\n+\t\t\t\treturn 1;\n+\t\t\t/*\n+\t\t\t * Fail to merge the two packets, as the packet\n+\t\t\t * length is greater than the max value. Store\n+\t\t\t * the packet into the flow.\n+\t\t\t */\n+\t\t\tif (insert_new_tcp_item(pkt, items, item_num, max_item_num,\n+\t\t\t\t\t\tstart_time, cur_idx, sent_seq, ip_id, is_atomic) ==\n+\t\t\t\t\tINVALID_ARRAY_INDEX)\n+\t\t\t\treturn -1;\n+\t\t\treturn 0;\n+\t\t}\n+\t\tprev_idx = cur_idx;\n+\t\tcur_idx = items[cur_idx].next_pkt_idx;\n+\t} while (cur_idx != INVALID_ARRAY_INDEX);\n+\n+\t/* Fail to find a neighbor, so store the packet into the flow. */\n+\tif (insert_new_tcp_item(pkt, items, item_num, max_item_num, start_time, prev_idx, sent_seq,\n+\t\t\t\tip_id, is_atomic) == INVALID_ARRAY_INDEX)\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\ndiff --git a/lib/gro/gro_tcp_internal.h b/lib/gro/gro_tcp_internal.h\nnew file mode 100644\nindex 0000000000..072b7aea13\n--- /dev/null\n+++ b/lib/gro/gro_tcp_internal.h\n@@ -0,0 +1,212 @@\n+#ifndef _GRO_TCP_H_\n+#define _GRO_TCP_H_\n+\n+#define INVALID_ARRAY_INDEX 0xffffffffUL\n+\n+#include <rte_tcp.h>\n+\n+/*\n+ * The max length of a IPv4 packet, which includes the length of the L3\n+ * header, the L4 header and the data payload.\n+ */\n+#define MAX_IP_PKT_LENGTH UINT16_MAX\n+\n+/* The maximum TCP header length */\n+#define MAX_TCP_HLEN 60\n+#define INVALID_TCP_HDRLEN(len) \\\n+\t(((len) < sizeof(struct rte_tcp_hdr)) || ((len) > MAX_TCP_HLEN))\n+\n+struct cmn_tcp_key {\n+\tstruct rte_ether_addr eth_saddr;\n+\tstruct rte_ether_addr eth_daddr;\n+\tuint32_t recv_ack;\n+\tuint16_t src_port;\n+\tuint16_t dst_port;\n+};\n+\n+#define ASSIGN_COMMON_TCP_KEY(k1, k2) \\\n+\tdo {\\\n+\t\trte_ether_addr_copy(&(k1->eth_saddr), &(k2->eth_saddr)); \\\n+\t\trte_ether_addr_copy(&(k1->eth_daddr), &(k2->eth_daddr)); \\\n+\t\tk2->recv_ack = k1->recv_ack; \\\n+\t\tk2->src_port = k1->src_port; \\\n+\t\tk2->dst_port = k1->dst_port; \\\n+\t} while (0)\n+\n+struct gro_tcp_item {\n+\t/*\n+\t * The first MBUF segment of the packet. If the value\n+\t * is NULL, it means the item is empty.\n+\t */\n+\tstruct rte_mbuf *firstseg;\n+\t/* The last MBUF segment of the packet */\n+\tstruct rte_mbuf *lastseg;\n+\t/*\n+\t * The time when the first packet is inserted into the table.\n+\t * This value won't be updated, even if the packet is merged\n+\t * with other packets.\n+\t */\n+\tuint64_t start_time;\n+\t/*\n+\t * next_pkt_idx is used to chain the packets that\n+\t * are in the same flow but can't be merged together\n+\t * (e.g. caused by packet reordering).\n+\t */\n+\tuint32_t next_pkt_idx;\n+\t/* TCP sequence number of the packet */\n+\tuint32_t sent_seq;\n+\tunion {\n+\t\t/* IPv4 ID of the packet */\n+\t\tuint16_t ip_id;\n+\t\t/* Unused field for IPv6 */\n+\t\tuint16_t unused;\n+\t} l3;\n+\t/* the number of merged packets */\n+\tuint16_t nb_merged;\n+\t/* Indicate if IPv4 ID can be ignored */\n+\tuint8_t is_atomic;\n+};\n+\n+uint32_t\n+insert_new_tcp_item(struct rte_mbuf *pkt,\n+\t\tstruct gro_tcp_item *items,\n+\t\tuint32_t *item_num,\n+\t\tuint32_t max_item_num,\n+\t\tuint64_t start_time,\n+\t\tuint32_t prev_idx,\n+\t\tuint32_t sent_seq,\n+\t\tuint16_t ip_id,\n+\t\tuint8_t is_atomic);\n+\n+uint32_t\n+delete_tcp_item(struct gro_tcp_item *items, uint32_t item_idx,\n+\t\tuint32_t *item_num,\n+\t\tuint32_t prev_item_idx);\n+\n+int32_t\n+process_tcp_item(struct rte_mbuf *pkt,\n+\tstruct rte_tcp_hdr *tcp_hdr,\n+\tint32_t tcp_dl,\n+\tstruct gro_tcp_item *items,\n+\tuint32_t item_idx,\n+\tuint32_t *item_num,\n+\tuint32_t max_item_num,\n+\tuint16_t ip_id,\n+\tuint8_t is_atomic,\n+\tuint64_t start_time);\n+\n+/*\n+ * Merge two TCP packets without updating checksums.\n+ * If cmp is larger than 0, append the new packet to the\n+ * original packet. Otherwise, pre-pend the new packet to\n+ * the original packet.\n+ */\n+static inline int\n+merge_two_tcp_packets(struct gro_tcp_item *item,\n+\t\tstruct rte_mbuf *pkt,\n+\t\tint cmp,\n+\t\tuint32_t sent_seq,\n+\t\tuint16_t ip_id,\n+\t\tuint16_t l2_offset)\n+{\n+\tstruct rte_mbuf *pkt_head, *pkt_tail, *lastseg;\n+\tuint16_t hdr_len, l2_len;\n+\n+\tif (cmp > 0) {\n+\t\tpkt_head = item->firstseg;\n+\t\tpkt_tail = pkt;\n+\t} else {\n+\t\tpkt_head = pkt;\n+\t\tpkt_tail = item->firstseg;\n+\t}\n+\n+\t/* check if the IPv4 packet length is greater than the max value */\n+\thdr_len = l2_offset + pkt_head->l2_len + pkt_head->l3_len +\n+\t\tpkt_head->l4_len;\n+\tl2_len = l2_offset > 0 ? pkt_head->outer_l2_len : pkt_head->l2_len;\n+\tif (unlikely(pkt_head->pkt_len - l2_len + pkt_tail->pkt_len -\n+\t\t\t\thdr_len > MAX_IP_PKT_LENGTH))\n+\t\treturn 0;\n+\n+\t/* remove the packet header for the tail packet */\n+\trte_pktmbuf_adj(pkt_tail, hdr_len);\n+\n+\t/* chain two packets together */\n+\tif (cmp > 0) {\n+\t\titem->lastseg->next = pkt;\n+\t\titem->lastseg = rte_pktmbuf_lastseg(pkt);\n+\t\t/* update IP ID to the larger value */\n+\t\titem->l3.ip_id = ip_id;\n+\t} else {\n+\t\tlastseg = rte_pktmbuf_lastseg(pkt);\n+\t\tlastseg->next = item->firstseg;\n+\t\titem->firstseg = pkt;\n+\t\t/* update sent_seq to the smaller value */\n+\t\titem->sent_seq = sent_seq;\n+\t\titem->l3.ip_id = ip_id;\n+\t}\n+\titem->nb_merged++;\n+\n+\t/* update MBUF metadata for the merged packet */\n+\tpkt_head->nb_segs += pkt_tail->nb_segs;\n+\tpkt_head->pkt_len += pkt_tail->pkt_len;\n+\n+\treturn 1;\n+}\n+\n+/*\n+ * Check if two TCP/IPv4 packets are neighbors.\n+ */\n+static inline int\n+check_seq_option(struct gro_tcp_item *item,\n+\t\tstruct rte_tcp_hdr *tcph,\n+\t\tuint32_t sent_seq,\n+\t\tuint16_t ip_id,\n+\t\tuint16_t tcp_hl,\n+\t\tuint16_t tcp_dl,\n+\t\tuint16_t l2_offset,\n+\t\tuint8_t is_atomic)\n+{\n+\tstruct rte_mbuf *pkt_orig = item->firstseg;\n+\tchar *iph_orig;\n+\tstruct rte_tcp_hdr *tcph_orig;\n+\tuint16_t len, tcp_hl_orig;\n+\n+\tiph_orig = (char *)(rte_pktmbuf_mtod(pkt_orig, char *) +\n+\t\t\tl2_offset + pkt_orig->l2_len);\n+\ttcph_orig = (struct rte_tcp_hdr *)(iph_orig + pkt_orig->l3_len);\n+\ttcp_hl_orig = pkt_orig->l4_len;\n+\n+\t/* Check if TCP option fields equal */\n+\tlen = RTE_MAX(tcp_hl, tcp_hl_orig) - sizeof(struct rte_tcp_hdr);\n+\tif ((tcp_hl != tcp_hl_orig) || ((len > 0) &&\n+\t\t\t\t(memcmp(tcph + 1, tcph_orig + 1,\n+\t\t\t\t\tlen) != 0)))\n+\t\treturn 0;\n+\n+\t/* Don't merge packets whose DF bits are different */\n+\tif (unlikely(item->is_atomic ^ is_atomic))\n+\t\treturn 0;\n+\n+\t/* check if the two packets are neighbors */\n+\tlen = pkt_orig->pkt_len - l2_offset - pkt_orig->l2_len -\n+\t\tpkt_orig->l3_len - tcp_hl_orig;\n+\tif ((sent_seq == item->sent_seq + len) && (is_atomic ||\n+\t\t\t\t(ip_id == item->l3.ip_id + 1)))\n+\t\t/* append the new packet */\n+\t\treturn 1;\n+\telse if ((sent_seq + tcp_dl == item->sent_seq) && (is_atomic ||\n+\t\t\t\t(ip_id + item->nb_merged == item->l3.ip_id)))\n+\t\t/* pre-pend the new packet */\n+\t\treturn -1;\n+\n+\treturn 0;\n+}\n+\n+static inline int\n+is_common_tcp_key(struct cmn_tcp_key *k1, struct cmn_tcp_key *k2)\n+{\n+\treturn (!memcmp(k1, k2, sizeof(struct cmn_tcp_key)));\n+}\n+\n+#endif\ndiff --git a/lib/gro/gro_vxlan_tcp4.c b/lib/gro/gro_vxlan_tcp4.c\nindex 3be4deb7c7..81eebf0d2d 100644\n--- a/lib/gro/gro_vxlan_tcp4.c\n+++ b/lib/gro/gro_vxlan_tcp4.c\n@@ -7,6 +7,7 @@\n #include <rte_ethdev.h>\n #include <rte_udp.h>\n \n+#include \"gro_tcp_internal.h\"\n #include \"gro_vxlan_tcp4.h\"\n \n void *\n@@ -116,7 +117,7 @@ insert_new_item(struct gro_vxlan_tcp4_tbl *tbl,\n \ttbl->items[item_idx].inner_item.start_time = start_time;\n \ttbl->items[item_idx].inner_item.next_pkt_idx = INVALID_ARRAY_INDEX;\n \ttbl->items[item_idx].inner_item.sent_seq = sent_seq;\n-\ttbl->items[item_idx].inner_item.ip_id = ip_id;\n+\ttbl->items[item_idx].inner_item.l3.ip_id = ip_id;\n \ttbl->items[item_idx].inner_item.nb_merged = 1;\n \ttbl->items[item_idx].inner_item.is_atomic = is_atomic;\n \ttbl->items[item_idx].outer_ip_id = outer_ip_id;\n@@ -163,15 +164,9 @@ insert_new_flow(struct gro_vxlan_tcp4_tbl *tbl,\n \n \tdst = &(tbl->flows[flow_idx].key);\n \n-\trte_ether_addr_copy(&(src->inner_key.eth_saddr),\n-\t\t\t&(dst->inner_key.eth_saddr));\n-\trte_ether_addr_copy(&(src->inner_key.eth_daddr),\n-\t\t\t&(dst->inner_key.eth_daddr));\n+\tASSIGN_COMMON_TCP_KEY((&(src->inner_key.cmn_key)), (&(dst->inner_key.cmn_key)));\n \tdst->inner_key.ip_src_addr = src->inner_key.ip_src_addr;\n \tdst->inner_key.ip_dst_addr = src->inner_key.ip_dst_addr;\n-\tdst->inner_key.recv_ack = src->inner_key.recv_ack;\n-\tdst->inner_key.src_port = src->inner_key.src_port;\n-\tdst->inner_key.dst_port = src->inner_key.dst_port;\n \n \tdst->vxlan_hdr.vx_flags = src->vxlan_hdr.vx_flags;\n \tdst->vxlan_hdr.vx_vni = src->vxlan_hdr.vx_vni;\n@@ -248,7 +243,7 @@ merge_two_vxlan_tcp4_packets(struct gro_vxlan_tcp4_item *item,\n \t\tuint16_t outer_ip_id,\n \t\tuint16_t ip_id)\n {\n-\tif (merge_two_tcp4_packets(&item->inner_item, pkt, cmp, sent_seq,\n+\tif (merge_two_tcp_packets(&item->inner_item, pkt, cmp, sent_seq,\n \t\t\t\tip_id, pkt->outer_l2_len +\n \t\t\t\tpkt->outer_l3_len)) {\n \t\t/* Update the outer IPv4 ID to the large value. */\n@@ -357,13 +352,13 @@ gro_vxlan_tcp4_reassemble(struct rte_mbuf *pkt,\n \n \tsent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);\n \n-\trte_ether_addr_copy(&(eth_hdr->src_addr), &(key.inner_key.eth_saddr));\n-\trte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.inner_key.eth_daddr));\n+\trte_ether_addr_copy(&(eth_hdr->src_addr), &(key.inner_key.cmn_key.eth_saddr));\n+\trte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.inner_key.cmn_key.eth_daddr));\n \tkey.inner_key.ip_src_addr = ipv4_hdr->src_addr;\n \tkey.inner_key.ip_dst_addr = ipv4_hdr->dst_addr;\n-\tkey.inner_key.recv_ack = tcp_hdr->recv_ack;\n-\tkey.inner_key.src_port = tcp_hdr->src_port;\n-\tkey.inner_key.dst_port = tcp_hdr->dst_port;\n+\tkey.inner_key.cmn_key.recv_ack = tcp_hdr->recv_ack;\n+\tkey.inner_key.cmn_key.src_port = tcp_hdr->src_port;\n+\tkey.inner_key.cmn_key.dst_port = tcp_hdr->dst_port;\n \n \tkey.vxlan_hdr.vx_flags = vxlan_hdr->vx_flags;\n \tkey.vxlan_hdr.vx_vni = vxlan_hdr->vx_vni;\ndiff --git a/lib/gro/gro_vxlan_tcp4.h b/lib/gro/gro_vxlan_tcp4.h\nindex 7832942a68..82eaaee11e 100644\n--- a/lib/gro/gro_vxlan_tcp4.h\n+++ b/lib/gro/gro_vxlan_tcp4.h\n@@ -5,6 +5,7 @@\n #ifndef _GRO_VXLAN_TCP4_H_\n #define _GRO_VXLAN_TCP4_H_\n \n+#include \"gro_tcp_internal.h\"\n #include \"gro_tcp4.h\"\n \n #define GRO_VXLAN_TCP4_TBL_MAX_ITEM_NUM (1024UL * 1024UL)\n@@ -36,7 +37,7 @@ struct gro_vxlan_tcp4_flow {\n };\n \n struct gro_vxlan_tcp4_item {\n-\tstruct gro_tcp4_item inner_item;\n+\tstruct gro_tcp_item inner_item;\n \t/* IPv4 ID in the outer IPv4 header */\n \tuint16_t outer_ip_id;\n \t/* Indicate if outer IPv4 ID can be ignored */\ndiff --git a/lib/gro/meson.build b/lib/gro/meson.build\nindex e4fa2958bd..1640317890 100644\n--- a/lib/gro/meson.build\n+++ b/lib/gro/meson.build\n@@ -3,7 +3,9 @@\n \n sources = files(\n         'rte_gro.c',\n+        'gro_tcp_internal.c',\n         'gro_tcp4.c',\n+        'gro_tcp6.c',\n         'gro_udp4.c',\n         'gro_vxlan_tcp4.c',\n         'gro_vxlan_udp4.c',\ndiff --git a/lib/gro/rte_gro.c b/lib/gro/rte_gro.c\nindex e35399fd42..d824eebd93 100644\n--- a/lib/gro/rte_gro.c\n+++ b/lib/gro/rte_gro.c\n@@ -8,6 +8,7 @@\n \n #include \"rte_gro.h\"\n #include \"gro_tcp4.h\"\n+#include \"gro_tcp6.h\"\n #include \"gro_udp4.h\"\n #include \"gro_vxlan_tcp4.h\"\n #include \"gro_vxlan_udp4.h\"\n@@ -20,14 +21,16 @@ typedef uint32_t (*gro_tbl_pkt_count_fn)(void *tbl);\n \n static gro_tbl_create_fn tbl_create_fn[RTE_GRO_TYPE_MAX_NUM] = {\n \t\tgro_tcp4_tbl_create, gro_vxlan_tcp4_tbl_create,\n-\t\tgro_udp4_tbl_create, gro_vxlan_udp4_tbl_create, NULL};\n+\t\tgro_udp4_tbl_create, gro_vxlan_udp4_tbl_create, gro_tcp6_tbl_create, NULL};\n static gro_tbl_destroy_fn tbl_destroy_fn[RTE_GRO_TYPE_MAX_NUM] = {\n \t\t\tgro_tcp4_tbl_destroy, gro_vxlan_tcp4_tbl_destroy,\n \t\t\tgro_udp4_tbl_destroy, gro_vxlan_udp4_tbl_destroy,\n+\t\t\tgro_tcp6_tbl_destroy,\n \t\t\tNULL};\n static gro_tbl_pkt_count_fn tbl_pkt_count_fn[RTE_GRO_TYPE_MAX_NUM] = {\n \t\t\tgro_tcp4_tbl_pkt_count, gro_vxlan_tcp4_tbl_pkt_count,\n \t\t\tgro_udp4_tbl_pkt_count, gro_vxlan_udp4_tbl_pkt_count,\n+\t\t\tgro_tcp6_tbl_pkt_count,\n \t\t\tNULL};\n \n #define IS_IPV4_TCP_PKT(ptype) (RTE_ETH_IS_IPV4_HDR(ptype) && \\\n@@ -35,6 +38,12 @@ static gro_tbl_pkt_count_fn tbl_pkt_count_fn[RTE_GRO_TYPE_MAX_NUM] = {\n \t\t((ptype & RTE_PTYPE_L4_FRAG) != RTE_PTYPE_L4_FRAG) && \\\n \t\t(RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))\n \n+/* GRO with extension headers is not supported */\n+#define IS_IPV6_TCP_PKT(ptype) (RTE_ETH_IS_IPV6_HDR(ptype) && \\\n+\t\t((ptype & RTE_PTYPE_L4_TCP) == RTE_PTYPE_L4_TCP) && \\\n+\t\t((ptype & RTE_PTYPE_L4_FRAG) != RTE_PTYPE_L4_FRAG) && \\\n+\t\t(RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))\n+\n #define IS_IPV4_UDP_PKT(ptype) (RTE_ETH_IS_IPV4_HDR(ptype) && \\\n \t\t((ptype & RTE_PTYPE_L4_UDP) == RTE_PTYPE_L4_UDP) && \\\n \t\t(RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))\n@@ -147,7 +156,11 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \t/* allocate a reassembly table for TCP/IPv4 GRO */\n \tstruct gro_tcp4_tbl tcp_tbl;\n \tstruct gro_tcp4_flow tcp_flows[RTE_GRO_MAX_BURST_ITEM_NUM];\n-\tstruct gro_tcp4_item tcp_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };\n+\tstruct gro_tcp_item tcp_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };\n+\n+\tstruct gro_tcp6_tbl tcp6_tbl;\n+\tstruct gro_tcp6_flow tcp6_flows[RTE_GRO_MAX_BURST_ITEM_NUM];\n+\tstruct gro_tcp_item tcp6_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };\n \n \t/* allocate a reassembly table for UDP/IPv4 GRO */\n \tstruct gro_udp4_tbl udp_tbl;\n@@ -171,10 +184,10 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \tint32_t ret;\n \tuint16_t i, unprocess_num = 0, nb_after_gro = nb_pkts;\n \tuint8_t do_tcp4_gro = 0, do_vxlan_tcp_gro = 0, do_udp4_gro = 0,\n-\t\tdo_vxlan_udp_gro = 0;\n+\t\tdo_vxlan_udp_gro = 0, do_tcp6_gro = 0;\n \n \tif (unlikely((param->gro_types & (RTE_GRO_IPV4_VXLAN_TCP_IPV4 |\n-\t\t\t\t\tRTE_GRO_TCP_IPV4 |\n+\t\t\t\t\tRTE_GRO_TCP_IPV4 | RTE_GRO_TCP_IPV6 |\n \t\t\t\t\tRTE_GRO_IPV4_VXLAN_UDP_IPV4 |\n \t\t\t\t\tRTE_GRO_UDP_IPV4)) == 0))\n \t\treturn nb_pkts;\n@@ -236,6 +249,18 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \t\tdo_udp4_gro = 1;\n \t}\n \n+\tif (param->gro_types & RTE_GRO_TCP_IPV6) {\n+\t\tfor (i = 0; i < item_num; i++)\n+\t\t\ttcp6_flows[i].start_index = INVALID_ARRAY_INDEX;\n+\n+\t\ttcp6_tbl.flows = tcp6_flows;\n+\t\ttcp6_tbl.items = tcp6_items;\n+\t\ttcp6_tbl.flow_num = 0;\n+\t\ttcp6_tbl.item_num = 0;\n+\t\ttcp6_tbl.max_flow_num = item_num;\n+\t\ttcp6_tbl.max_item_num = item_num;\n+\t\tdo_tcp6_gro = 1;\n+\t}\n \n \tfor (i = 0; i < nb_pkts; i++) {\n \t\t/*\n@@ -276,6 +301,14 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \t\t\t\tnb_after_gro--;\n \t\t\telse if (ret < 0)\n \t\t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n+\t\t} else if (IS_IPV6_TCP_PKT(pkts[i]->packet_type) &&\n+\t\t\t\tdo_tcp6_gro) {\n+\t\t\tret = gro_tcp6_reassemble(pkts[i], &tcp6_tbl, 0);\n+\t\t\tif (ret > 0)\n+\t\t\t\t/* merge successfully */\n+\t\t\t\tnb_after_gro--;\n+\t\t\telse if (ret < 0)\n+\t\t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n \t\t} else\n \t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n \t}\n@@ -283,9 +316,17 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \tif ((nb_after_gro < nb_pkts)\n \t\t || (unprocess_num < nb_pkts)) {\n \t\ti = 0;\n+\t\t/* Copy unprocessed packets */\n+\t\tif (unprocess_num > 0) {\n+\t\t\tmemcpy(&pkts[i], unprocess_pkts,\n+\t\t\t\t\tsizeof(struct rte_mbuf *) *\n+\t\t\t\t\tunprocess_num);\n+\t\t\ti = unprocess_num;\n+\t\t}\n+\n \t\t/* Flush all packets from the tables */\n \t\tif (do_vxlan_tcp_gro) {\n-\t\t\ti = gro_vxlan_tcp4_tbl_timeout_flush(&vxlan_tcp_tbl,\n+\t\t\ti += gro_vxlan_tcp4_tbl_timeout_flush(&vxlan_tcp_tbl,\n \t\t\t\t\t0, pkts, nb_pkts);\n \t\t}\n \n@@ -304,13 +345,11 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,\n \t\t\ti += gro_udp4_tbl_timeout_flush(&udp_tbl, 0,\n \t\t\t\t\t&pkts[i], nb_pkts - i);\n \t\t}\n-\t\t/* Copy unprocessed packets */\n-\t\tif (unprocess_num > 0) {\n-\t\t\tmemcpy(&pkts[i], unprocess_pkts,\n-\t\t\t\t\tsizeof(struct rte_mbuf *) *\n-\t\t\t\t\tunprocess_num);\n+\n+\t\tif (do_tcp6_gro) {\n+\t\t\ti += gro_tcp6_tbl_timeout_flush(&tcp6_tbl, 0,\n+\t\t\t\t\t&pkts[i], nb_pkts - i);\n \t\t}\n-\t\tnb_after_gro = i + unprocess_num;\n \t}\n \n \treturn nb_after_gro;\n@@ -323,13 +362,13 @@ rte_gro_reassemble(struct rte_mbuf **pkts,\n {\n \tstruct rte_mbuf *unprocess_pkts[nb_pkts];\n \tstruct gro_ctx *gro_ctx = ctx;\n-\tvoid *tcp_tbl, *udp_tbl, *vxlan_tcp_tbl, *vxlan_udp_tbl;\n+\tvoid *tcp_tbl, *udp_tbl, *vxlan_tcp_tbl, *vxlan_udp_tbl, *tcp6_tbl;\n \tuint64_t current_time;\n \tuint16_t i, unprocess_num = 0;\n-\tuint8_t do_tcp4_gro, do_vxlan_tcp_gro, do_udp4_gro, do_vxlan_udp_gro;\n+\tuint8_t do_tcp4_gro, do_vxlan_tcp_gro, do_udp4_gro, do_vxlan_udp_gro, do_tcp6_gro;\n \n \tif (unlikely((gro_ctx->gro_types & (RTE_GRO_IPV4_VXLAN_TCP_IPV4 |\n-\t\t\t\t\tRTE_GRO_TCP_IPV4 |\n+\t\t\t\t\tRTE_GRO_TCP_IPV4 | RTE_GRO_TCP_IPV6 |\n \t\t\t\t\tRTE_GRO_IPV4_VXLAN_UDP_IPV4 |\n \t\t\t\t\tRTE_GRO_UDP_IPV4)) == 0))\n \t\treturn nb_pkts;\n@@ -338,6 +377,7 @@ rte_gro_reassemble(struct rte_mbuf **pkts,\n \tvxlan_tcp_tbl = gro_ctx->tbls[RTE_GRO_IPV4_VXLAN_TCP_IPV4_INDEX];\n \tudp_tbl = gro_ctx->tbls[RTE_GRO_UDP_IPV4_INDEX];\n \tvxlan_udp_tbl = gro_ctx->tbls[RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX];\n+\ttcp6_tbl = gro_ctx->tbls[RTE_GRO_TCP_IPV6_INDEX];\n \n \tdo_tcp4_gro = (gro_ctx->gro_types & RTE_GRO_TCP_IPV4) ==\n \t\tRTE_GRO_TCP_IPV4;\n@@ -347,6 +387,7 @@ rte_gro_reassemble(struct rte_mbuf **pkts,\n \t\tRTE_GRO_UDP_IPV4;\n \tdo_vxlan_udp_gro = (gro_ctx->gro_types & RTE_GRO_IPV4_VXLAN_UDP_IPV4) ==\n \t\tRTE_GRO_IPV4_VXLAN_UDP_IPV4;\n+\tdo_tcp6_gro = (gro_ctx->gro_types & RTE_GRO_TCP_IPV6) == RTE_GRO_TCP_IPV6;\n \n \tcurrent_time = rte_rdtsc();\n \n@@ -371,6 +412,11 @@ rte_gro_reassemble(struct rte_mbuf **pkts,\n \t\t\tif (gro_udp4_reassemble(pkts[i], udp_tbl,\n \t\t\t\t\t\tcurrent_time) < 0)\n \t\t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n+\t\t} else if (IS_IPV6_TCP_PKT(pkts[i]->packet_type) &&\n+\t\t\t\tdo_tcp6_gro) {\n+\t\t\tif (gro_tcp6_reassemble(pkts[i], tcp6_tbl,\n+\t\t\t\t\t\tcurrent_time) < 0)\n+\t\t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n \t\t} else\n \t\t\tunprocess_pkts[unprocess_num++] = pkts[i];\n \t}\n@@ -426,6 +472,15 @@ rte_gro_timeout_flush(void *ctx,\n \t\t\t\tgro_ctx->tbls[RTE_GRO_UDP_IPV4_INDEX],\n \t\t\t\tflush_timestamp,\n \t\t\t\t&out[num], left_nb_out);\n+\t\tleft_nb_out = max_nb_out - num;\n+\t}\n+\n+\tif ((gro_types & RTE_GRO_TCP_IPV6) && left_nb_out > 0) {\n+\t\tnum += gro_tcp6_tbl_timeout_flush(\n+\t\t\t\tgro_ctx->tbls[RTE_GRO_TCP_IPV6_INDEX],\n+\t\t\t\tflush_timestamp,\n+\t\t\t\t&out[num], left_nb_out);\n+\n \t}\n \n \treturn num;\ndiff --git a/lib/gro/rte_gro.h b/lib/gro/rte_gro.h\nindex 9f9ed4935a..c83dfd9ad1 100644\n--- a/lib/gro/rte_gro.h\n+++ b/lib/gro/rte_gro.h\n@@ -38,6 +38,9 @@ extern \"C\" {\n #define RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX 3\n #define RTE_GRO_IPV4_VXLAN_UDP_IPV4 (1ULL << RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX)\n /**< VxLAN UDP/IPv4 GRO flag. */\n+#define RTE_GRO_TCP_IPV6_INDEX 4\n+#define RTE_GRO_TCP_IPV6 (1ULL << RTE_GRO_TCP_IPV6_INDEX)\n+/**< TCP/IPv6 GRO flag. */\n \n /**\n  * Structure used to create GRO context objects or used to pass\n",
    "prefixes": [
        "v7"
    ]
}