<html xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:w="urn:schemas-microsoft-com:office:word" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml" xmlns="http://www.w3.org/TR/REC-html40">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="Generator" content="Microsoft Word 15 (filtered medium)">
<style><!--
/* Font Definitions */
@font-face
        {font-family:"Cambria Math";
        panose-1:2 4 5 3 5 4 6 3 2 4;}
@font-face
        {font-family:DengXian;
        panose-1:2 1 6 0 3 1 1 1 1 1;}
@font-face
        {font-family:Calibri;
        panose-1:2 15 5 2 2 2 4 3 2 4;}
@font-face
        {font-family:"\@DengXian";
        panose-1:2 1 6 0 3 1 1 1 1 1;}
/* Style Definitions */
p.MsoNormal, li.MsoNormal, div.MsoNormal
        {margin:0in;
        font-size:11.0pt;
        font-family:"Calibri",sans-serif;}
a:link, span.MsoHyperlink
        {mso-style-priority:99;
        color:blue;
        text-decoration:underline;}
span.EmailStyle19
        {mso-style-type:personal-compose;
        font-family:"Calibri",sans-serif;
        color:windowtext;}
.MsoChpDefault
        {mso-style-type:export-only;
        font-family:"Calibri",sans-serif;}
@page WordSection1
        {size:8.5in 11.0in;
        margin:1.0in 1.0in 1.0in 1.0in;}
div.WordSection1
        {page:WordSection1;}
--></style><!--[if gte mso 9]><xml>
<o:shapedefaults v:ext="edit" spidmax="1026" />
</xml><![endif]--><!--[if gte mso 9]><xml>
<o:shapelayout v:ext="edit">
<o:idmap v:ext="edit" data="1" />
</o:shapelayout></xml><![endif]-->
</head>
<body lang="EN-US" link="blue" vlink="purple" style="word-wrap:break-word">
<div class="WordSection1">
<p class="MsoNormal">Hi Kumar,<o:p></o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<p class="MsoNormal">I don’t have much experience on IPv6, but I will find time to review the patch.<o:p></o:p></p>
<p class="MsoNormal">Besides, if anyone can help to review the patch, it would be appreciated.<o:p></o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<p class="MsoNormal">Thanks,<o:p></o:p></p>
<p class="MsoNormal">Jiayu<o:p></o:p></p>
<p class="MsoNormal"><o:p> </o:p></p>
<div style="border:none;border-left:solid blue 1.5pt;padding:0in 0in 0in 4.0pt">
<div>
<div style="border:none;border-top:solid #E1E1E1 1.0pt;padding:3.0pt 0in 0in 0in">
<p class="MsoNormal"><b>From:</b> kumaraparameshwaran rathinavel <kumaraparamesh92@gmail.com>
<br>
<b>Sent:</b> Thursday, March 30, 2023 12:39 AM<br>
<b>To:</b> dev@dpdk.org<br>
<b>Cc:</b> Hu, Jiayu <jiayu.hu@intel.com>; Thomas Monjalon <thomas@monjalon.net><br>
<b>Subject:</b> Re: [PATCH] gro : ipv6 changes to support GRO for TCP/ipv6<o:p></o:p></p>
</div>
</div>
<p class="MsoNormal"><o:p> </o:p></p>
<div>
<div>
<p class="MsoNormal">Hi, <o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">I would like to get a review on the following patch where support is added for IPv6 GRO.
<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<div>
<p class="MsoNormal">Thanks,<o:p></o:p></p>
</div>
<div>
<p class="MsoNormal">Param.<o:p></o:p></p>
</div>
<div>
<div>
<p class="MsoNormal"><o:p> </o:p></p>
</div>
<p class="MsoNormal"><o:p> </o:p></p>
<div>
<div>
<p class="MsoNormal">On Thu, Oct 20, 2022 at 5:43 PM Kumara Parameshwaran <<a href="mailto:kumaraparamesh92@gmail.com">kumaraparamesh92@gmail.com</a>> wrote:<o:p></o:p></p>
</div>
<blockquote style="border:none;border-left:solid #CCCCCC 1.0pt;padding:0in 0in 0in 6.0pt;margin-left:4.8pt;margin-right:0in">
<p class="MsoNormal" style="margin-bottom:12.0pt">From: Kumara Parameshwaran <<a href="mailto:kumaraparamesh92@gmail.com" target="_blank">kumaraparamesh92@gmail.com</a>><br>
<br>
The patch adds GRO support for TCP/ipv6 packets. This does not<br>
include the support for vxlan, udp ipv6 packets.<br>
<br>
Signed-off-by: Kumara Parameshwaran <<a href="mailto:kumaraparamesh92@gmail.com" target="_blank">kumaraparamesh92@gmail.com</a>><br>
---<br>
v1:<br>
        * Changes to support GRO for TCP/ipv6 packets. This does not include<br>
          vxlan changes. <br>
        * The GRO is performed only for ipv6 packets that does not contain <br>
         extension headers. <br>
        * The logic for the TCP coalescing remains the same, in ipv6 header <br>
          the source address, destination address, flow label, version fields <br>
          are expected to be the same. <br>
        * Re-organised the code to reuse certain tcp functions for both ipv4 and <br>
          ipv6 flows.<br>
<br>
 lib/gro/gro_tcp.h        | 155 ++++++++++++++++<br>
 lib/gro/gro_tcp4.c       |   7 +-<br>
 lib/gro/gro_tcp4.h       | 152 +--------------<br>
 lib/gro/gro_tcp6.c       | 388 +++++++++++++++++++++++++++++++++++++++<br>
 lib/gro/gro_tcp6.h       | 150 +++++++++++++++<br>
 lib/gro/gro_vxlan_tcp4.c |   3 +-<br>
 lib/gro/gro_vxlan_tcp4.h |   3 +-<br>
 lib/gro/meson.build      |   1 +<br>
 lib/gro/rte_gro.c        |  86 +++++++--<br>
 lib/gro/rte_gro.h        |   3 +<br>
 10 files changed, 777 insertions(+), 171 deletions(-)<br>
 create mode 100644 lib/gro/gro_tcp.h<br>
 create mode 100644 lib/gro/gro_tcp6.c<br>
 create mode 100644 lib/gro/gro_tcp6.h<br>
<br>
diff --git a/lib/gro/gro_tcp.h b/lib/gro/gro_tcp.h<br>
new file mode 100644<br>
index 0000000000..16bce9d098<br>
--- /dev/null<br>
+++ b/lib/gro/gro_tcp.h<br>
@@ -0,0 +1,155 @@<br>
+#ifndef _GRO_TCP_H_<br>
+#define _GRO_TCP_H_<br>
+<br>
+#include <rte_tcp.h><br>
+<br>
+/*<br>
+ * The max length of a IPv4 packet, which includes the length of the L3<br>
+ * header, the L4 header and the data payload.<br>
+ */<br>
+#define MAX_IP_PKT_LENGTH UINT16_MAX<br>
+<br>
+/* The maximum TCP header length */<br>
+#define MAX_TCP_HLEN 60<br>
+#define INVALID_TCP_HDRLEN(len) \<br>
+       (((len) < sizeof(struct rte_tcp_hdr)) || ((len) > MAX_TCP_HLEN))<br>
+<br>
+struct gro_tcp_item {<br>
+       /*<br>
+        * The first MBUF segment of the packet. If the value<br>
+        * is NULL, it means the item is empty.<br>
+        */<br>
+       struct rte_mbuf *firstseg;<br>
+       /* The last MBUF segment of the packet */<br>
+       struct rte_mbuf *lastseg;<br>
+       /*<br>
+        * The time when the first packet is inserted into the table.<br>
+        * This value won't be updated, even if the packet is merged<br>
+        * with other packets.<br>
+        */<br>
+       uint64_t start_time;<br>
+       /*<br>
+        * next_pkt_idx is used to chain the packets that<br>
+        * are in the same flow but can't be merged together<br>
+        * (e.g. caused by packet reordering).<br>
+        */<br>
+       uint32_t next_pkt_idx;<br>
+       /* TCP sequence number of the packet */<br>
+       uint32_t sent_seq;<br>
+       /* IPv4 ID of the packet */<br>
+       uint16_t ip_id;<br>
+       /* the number of merged packets */<br>
+       uint16_t nb_merged;<br>
+       /* Indicate if IPv4 ID can be ignored */<br>
+       uint8_t is_atomic;<br>
+};<br>
+<br>
+/*<br>
+ * Merge two TCP packets without updating checksums.<br>
+ * If cmp is larger than 0, append the new packet to the<br>
+ * original packet. Otherwise, pre-pend the new packet to<br>
+ * the original packet.<br>
+ */<br>
+static inline int<br>
+merge_two_tcp_packets(struct gro_tcp_item *item,<br>
+               struct rte_mbuf *pkt,<br>
+               int cmp,<br>
+               uint32_t sent_seq,<br>
+               uint16_t ip_id,<br>
+               uint16_t l2_offset)<br>
+{<br>
+       struct rte_mbuf *pkt_head, *pkt_tail, *lastseg;<br>
+       uint16_t hdr_len, l2_len;<br>
+<br>
+       if (cmp > 0) {<br>
+               pkt_head = item->firstseg;<br>
+               pkt_tail = pkt;<br>
+       } else {<br>
+               pkt_head = pkt;<br>
+               pkt_tail = item->firstseg;<br>
+       }<br>
+<br>
+       /* check if the IPv4 packet length is greater than the max value */<br>
+       hdr_len = l2_offset + pkt_head->l2_len + pkt_head->l3_len +<br>
+               pkt_head->l4_len;<br>
+       l2_len = l2_offset > 0 ? pkt_head->outer_l2_len : pkt_head->l2_len;<br>
+       if (unlikely(pkt_head->pkt_len - l2_len + pkt_tail->pkt_len -<br>
+                               hdr_len > MAX_IP_PKT_LENGTH))<br>
+               return 0;<br>
+<br>
+       /* remove the packet header for the tail packet */<br>
+       rte_pktmbuf_adj(pkt_tail, hdr_len);<br>
+<br>
+       /* chain two packets together */<br>
+       if (cmp > 0) {<br>
+               item->lastseg->next = pkt;<br>
+               item->lastseg = rte_pktmbuf_lastseg(pkt);<br>
+               /* update IP ID to the larger value */<br>
+               item->ip_id = ip_id;<br>
+       } else {<br>
+               lastseg = rte_pktmbuf_lastseg(pkt);<br>
+               lastseg->next = item->firstseg;<br>
+               item->firstseg = pkt;<br>
+               /* update sent_seq to the smaller value */<br>
+               item->sent_seq = sent_seq;<br>
+               item->ip_id = ip_id;<br>
+       }<br>
+       item->nb_merged++;<br>
+<br>
+       /* update MBUF metadata for the merged packet */<br>
+       pkt_head->nb_segs += pkt_tail->nb_segs;<br>
+       pkt_head->pkt_len += pkt_tail->pkt_len;<br>
+<br>
+       return 1;<br>
+}<br>
+<br>
+/*<br>
+ * Check if two TCP/IPv4 packets are neighbors.<br>
+ */<br>
+static inline int<br>
+check_seq_option(struct gro_tcp_item *item,<br>
+               struct rte_tcp_hdr *tcph,<br>
+               uint32_t sent_seq,<br>
+               uint16_t ip_id,<br>
+               uint16_t tcp_hl,<br>
+               uint16_t tcp_dl,<br>
+               uint16_t l2_offset,<br>
+               uint8_t is_atomic)<br>
+{<br>
+       struct rte_mbuf *pkt_orig = item->firstseg;<br>
+       char *iph_orig;<br>
+       struct rte_tcp_hdr *tcph_orig;<br>
+       uint16_t len, tcp_hl_orig;<br>
+<br>
+       iph_orig = (char *)(rte_pktmbuf_mtod(pkt_orig, char *) + <br>
+                l2_offset + pkt_orig->l2_len);<br>
+       tcph_orig = (struct rte_tcp_hdr *)(iph_orig + pkt_orig->l3_len);<br>
+       tcp_hl_orig = pkt_orig->l4_len;<br>
+<br>
+       /* Check if TCP option fields equal */<br>
+       len = RTE_MAX(tcp_hl, tcp_hl_orig) - sizeof(struct rte_tcp_hdr);<br>
+       if ((tcp_hl != tcp_hl_orig) || ((len > 0) &&<br>
+                               (memcmp(tcph + 1, tcph_orig + 1,<br>
+                                       len) != 0)))<br>
+               return 0;<br>
+<br>
+       /* Don't merge packets whose DF bits are different */<br>
+       if (unlikely(item->is_atomic ^ is_atomic))<br>
+               return 0;<br>
+<br>
+       /* check if the two packets are neighbors */<br>
+       len = pkt_orig->pkt_len - l2_offset - pkt_orig->l2_len -<br>
+               pkt_orig->l3_len - tcp_hl_orig;<br>
+       if ((sent_seq == item->sent_seq + len) && (is_atomic ||<br>
+                               (ip_id == item->ip_id + 1)))<br>
+               /* append the new packet */<br>
+               return 1;<br>
+       else if ((sent_seq + tcp_dl == item->sent_seq) && (is_atomic ||<br>
+                               (ip_id + item->nb_merged == item->ip_id)))<br>
+               /* pre-pend the new packet */<br>
+               return -1;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+#endif<br>
diff --git a/lib/gro/gro_tcp4.c b/lib/gro/gro_tcp4.c<br>
index 8f5e800250..eea2a72ecd 100644<br>
--- a/lib/gro/gro_tcp4.c<br>
+++ b/lib/gro/gro_tcp4.c<br>
@@ -7,6 +7,7 @@<br>
 #include <rte_ethdev.h><br>
<br>
 #include "gro_tcp4.h"<br>
+#include "gro_tcp.h"<br>
<br>
 void *<br>
 gro_tcp4_tbl_create(uint16_t socket_id,<br>
@@ -30,7 +31,7 @@ gro_tcp4_tbl_create(uint16_t socket_id,<br>
        if (tbl == NULL)<br>
                return NULL;<br>
<br>
-       size = sizeof(struct gro_tcp4_item) * entries_num;<br>
+       size = sizeof(struct gro_tcp_item) * entries_num;<br>
        tbl->items = rte_zmalloc_socket(__func__,<br>
                        size,<br>
                        RTE_CACHE_LINE_SIZE,<br>
@@ -177,7 +178,7 @@ insert_new_flow(struct gro_tcp4_tbl *tbl,<br>
  * update the packet length for the flushed packet.<br>
  */<br>
 static inline void<br>
-update_header(struct gro_tcp4_item *item)<br>
+update_header(struct gro_tcp_item *item)<br>
 {<br>
        struct rte_ipv4_hdr *ipv4_hdr;<br>
        struct rte_mbuf *pkt = item->firstseg;<br>
@@ -302,7 +303,7 @@ gro_tcp4_reassemble(struct rte_mbuf *pkt,<br>
                                sent_seq, ip_id, pkt->l4_len, tcp_dl, 0,<br>
                                is_atomic);<br>
                if (cmp) {<br>
-                       if (merge_two_tcp4_packets(&(tbl->items[cur_idx]),<br>
+                       if (merge_two_tcp_packets(&(tbl->items[cur_idx]),<br>
                                                pkt, cmp, sent_seq, ip_id, 0))<br>
                                return 1;<br>
                        /*<br>
diff --git a/lib/gro/gro_tcp4.h b/lib/gro/gro_tcp4.h<br>
index 212f97a042..634a215b98 100644<br>
--- a/lib/gro/gro_tcp4.h<br>
+++ b/lib/gro/gro_tcp4.h<br>
@@ -5,22 +5,9 @@<br>
 #ifndef _GRO_TCP4_H_<br>
 #define _GRO_TCP4_H_<br>
<br>
-#include <rte_tcp.h><br>
-<br>
 #define INVALID_ARRAY_INDEX 0xffffffffUL<br>
 #define GRO_TCP4_TBL_MAX_ITEM_NUM (1024UL * 1024UL)<br>
<br>
-/*<br>
- * The max length of a IPv4 packet, which includes the length of the L3<br>
- * header, the L4 header and the data payload.<br>
- */<br>
-#define MAX_IPV4_PKT_LENGTH UINT16_MAX<br>
-<br>
-/* The maximum TCP header length */<br>
-#define MAX_TCP_HLEN 60<br>
-#define INVALID_TCP_HDRLEN(len) \<br>
-       (((len) < sizeof(struct rte_tcp_hdr)) || ((len) > MAX_TCP_HLEN))<br>
-<br>
 /* Header fields representing a TCP/IPv4 flow */<br>
 struct tcp4_flow_key {<br>
        struct rte_ether_addr eth_saddr;<br>
@@ -42,42 +29,12 @@ struct gro_tcp4_flow {<br>
        uint32_t start_index;<br>
 };<br>
<br>
-struct gro_tcp4_item {<br>
-       /*<br>
-        * The first MBUF segment of the packet. If the value<br>
-        * is NULL, it means the item is empty.<br>
-        */<br>
-       struct rte_mbuf *firstseg;<br>
-       /* The last MBUF segment of the packet */<br>
-       struct rte_mbuf *lastseg;<br>
-       /*<br>
-        * The time when the first packet is inserted into the table.<br>
-        * This value won't be updated, even if the packet is merged<br>
-        * with other packets.<br>
-        */<br>
-       uint64_t start_time;<br>
-       /*<br>
-        * next_pkt_idx is used to chain the packets that<br>
-        * are in the same flow but can't be merged together<br>
-        * (e.g. caused by packet reordering).<br>
-        */<br>
-       uint32_t next_pkt_idx;<br>
-       /* TCP sequence number of the packet */<br>
-       uint32_t sent_seq;<br>
-       /* IPv4 ID of the packet */<br>
-       uint16_t ip_id;<br>
-       /* the number of merged packets */<br>
-       uint16_t nb_merged;<br>
-       /* Indicate if IPv4 ID can be ignored */<br>
-       uint8_t is_atomic;<br>
-};<br>
-<br>
 /*<br>
  * TCP/IPv4 reassembly table structure.<br>
  */<br>
 struct gro_tcp4_tbl {<br>
        /* item array */<br>
-       struct gro_tcp4_item *items;<br>
+       struct gro_tcp_item *items;<br>
        /* flow array */<br>
        struct gro_tcp4_flow *flows;<br>
        /* current item number */<br>
@@ -195,111 +152,4 @@ is_same_tcp4_flow(struct tcp4_flow_key k1, struct tcp4_flow_key k2)<br>
                        (k1.dst_port == k2.dst_port));<br>
 }<br>
<br>
-/*<br>
- * Merge two TCP/IPv4 packets without updating checksums.<br>
- * If cmp is larger than 0, append the new packet to the<br>
- * original packet. Otherwise, pre-pend the new packet to<br>
- * the original packet.<br>
- */<br>
-static inline int<br>
-merge_two_tcp4_packets(struct gro_tcp4_item *item,<br>
-               struct rte_mbuf *pkt,<br>
-               int cmp,<br>
-               uint32_t sent_seq,<br>
-               uint16_t ip_id,<br>
-               uint16_t l2_offset)<br>
-{<br>
-       struct rte_mbuf *pkt_head, *pkt_tail, *lastseg;<br>
-       uint16_t hdr_len, l2_len;<br>
-<br>
-       if (cmp > 0) {<br>
-               pkt_head = item->firstseg;<br>
-               pkt_tail = pkt;<br>
-       } else {<br>
-               pkt_head = pkt;<br>
-               pkt_tail = item->firstseg;<br>
-       }<br>
-<br>
-       /* check if the IPv4 packet length is greater than the max value */<br>
-       hdr_len = l2_offset + pkt_head->l2_len + pkt_head->l3_len +<br>
-               pkt_head->l4_len;<br>
-       l2_len = l2_offset > 0 ? pkt_head->outer_l2_len : pkt_head->l2_len;<br>
-       if (unlikely(pkt_head->pkt_len - l2_len + pkt_tail->pkt_len -<br>
-                               hdr_len > MAX_IPV4_PKT_LENGTH))<br>
-               return 0;<br>
-<br>
-       /* remove the packet header for the tail packet */<br>
-       rte_pktmbuf_adj(pkt_tail, hdr_len);<br>
-<br>
-       /* chain two packets together */<br>
-       if (cmp > 0) {<br>
-               item->lastseg->next = pkt;<br>
-               item->lastseg = rte_pktmbuf_lastseg(pkt);<br>
-               /* update IP ID to the larger value */<br>
-               item->ip_id = ip_id;<br>
-       } else {<br>
-               lastseg = rte_pktmbuf_lastseg(pkt);<br>
-               lastseg->next = item->firstseg;<br>
-               item->firstseg = pkt;<br>
-               /* update sent_seq to the smaller value */<br>
-               item->sent_seq = sent_seq;<br>
-               item->ip_id = ip_id;<br>
-       }<br>
-       item->nb_merged++;<br>
-<br>
-       /* update MBUF metadata for the merged packet */<br>
-       pkt_head->nb_segs += pkt_tail->nb_segs;<br>
-       pkt_head->pkt_len += pkt_tail->pkt_len;<br>
-<br>
-       return 1;<br>
-}<br>
-<br>
-/*<br>
- * Check if two TCP/IPv4 packets are neighbors.<br>
- */<br>
-static inline int<br>
-check_seq_option(struct gro_tcp4_item *item,<br>
-               struct rte_tcp_hdr *tcph,<br>
-               uint32_t sent_seq,<br>
-               uint16_t ip_id,<br>
-               uint16_t tcp_hl,<br>
-               uint16_t tcp_dl,<br>
-               uint16_t l2_offset,<br>
-               uint8_t is_atomic)<br>
-{<br>
-       struct rte_mbuf *pkt_orig = item->firstseg;<br>
-       struct rte_ipv4_hdr *iph_orig;<br>
-       struct rte_tcp_hdr *tcph_orig;<br>
-       uint16_t len, tcp_hl_orig;<br>
-<br>
-       iph_orig = (struct rte_ipv4_hdr *)(rte_pktmbuf_mtod(pkt_orig, char *) +<br>
-                       l2_offset + pkt_orig->l2_len);<br>
-       tcph_orig = (struct rte_tcp_hdr *)((char *)iph_orig + pkt_orig->l3_len);<br>
-       tcp_hl_orig = pkt_orig->l4_len;<br>
-<br>
-       /* Check if TCP option fields equal */<br>
-       len = RTE_MAX(tcp_hl, tcp_hl_orig) - sizeof(struct rte_tcp_hdr);<br>
-       if ((tcp_hl != tcp_hl_orig) || ((len > 0) &&<br>
-                               (memcmp(tcph + 1, tcph_orig + 1,<br>
-                                       len) != 0)))<br>
-               return 0;<br>
-<br>
-       /* Don't merge packets whose DF bits are different */<br>
-       if (unlikely(item->is_atomic ^ is_atomic))<br>
-               return 0;<br>
-<br>
-       /* check if the two packets are neighbors */<br>
-       len = pkt_orig->pkt_len - l2_offset - pkt_orig->l2_len -<br>
-               pkt_orig->l3_len - tcp_hl_orig;<br>
-       if ((sent_seq == item->sent_seq + len) && (is_atomic ||<br>
-                               (ip_id == item->ip_id + 1)))<br>
-               /* append the new packet */<br>
-               return 1;<br>
-       else if ((sent_seq + tcp_dl == item->sent_seq) && (is_atomic ||<br>
-                               (ip_id + item->nb_merged == item->ip_id)))<br>
-               /* pre-pend the new packet */<br>
-               return -1;<br>
-<br>
-       return 0;<br>
-}<br>
 #endif<br>
diff --git a/lib/gro/gro_tcp6.c b/lib/gro/gro_tcp6.c<br>
new file mode 100644<br>
index 0000000000..7a739f2472<br>
--- /dev/null<br>
+++ b/lib/gro/gro_tcp6.c<br>
@@ -0,0 +1,388 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause<br>
+ * Copyright(c) 2017 Intel Corporation<br>
+ */<br>
+<br>
+#include <rte_malloc.h><br>
+#include <rte_mbuf.h><br>
+#include <rte_ethdev.h><br>
+<br>
+#include "gro_tcp6.h"<br>
+#include "gro_tcp.h"<br>
+<br>
+void *<br>
+gro_tcp6_tbl_create(uint16_t socket_id,<br>
+               uint16_t max_flow_num,<br>
+               uint16_t max_item_per_flow)<br>
+{<br>
+       struct gro_tcp6_tbl *tbl;<br>
+       size_t size;<br>
+       uint32_t entries_num, i;<br>
+<br>
+       entries_num = max_flow_num * max_item_per_flow;<br>
+       entries_num = RTE_MIN(entries_num, GRO_TCP6_TBL_MAX_ITEM_NUM);<br>
+<br>
+       if (entries_num == 0)<br>
+               return NULL;<br>
+<br>
+       tbl = rte_zmalloc_socket(__func__,<br>
+                       sizeof(struct gro_tcp6_tbl),<br>
+                       RTE_CACHE_LINE_SIZE,<br>
+                       socket_id);<br>
+       if (tbl == NULL)<br>
+               return NULL;<br>
+<br>
+       size = sizeof(struct gro_tcp_item) * entries_num;<br>
+       tbl->items = rte_zmalloc_socket(__func__,<br>
+                       size,<br>
+                       RTE_CACHE_LINE_SIZE,<br>
+                       socket_id);<br>
+       if (tbl->items == NULL) {<br>
+               rte_free(tbl);<br>
+               return NULL;<br>
+       }<br>
+       tbl->max_item_num = entries_num;<br>
+<br>
+       size = sizeof(struct gro_tcp6_flow) * entries_num;<br>
+       tbl->flows = rte_zmalloc_socket(__func__,<br>
+                       size,<br>
+                       RTE_CACHE_LINE_SIZE,<br>
+                       socket_id);<br>
+       if (tbl->flows == NULL) {<br>
+               rte_free(tbl->items);<br>
+               rte_free(tbl);<br>
+               return NULL;<br>
+       }<br>
+       /* INVALID_ARRAY_INDEX indicates an empty flow */<br>
+       for (i = 0; i < entries_num; i++)<br>
+               tbl->flows[i].start_index = INVALID_ARRAY_INDEX;<br>
+       tbl->max_flow_num = entries_num;<br>
+<br>
+       return tbl;<br>
+}<br>
+<br>
+void<br>
+gro_tcp6_tbl_destroy(void *tbl)<br>
+{<br>
+       struct gro_tcp6_tbl *tcp_tbl = tbl;<br>
+<br>
+       if (tcp_tbl) {<br>
+               rte_free(tcp_tbl->items);<br>
+               rte_free(tcp_tbl->flows);<br>
+       }<br>
+       rte_free(tcp_tbl);<br>
+}<br>
+<br>
+static inline uint32_t<br>
+find_an_empty_item(struct gro_tcp6_tbl *tbl)<br>
+{<br>
+       uint32_t i;<br>
+       uint32_t max_item_num = tbl->max_item_num;<br>
+<br>
+       for (i = 0; i < max_item_num; i++)<br>
+               if (tbl->items[i].firstseg == NULL)<br>
+                       return i;<br>
+       return INVALID_ARRAY_INDEX;<br>
+}<br>
+<br>
+static inline uint32_t<br>
+find_an_empty_flow(struct gro_tcp6_tbl *tbl)<br>
+{<br>
+       uint32_t i;<br>
+       uint32_t max_flow_num = tbl->max_flow_num;<br>
+<br>
+       for (i = 0; i < max_flow_num; i++)<br>
+               if (tbl->flows[i].start_index == INVALID_ARRAY_INDEX)<br>
+                       return i;<br>
+       return INVALID_ARRAY_INDEX;<br>
+}<br>
+<br>
+static inline uint32_t<br>
+insert_new_item(struct gro_tcp6_tbl *tbl,<br>
+               struct rte_mbuf *pkt,<br>
+               uint64_t start_time,<br>
+               uint32_t prev_idx,<br>
+               uint32_t sent_seq,<br>
+               uint8_t is_atomic)<br>
+{<br>
+       uint32_t item_idx;<br>
+<br>
+       item_idx = find_an_empty_item(tbl);<br>
+       if (item_idx == INVALID_ARRAY_INDEX)<br>
+               return INVALID_ARRAY_INDEX;<br>
+<br>
+       tbl->items[item_idx].firstseg = pkt;<br>
+       tbl->items[item_idx].lastseg = rte_pktmbuf_lastseg(pkt);<br>
+       tbl->items[item_idx].start_time = start_time;<br>
+       tbl->items[item_idx].next_pkt_idx = INVALID_ARRAY_INDEX;<br>
+       tbl->items[item_idx].sent_seq = sent_seq;<br>
+       tbl->items[item_idx].nb_merged = 1;<br>
+       tbl->items[item_idx].is_atomic = is_atomic;<br>
+       tbl->item_num++;<br>
+<br>
+       /* if the previous packet exists, chain them together. */<br>
+       if (prev_idx != INVALID_ARRAY_INDEX) {<br>
+               tbl->items[item_idx].next_pkt_idx =<br>
+                       tbl->items[prev_idx].next_pkt_idx;<br>
+               tbl->items[prev_idx].next_pkt_idx = item_idx;<br>
+       }<br>
+<br>
+       return item_idx;<br>
+}<br>
+<br>
+static inline uint32_t<br>
+delete_item(struct gro_tcp6_tbl *tbl, uint32_t item_idx,<br>
+               uint32_t prev_item_idx)<br>
+{<br>
+       uint32_t next_idx = tbl->items[item_idx].next_pkt_idx;<br>
+<br>
+       /* NULL indicates an empty item */<br>
+       tbl->items[item_idx].firstseg = NULL;<br>
+       tbl->item_num--;<br>
+       if (prev_item_idx != INVALID_ARRAY_INDEX)<br>
+               tbl->items[prev_item_idx].next_pkt_idx = next_idx;<br>
+<br>
+       return next_idx;<br>
+}<br>
+<br>
+static inline uint32_t<br>
+insert_new_flow(struct gro_tcp6_tbl *tbl,<br>
+               struct tcp6_flow_key *src,<br>
+               rte_be32_t vtc_flow,<br>
+               uint32_t item_idx)<br>
+{<br>
+       struct tcp6_flow_key *dst;<br>
+       uint32_t flow_idx;<br>
+<br>
+       flow_idx = find_an_empty_flow(tbl);<br>
+       if (unlikely(flow_idx == INVALID_ARRAY_INDEX))<br>
+               return INVALID_ARRAY_INDEX;<br>
+<br>
+       dst = &(tbl->flows[flow_idx].key);<br>
+<br>
+       rte_ether_addr_copy(&(src->eth_saddr), &(dst->eth_saddr));<br>
+       rte_ether_addr_copy(&(src->eth_daddr), &(dst->eth_daddr));<br>
+       memcpy(&dst->src_addr[0], &src->src_addr[0], sizeof(dst->src_addr));<br>
+       memcpy(&dst->dst_addr[0], &src->dst_addr[0], sizeof(dst->dst_addr));<br>
+       dst->recv_ack = src->recv_ack;<br>
+       dst->src_port = src->src_port;<br>
+       dst->dst_port = src->dst_port;<br>
+<br>
+       tbl->flows[flow_idx].start_index = item_idx;<br>
+       tbl->flow_num++;<br>
+       tbl->flows->vtc_flow = vtc_flow;<br>
+<br>
+       return flow_idx;<br>
+}<br>
+<br>
+/*<br>
+ * update the packet length for the flushed packet.<br>
+ */<br>
+static inline void<br>
+update_header(struct gro_tcp_item *item)<br>
+{<br>
+       struct rte_ipv6_hdr *ipv6_hdr;<br>
+       struct rte_mbuf *pkt = item->firstseg;<br>
+<br>
+       ipv6_hdr = (struct rte_ipv6_hdr *)(rte_pktmbuf_mtod(pkt, char *) +<br>
+                       pkt->l2_len);<br>
+       ipv6_hdr->payload_len = rte_cpu_to_be_16(pkt->pkt_len -<br>
+                       pkt->l2_len - pkt->l3_len);<br>
+}<br>
+<br>
+int32_t<br>
+gro_tcp6_reassemble(struct rte_mbuf *pkt,<br>
+               struct gro_tcp6_tbl *tbl,<br>
+               uint64_t start_time)<br>
+{<br>
+       struct rte_ether_hdr *eth_hdr;<br>
+       struct rte_ipv6_hdr *ipv6_hdr;<br>
+       struct rte_tcp_hdr *tcp_hdr;<br>
+       uint32_t sent_seq;<br>
+       int32_t tcp_dl;<br>
+       uint16_t ip_tlen;<br>
+       struct tcp6_flow_key key;<br>
+       uint32_t cur_idx, prev_idx, item_idx;<br>
+       uint32_t i, max_flow_num, remaining_flow_num;<br>
+       int cmp;<br>
+       uint8_t find;<br>
+       rte_be32_t vtc_flow_diff;<br>
+<br>
+       /*<br>
+        * Don't process the packet whose TCP header length is greater<br>
+        * than 60 bytes or less than 20 bytes.<br>
+        */<br>
+       if (unlikely(INVALID_TCP_HDRLEN(pkt->l4_len)))<br>
+               return -1;<br>
+<br>
+       eth_hdr = rte_pktmbuf_mtod(pkt, struct rte_ether_hdr *);<br>
+       ipv6_hdr = (struct rte_ipv6_hdr *)((char *)eth_hdr + pkt->l2_len);<br>
+       tcp_hdr = (struct rte_tcp_hdr *)((char *)ipv6_hdr + pkt->l3_len);<br>
+<br>
+       /*<br>
+        * Don't process the packet which has FIN, SYN, RST, PSH, URG, ECE<br>
+        * or CWR set.<br>
+        */<br>
+       if (tcp_hdr->tcp_flags != RTE_TCP_ACK_FLAG)<br>
+               return -1;<br>
+<br>
+       ip_tlen = rte_be_to_cpu_16(ipv6_hdr->payload_len);<br>
+       /* <br>
+        * Trim the tail padding bytes. The IPv6 header is fixed to <br>
+        * 40 bytes unlike IPv4 that is variable. The length in the IPv6 header <br>
+        * contains only length of TCP Header + TCP Payload, whereas IPv4 header contains
<br>
+        * length of IP Header + TCP Header + TCP Payload<br>
+        */<br>
+       if (pkt->pkt_len > (uint32_t)(ip_tlen + pkt->l2_len + pkt->l3_len))<br>
+               rte_pktmbuf_trim(pkt, pkt->pkt_len - ip_tlen - pkt->l2_len - pkt->l3_len);<br>
+       /*<br>
+        * Don't process the packet whose payload length is less than or<br>
+        * equal to 0.<br>
+        */<br>
+       tcp_dl = ip_tlen - pkt->l4_len;<br>
+       if (tcp_dl <= 0)<br>
+               return -1;<br>
+<br>
+       sent_seq = rte_be_to_cpu_32(tcp_hdr->sent_seq);<br>
+<br>
+       rte_ether_addr_copy(&(eth_hdr->src_addr), &(key.eth_saddr));<br>
+       rte_ether_addr_copy(&(eth_hdr->dst_addr), &(key.eth_daddr));<br>
+    memcpy(&key.src_addr[0], &ipv6_hdr->src_addr, sizeof(key.src_addr));<br>
+    memcpy(&key.dst_addr[0], &ipv6_hdr->dst_addr, sizeof(key.dst_addr));<br>
+       key.src_port = tcp_hdr->src_port;<br>
+       key.dst_port = tcp_hdr->dst_port;<br>
+       key.recv_ack = tcp_hdr->recv_ack;<br>
+<br>
+       /* Search for a matched flow. */<br>
+       max_flow_num = tbl->max_flow_num;<br>
+       remaining_flow_num = tbl->flow_num;<br>
+       find = 0;<br>
+       for (i = 0; i < max_flow_num && remaining_flow_num; i++) {<br>
+               if (tbl->flows[i].start_index != INVALID_ARRAY_INDEX) {<br>
+                       if (is_same_tcp6_flow(tbl->flows[i].key, key)) {<br>
+                               /* <br>
+                                * IP version (4) Traffic Class (8) Flow Label (20)
<br>
+                                * All fields except Traffic class should be same<br>
+                               */<br>
+                               vtc_flow_diff = (ipv6_hdr->vtc_flow ^ tbl->flows->vtc_flow);<br>
+                               if (vtc_flow_diff & htonl(0xF00FFFFF)) {<br>
+                                       continue;<br>
+                               }<br>
+                               find = 1;<br>
+                               break;<br>
+                       }<br>
+                       remaining_flow_num--;<br>
+               }<br>
+       }<br>
+<br>
+       /*<br>
+        * Fail to find a matched flow. Insert a new flow and store the<br>
+        * packet into the flow.<br>
+        */<br>
+       if (find == 0) {<br>
+               item_idx = insert_new_item(tbl, pkt, start_time,<br>
+                               INVALID_ARRAY_INDEX, sent_seq, true);<br>
+               if (item_idx == INVALID_ARRAY_INDEX)<br>
+                       return -1;<br>
+               if (insert_new_flow(tbl, &key, ipv6_hdr->vtc_flow, item_idx) ==<br>
+                               INVALID_ARRAY_INDEX) {<br>
+                       /*<br>
+                        * Fail to insert a new flow, so delete the<br>
+                        * stored packet.<br>
+                        */<br>
+                       delete_item(tbl, item_idx, INVALID_ARRAY_INDEX);<br>
+                       return -1;<br>
+               }<br>
+               return 0;<br>
+       }<br>
+<br>
+       /*<br>
+        * Check all packets in the flow and try to find a neighbor for<br>
+        * the input packet.<br>
+        */<br>
+       cur_idx = tbl->flows[i].start_index;<br>
+       prev_idx = cur_idx;<br>
+       do {<br>
+               cmp = check_seq_option(&(tbl->items[cur_idx]), tcp_hdr,<br>
+                               sent_seq, 0, pkt->l4_len, tcp_dl, 0,<br>
+                               true);<br>
+               if (cmp) {<br>
+                       if (merge_two_tcp_packets(&(tbl->items[cur_idx]),<br>
+                                               pkt, cmp, sent_seq, 0, 0)) {<br>
+                               return 1;<br>
+            }<br>
+<br>
+                       /*<br>
+                        * Fail to merge the two packets, as the packet<br>
+                        * length is greater than the max value. Store<br>
+                        * the packet into the flow.<br>
+                        */<br>
+                       if (insert_new_item(tbl, pkt, start_time, cur_idx,<br>
+                                               sent_seq, true) ==<br>
+                                       INVALID_ARRAY_INDEX)<br>
+                               return -1;<br>
+                       return 0;<br>
+               }<br>
+               prev_idx = cur_idx;<br>
+               cur_idx = tbl->items[cur_idx].next_pkt_idx;<br>
+       } while (cur_idx != INVALID_ARRAY_INDEX);<br>
+<br>
+       /* Fail to find a neighbor, so store the packet into the flow. */<br>
+       if (insert_new_item(tbl, pkt, start_time, prev_idx, sent_seq,<br>
+                               true) == INVALID_ARRAY_INDEX)<br>
+               return -1;<br>
+<br>
+       return 0;<br>
+}<br>
+<br>
+uint16_t<br>
+gro_tcp6_tbl_timeout_flush(struct gro_tcp6_tbl *tbl,<br>
+               uint64_t flush_timestamp,<br>
+               struct rte_mbuf **out,<br>
+               uint16_t nb_out)<br>
+{<br>
+       uint16_t k = 0;<br>
+       uint32_t i, j;<br>
+       uint32_t max_flow_num = tbl->max_flow_num;<br>
+<br>
+       for (i = 0; i < max_flow_num; i++) {<br>
+               if (unlikely(tbl->flow_num == 0))<br>
+                       return k;<br>
+<br>
+               j = tbl->flows[i].start_index;<br>
+               while (j != INVALID_ARRAY_INDEX) {<br>
+                       if (tbl->items[j].start_time <= flush_timestamp) {<br>
+                               out[k++] = tbl->items[j].firstseg;<br>
+                               if (tbl->items[j].nb_merged > 1)<br>
+                                       update_header(&(tbl->items[j]));<br>
+                               /*<br>
+                                * Delete the packet and get the next<br>
+                                * packet in the flow.<br>
+                                */<br>
+                               j = delete_item(tbl, j, INVALID_ARRAY_INDEX);<br>
+                               tbl->flows[i].start_index = j;<br>
+                               if (j == INVALID_ARRAY_INDEX)<br>
+                                       tbl->flow_num--;<br>
+<br>
+                               if (unlikely(k == nb_out))<br>
+                                       return k;<br>
+                       } else<br>
+                               /*<br>
+                                * The left packets in this flow won't be<br>
+                                * timeout. Go to check other flows.<br>
+                                */<br>
+                               break;<br>
+               }<br>
+       }<br>
+       return k;<br>
+}<br>
+<br>
+uint32_t<br>
+gro_tcp6_tbl_pkt_count(void *tbl)<br>
+{<br>
+       struct gro_tcp6_tbl *gro_tbl = tbl;<br>
+<br>
+       if (gro_tbl)<br>
+               return gro_tbl->item_num;<br>
+<br>
+       return 0;<br>
+}<br>
diff --git a/lib/gro/gro_tcp6.h b/lib/gro/gro_tcp6.h<br>
new file mode 100644<br>
index 0000000000..aea231adbb<br>
--- /dev/null<br>
+++ b/lib/gro/gro_tcp6.h<br>
@@ -0,0 +1,150 @@<br>
+/* SPDX-License-Identifier: BSD-3-Clause<br>
+ * Copyright(c) 2017 Intel Corporation<br>
+ */<br>
+<br>
+#ifndef _GRO_TCP6_H_<br>
+#define _GRO_TCP6_H_<br>
+<br>
+#define INVALID_ARRAY_INDEX 0xffffffffUL<br>
+#define GRO_TCP6_TBL_MAX_ITEM_NUM (1024UL * 1024UL)<br>
+<br>
+/* Header fields representing a TCP/IPv6 flow */<br>
+struct tcp6_flow_key {<br>
+       struct rte_ether_addr eth_saddr;<br>
+       struct rte_ether_addr eth_daddr;<br>
+    uint8_t  src_addr[16];<br>
+    uint8_t  dst_addr[16];<br>
+<br>
+       uint32_t recv_ack;<br>
+       uint16_t src_port;<br>
+       uint16_t dst_port;<br>
+};<br>
+<br>
+struct gro_tcp6_flow {<br>
+       struct tcp6_flow_key key;<br>
+       rte_be32_t vtc_flow;<br>
+       /*<br>
+        * The index of the first packet in the flow.<br>
+        * INVALID_ARRAY_INDEX indicates an empty flow.<br>
+        */<br>
+       uint32_t start_index;<br>
+};<br>
+<br>
+/*<br>
+ * TCP/IPv6 reassembly table structure.<br>
+ */<br>
+struct gro_tcp6_tbl {<br>
+       /* item array */<br>
+       struct gro_tcp_item *items;<br>
+       /* flow array */<br>
+       struct gro_tcp6_flow *flows;<br>
+       /* current item number */<br>
+       uint32_t item_num;<br>
+       /* current flow num */<br>
+       uint32_t flow_num;<br>
+       /* item array size */<br>
+       uint32_t max_item_num;<br>
+       /* flow array size */<br>
+       uint32_t max_flow_num;<br>
+};<br>
+<br>
+/**<br>
+ * This function creates a TCP/IPv6 reassembly table.<br>
+ *<br>
+ * @param socket_id<br>
+ *  Socket index for allocating the TCP/IPv6 reassemble table<br>
+ * @param max_flow_num<br>
+ *  The maximum number of flows in the TCP/IPv6 GRO table<br>
+ * @param max_item_per_flow<br>
+ *  The maximum number of packets per flow<br>
+ *<br>
+ * @return<br>
+ *  - Return the table pointer on success.<br>
+ *  - Return NULL on failure.<br>
+ */<br>
+void *gro_tcp6_tbl_create(uint16_t socket_id,<br>
+               uint16_t max_flow_num,<br>
+               uint16_t max_item_per_flow);<br>
+<br>
+/**<br>
+ * This function destroys a TCP/IPv6 reassembly table.<br>
+ *<br>
+ * @param tbl<br>
+ *  Pointer pointing to the TCP/IPv6 reassembly table.<br>
+ */<br>
+void gro_tcp6_tbl_destroy(void *tbl);<br>
+<br>
+/**<br>
+ * This function merges a TCP/IPv6 packet. It doesn't process the packet,<br>
+ * which has SYN, FIN, RST, PSH, CWR, ECE or URG set, or doesn't have<br>
+ * payload.<br>
+ *<br>
+ * This function doesn't check if the packet has correct checksums and<br>
+ * doesn't re-calculate checksums for the merged packet. Additionally,<br>
+ * it assumes the packets are complete (i.e., MF==0 && frag_off==0),<br>
+ * when IP fragmentation is possible (i.e., DF==0). It returns the<br>
+ * packet, if the packet has invalid parameters (e.g. SYN bit is set)<br>
+ * or there is no available space in the table.<br>
+ *<br>
+ * @param pkt<br>
+ *  Packet to reassemble<br>
+ * @param tbl<br>
+ *  Pointer pointing to the TCP/IPv6 reassembly table<br>
+ * @start_time<br>
+ *  The time when the packet is inserted into the table<br>
+ *<br>
+ * @return<br>
+ *  - Return a positive value if the packet is merged.<br>
+ *  - Return zero if the packet isn't merged but stored in the table.<br>
+ *  - Return a negative value for invalid parameters or no available<br>
+ *    space in the table.<br>
+ */<br>
+int32_t gro_tcp6_reassemble(struct rte_mbuf *pkt,<br>
+               struct gro_tcp6_tbl *tbl,<br>
+               uint64_t start_time);<br>
+<br>
+/**<br>
+ * This function flushes timeout packets in a TCP/IPv4 reassembly table,<br>
+ * and without updating checksums.<br>
+ *<br>
+ * @param tbl<br>
+ *  TCP/IPv4 reassembly table pointer<br>
+ * @param flush_timestamp<br>
+ *  Flush packets which are inserted into the table before or at the<br>
+ *  flush_timestamp.<br>
+ * @param out<br>
+ *  Pointer array used to keep flushed packets<br>
+ * @param nb_out<br>
+ *  The element number in 'out'. It also determines the maximum number of<br>
+ *  packets that can be flushed finally.<br>
+ *<br>
+ * @return<br>
+ *  The number of flushed packets<br>
+ */<br>
+uint16_t gro_tcp6_tbl_timeout_flush(struct gro_tcp6_tbl *tbl,<br>
+               uint64_t flush_timestamp,<br>
+               struct rte_mbuf **out,<br>
+               uint16_t nb_out);<br>
+<br>
+/**<br>
+ * This function returns the number of the packets in a TCP/IPv4<br>
+ * reassembly table.<br>
+ *<br>
+ * @param tbl<br>
+ *  TCP/IPv4 reassembly table pointer<br>
+ *<br>
+ * @return<br>
+ *  The number of packets in the table<br>
+ */<br>
+uint32_t gro_tcp6_tbl_pkt_count(void *tbl);<br>
+<br>
+/*<br>
+ * Check if two TCP/IPv4 packets belong to the same flow.<br>
+ */<br>
+static inline int<br>
+is_same_tcp6_flow(struct tcp6_flow_key k1, struct tcp6_flow_key k2)<br>
+{<br>
+    return (!memcmp(&k1, &k2, sizeof(struct tcp6_flow_key)));<br>
+}<br>
+<br>
+#endif<br>
\ No newline at end of file<br>
diff --git a/lib/gro/gro_vxlan_tcp4.c b/lib/gro/gro_vxlan_tcp4.c<br>
index 3be4deb7c7..56b30b8c98 100644<br>
--- a/lib/gro/gro_vxlan_tcp4.c<br>
+++ b/lib/gro/gro_vxlan_tcp4.c<br>
@@ -7,6 +7,7 @@<br>
 #include <rte_ethdev.h><br>
 #include <rte_udp.h><br>
<br>
+#include "gro_tcp.h"<br>
 #include "gro_vxlan_tcp4.h"<br>
<br>
 void *<br>
@@ -248,7 +249,7 @@ merge_two_vxlan_tcp4_packets(struct gro_vxlan_tcp4_item *item,<br>
                uint16_t outer_ip_id,<br>
                uint16_t ip_id)<br>
 {<br>
-       if (merge_two_tcp4_packets(&item->inner_item, pkt, cmp, sent_seq,<br>
+       if (merge_two_tcp_packets(&item->inner_item, pkt, cmp, sent_seq,<br>
                                ip_id, pkt->outer_l2_len +<br>
                                pkt->outer_l3_len)) {<br>
                /* Update the outer IPv4 ID to the large value. */<br>
diff --git a/lib/gro/gro_vxlan_tcp4.h b/lib/gro/gro_vxlan_tcp4.h<br>
index 7832942a68..d68d5fcd5b 100644<br>
--- a/lib/gro/gro_vxlan_tcp4.h<br>
+++ b/lib/gro/gro_vxlan_tcp4.h<br>
@@ -5,6 +5,7 @@<br>
 #ifndef _GRO_VXLAN_TCP4_H_<br>
 #define _GRO_VXLAN_TCP4_H_<br>
<br>
+#include "gro_tcp.h"<br>
 #include "gro_tcp4.h"<br>
<br>
 #define GRO_VXLAN_TCP4_TBL_MAX_ITEM_NUM (1024UL * 1024UL)<br>
@@ -36,7 +37,7 @@ struct gro_vxlan_tcp4_flow {<br>
 };<br>
<br>
 struct gro_vxlan_tcp4_item {<br>
-       struct gro_tcp4_item inner_item;<br>
+       struct gro_tcp_item inner_item;<br>
        /* IPv4 ID in the outer IPv4 header */<br>
        uint16_t outer_ip_id;<br>
        /* Indicate if outer IPv4 ID can be ignored */<br>
diff --git a/lib/gro/meson.build b/lib/gro/meson.build<br>
index e4fa2958bd..dbce05220d 100644<br>
--- a/lib/gro/meson.build<br>
+++ b/lib/gro/meson.build<br>
@@ -4,6 +4,7 @@<br>
 sources = files(<br>
         'rte_gro.c',<br>
         'gro_tcp4.c',<br>
+        'gro_tcp6.c',<br>
         'gro_udp4.c',<br>
         'gro_vxlan_tcp4.c',<br>
         'gro_vxlan_udp4.c',<br>
diff --git a/lib/gro/rte_gro.c b/lib/gro/rte_gro.c<br>
index e35399fd42..c8180d24e3 100644<br>
--- a/lib/gro/rte_gro.c<br>
+++ b/lib/gro/rte_gro.c<br>
@@ -8,6 +8,7 @@<br>
<br>
 #include "rte_gro.h"<br>
 #include "gro_tcp4.h"<br>
+#include "gro_tcp6.h"<br>
 #include "gro_udp4.h"<br>
 #include "gro_vxlan_tcp4.h"<br>
 #include "gro_vxlan_udp4.h"<br>
@@ -20,14 +21,16 @@ typedef uint32_t (*gro_tbl_pkt_count_fn)(void *tbl);<br>
<br>
 static gro_tbl_create_fn tbl_create_fn[RTE_GRO_TYPE_MAX_NUM] = {<br>
                gro_tcp4_tbl_create, gro_vxlan_tcp4_tbl_create,<br>
-               gro_udp4_tbl_create, gro_vxlan_udp4_tbl_create, NULL};<br>
+               gro_udp4_tbl_create, gro_vxlan_udp4_tbl_create, gro_tcp6_tbl_create, NULL};<br>
 static gro_tbl_destroy_fn tbl_destroy_fn[RTE_GRO_TYPE_MAX_NUM] = {<br>
                        gro_tcp4_tbl_destroy, gro_vxlan_tcp4_tbl_destroy,<br>
                        gro_udp4_tbl_destroy, gro_vxlan_udp4_tbl_destroy,<br>
+                       gro_tcp6_tbl_destroy,<br>
                        NULL};<br>
 static gro_tbl_pkt_count_fn tbl_pkt_count_fn[RTE_GRO_TYPE_MAX_NUM] = {<br>
                        gro_tcp4_tbl_pkt_count, gro_vxlan_tcp4_tbl_pkt_count,<br>
                        gro_udp4_tbl_pkt_count, gro_vxlan_udp4_tbl_pkt_count,<br>
+                       gro_tcp6_tbl_pkt_count,<br>
                        NULL};<br>
<br>
 #define IS_IPV4_TCP_PKT(ptype) (RTE_ETH_IS_IPV4_HDR(ptype) && \<br>
@@ -35,6 +38,12 @@ static gro_tbl_pkt_count_fn tbl_pkt_count_fn[RTE_GRO_TYPE_MAX_NUM] = {<br>
                ((ptype & RTE_PTYPE_L4_FRAG) != RTE_PTYPE_L4_FRAG) && \<br>
                (RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))<br>
<br>
+/* GRO with extension headers is not supported */<br>
+#define IS_IPV6_TCP_PKT(ptype) (RTE_ETH_IS_IPV6_HDR(ptype) && \<br>
+               ((ptype & RTE_PTYPE_L4_TCP) == RTE_PTYPE_L4_TCP) && \<br>
+               ((ptype & RTE_PTYPE_L4_FRAG) != RTE_PTYPE_L4_FRAG) && \<br>
+               (RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))<br>
+<br>
 #define IS_IPV4_UDP_PKT(ptype) (RTE_ETH_IS_IPV4_HDR(ptype) && \<br>
                ((ptype & RTE_PTYPE_L4_UDP) == RTE_PTYPE_L4_UDP) && \<br>
                (RTE_ETH_IS_TUNNEL_PKT(ptype) == 0))<br>
@@ -147,7 +156,11 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
        /* allocate a reassembly table for TCP/IPv4 GRO */<br>
        struct gro_tcp4_tbl tcp_tbl;<br>
        struct gro_tcp4_flow tcp_flows[RTE_GRO_MAX_BURST_ITEM_NUM];<br>
-       struct gro_tcp4_item tcp_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };<br>
+       struct gro_tcp_item tcp_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };<br>
+<br>
+       struct gro_tcp6_tbl tcp6_tbl;<br>
+       struct gro_tcp6_flow tcp6_flows[RTE_GRO_MAX_BURST_ITEM_NUM];<br>
+       struct gro_tcp_item tcp6_items[RTE_GRO_MAX_BURST_ITEM_NUM] = {{0} };<br>
<br>
        /* allocate a reassembly table for UDP/IPv4 GRO */<br>
        struct gro_udp4_tbl udp_tbl;<br>
@@ -171,10 +184,10 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
        int32_t ret;<br>
        uint16_t i, unprocess_num = 0, nb_after_gro = nb_pkts;<br>
        uint8_t do_tcp4_gro = 0, do_vxlan_tcp_gro = 0, do_udp4_gro = 0,<br>
-               do_vxlan_udp_gro = 0;<br>
+               do_vxlan_udp_gro = 0, do_tcp6_gro = 0;<br>
<br>
        if (unlikely((param->gro_types & (RTE_GRO_IPV4_VXLAN_TCP_IPV4 |<br>
-                                       RTE_GRO_TCP_IPV4 |<br>
+                                       RTE_GRO_TCP_IPV4 | RTE_GRO_TCP_IPV6 |<br>
                                        RTE_GRO_IPV4_VXLAN_UDP_IPV4 |<br>
                                        RTE_GRO_UDP_IPV4)) == 0))<br>
                return nb_pkts;<br>
@@ -236,6 +249,18 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
                do_udp4_gro = 1;<br>
        }<br>
<br>
+       if (param->gro_types & RTE_GRO_TCP_IPV6) {<br>
+               for (i = 0; i < item_num; i++)<br>
+                       tcp6_flows[i].start_index = INVALID_ARRAY_INDEX;<br>
+<br>
+               tcp6_tbl.flows = tcp6_flows;<br>
+               tcp6_tbl.items = tcp6_items;<br>
+               tcp6_tbl.flow_num = 0;<br>
+               tcp6_tbl.item_num = 0;<br>
+               tcp6_tbl.max_flow_num = item_num;<br>
+               tcp6_tbl.max_item_num = item_num;<br>
+               do_tcp6_gro = 1;<br>
+       }<br>
<br>
        for (i = 0; i < nb_pkts; i++) {<br>
                /*<br>
@@ -276,6 +301,14 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
                                nb_after_gro--;<br>
                        else if (ret < 0)<br>
                                unprocess_pkts[unprocess_num++] = pkts[i];<br>
+               } else if (IS_IPV6_TCP_PKT(pkts[i]->packet_type) && <br>
+                               do_tcp6_gro) {<br>
+                       ret = gro_tcp6_reassemble(pkts[i], &tcp6_tbl, 0);<br>
+                       if (ret > 0)<br>
+                               /* merge successfully */<br>
+                               nb_after_gro--;<br>
+                       else if (ret < 0)<br>
+                               unprocess_pkts[unprocess_num++] = pkts[i];<br>
                } else<br>
                        unprocess_pkts[unprocess_num++] = pkts[i];<br>
        }<br>
@@ -283,9 +316,17 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
        if ((nb_after_gro < nb_pkts)<br>
                 || (unprocess_num < nb_pkts)) {<br>
                i = 0;<br>
+               /* Copy unprocessed packets */<br>
+               if (unprocess_num > 0) {<br>
+                       memcpy(&pkts[i], unprocess_pkts,<br>
+                                       sizeof(struct rte_mbuf *) *<br>
+                                       unprocess_num);<br>
+                       i = unprocess_num;<br>
+               }<br>
+<br>
                /* Flush all packets from the tables */<br>
                if (do_vxlan_tcp_gro) {<br>
-                       i = gro_vxlan_tcp4_tbl_timeout_flush(&vxlan_tcp_tbl,<br>
+                       i += gro_vxlan_tcp4_tbl_timeout_flush(&vxlan_tcp_tbl,<br>
                                        0, pkts, nb_pkts);<br>
                }<br>
<br>
@@ -304,13 +345,11 @@ rte_gro_reassemble_burst(struct rte_mbuf **pkts,<br>
                        i += gro_udp4_tbl_timeout_flush(&udp_tbl, 0,<br>
                                        &pkts[i], nb_pkts - i);<br>
                }<br>
-               /* Copy unprocessed packets */<br>
-               if (unprocess_num > 0) {<br>
-                       memcpy(&pkts[i], unprocess_pkts,<br>
-                                       sizeof(struct rte_mbuf *) *<br>
-                                       unprocess_num);<br>
+<br>
+               if (do_tcp6_gro) {<br>
+                       i += gro_tcp6_tbl_timeout_flush(&tcp6_tbl, 0, <br>
+                                       &pkts[i], nb_pkts - i);<br>
                }<br>
-               nb_after_gro = i + unprocess_num;<br>
        }<br>
<br>
        return nb_after_gro;<br>
@@ -323,13 +362,13 @@ rte_gro_reassemble(struct rte_mbuf **pkts,<br>
 {<br>
        struct rte_mbuf *unprocess_pkts[nb_pkts];<br>
        struct gro_ctx *gro_ctx = ctx;<br>
-       void *tcp_tbl, *udp_tbl, *vxlan_tcp_tbl, *vxlan_udp_tbl;<br>
+       void *tcp_tbl, *udp_tbl, *vxlan_tcp_tbl, *vxlan_udp_tbl, *tcp6_tbl;<br>
        uint64_t current_time;<br>
        uint16_t i, unprocess_num = 0;<br>
-       uint8_t do_tcp4_gro, do_vxlan_tcp_gro, do_udp4_gro, do_vxlan_udp_gro;<br>
+       uint8_t do_tcp4_gro, do_vxlan_tcp_gro, do_udp4_gro, do_vxlan_udp_gro, do_tcp6_gro;<br>
<br>
        if (unlikely((gro_ctx->gro_types & (RTE_GRO_IPV4_VXLAN_TCP_IPV4 |<br>
-                                       RTE_GRO_TCP_IPV4 |<br>
+                                       RTE_GRO_TCP_IPV4 | RTE_GRO_TCP_IPV6 | <br>
                                        RTE_GRO_IPV4_VXLAN_UDP_IPV4 |<br>
                                        RTE_GRO_UDP_IPV4)) == 0))<br>
                return nb_pkts;<br>
@@ -338,6 +377,7 @@ rte_gro_reassemble(struct rte_mbuf **pkts,<br>
        vxlan_tcp_tbl = gro_ctx->tbls[RTE_GRO_IPV4_VXLAN_TCP_IPV4_INDEX];<br>
        udp_tbl = gro_ctx->tbls[RTE_GRO_UDP_IPV4_INDEX];<br>
        vxlan_udp_tbl = gro_ctx->tbls[RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX];<br>
+       tcp6_tbl = gro_ctx->tbls[RTE_GRO_TCP_IPV6_INDEX];<br>
<br>
        do_tcp4_gro = (gro_ctx->gro_types & RTE_GRO_TCP_IPV4) ==<br>
                RTE_GRO_TCP_IPV4;<br>
@@ -347,6 +387,7 @@ rte_gro_reassemble(struct rte_mbuf **pkts,<br>
                RTE_GRO_UDP_IPV4;<br>
        do_vxlan_udp_gro = (gro_ctx->gro_types & RTE_GRO_IPV4_VXLAN_UDP_IPV4) ==<br>
                RTE_GRO_IPV4_VXLAN_UDP_IPV4;<br>
+       do_tcp6_gro = (gro_ctx->gro_types & RTE_GRO_TCP_IPV6) == RTE_GRO_TCP_IPV6;<br>
<br>
        current_time = rte_rdtsc();<br>
<br>
@@ -371,7 +412,13 @@ rte_gro_reassemble(struct rte_mbuf **pkts,<br>
                        if (gro_udp4_reassemble(pkts[i], udp_tbl,<br>
                                                current_time) < 0)<br>
                                unprocess_pkts[unprocess_num++] = pkts[i];<br>
-               } else<br>
+               } else if (IS_IPV6_TCP_PKT(pkts[i]->packet_type) && <br>
+                               do_tcp6_gro) {<br>
+                       if (gro_tcp6_reassemble(pkts[i], tcp6_tbl, <br>
+                                               current_time) < 0)<br>
+                               unprocess_pkts[unprocess_num++] = pkts[i];<br>
+               }<br>
+               else<br>
                        unprocess_pkts[unprocess_num++] = pkts[i];<br>
        }<br>
        if (unprocess_num > 0) {<br>
@@ -426,6 +473,15 @@ rte_gro_timeout_flush(void *ctx,<br>
                                gro_ctx->tbls[RTE_GRO_UDP_IPV4_INDEX],<br>
                                flush_timestamp,<br>
                                &out[num], left_nb_out);<br>
+               left_nb_out = max_nb_out - num;<br>
+       }<br>
+<br>
+       if ((gro_types & RTE_GRO_TCP_IPV6) && left_nb_out > 0) {<br>
+               num += gro_tcp6_tbl_timeout_flush(<br>
+                               gro_ctx->tbls[RTE_GRO_TCP_IPV6_INDEX],<br>
+                               flush_timestamp,<br>
+                               &out[num], left_nb_out);<br>
+<br>
        }<br>
<br>
        return num;<br>
diff --git a/lib/gro/rte_gro.h b/lib/gro/rte_gro.h<br>
index 9f9ed4935a..ac5a464cf1 100644<br>
--- a/lib/gro/rte_gro.h<br>
+++ b/lib/gro/rte_gro.h<br>
@@ -37,6 +37,9 @@ extern "C" {<br>
 /**< UDP/IPv4 GRO flag */<br>
 #define RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX 3<br>
 #define RTE_GRO_IPV4_VXLAN_UDP_IPV4 (1ULL << RTE_GRO_IPV4_VXLAN_UDP_IPV4_INDEX)<br>
+<br>
+#define RTE_GRO_TCP_IPV6_INDEX 4<br>
+#define RTE_GRO_TCP_IPV6 (1ULL << RTE_GRO_TCP_IPV6_INDEX)<br>
 /**< VxLAN UDP/IPv4 GRO flag. */<br>
<br>
 /**<br>
-- <br>
2.25.1<o:p></o:p></p>
</blockquote>
</div>
</div>
</div>
</div>
</div>
</body>
</html>