diff mbox

[RFC,v2,09/10] virtio-net rsc: Add IPv6 support

Message ID 1454264009-24094-10-git-send-email-wexu@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Wei Xu Jan. 31, 2016, 6:13 p.m. UTC
From: Wei Xu <wei@wei-thinkpad.nay.redhat.com>

A few more stuffs should be included to support this
1. Corresponding chain lookup
2. Coalescing callback for the protocol chain
3. Filter & Sanity Check.

Signed-off-by: Wei Xu <wexu@redhat.com>
---
 hw/net/virtio-net.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 102 insertions(+), 2 deletions(-)

Comments

Jason Wang Feb. 1, 2016, 7:14 a.m. UTC | #1
On 02/01/2016 02:13 AM, wexu@redhat.com wrote:
> From: Wei Xu <wei@wei-thinkpad.nay.redhat.com>
>
> A few more stuffs should be included to support this
> 1. Corresponding chain lookup
> 2. Coalescing callback for the protocol chain
> 3. Filter & Sanity Check.
>
> Signed-off-by: Wei Xu <wexu@redhat.com>
> ---
>  hw/net/virtio-net.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++-
>  1 file changed, 102 insertions(+), 2 deletions(-)
>
> diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
> index 9b44762..c9f6bfc 100644
> --- a/hw/net/virtio-net.c
> +++ b/hw/net/virtio-net.c
> @@ -46,12 +46,19 @@
>  #define TCP4_OFFSET (IP_OFFSET + sizeof(struct ip_header)) /* tcp4 header */
>  #define TCP4_PORT_OFFSET TCP4_OFFSET        /* tcp4 port offset */
>  #define IP4_ADDR_SIZE   8                   /* ipv4 saddr + daddr */
> +
> +#define IP6_ADDR_OFFSET (IP_OFFSET + 8)     /* ipv6 address start */
> +#define TCP6_OFFSET (IP_OFFSET + sizeof(struct ip6_header)) /* tcp6 header */
> +#define TCP6_PORT_OFFSET TCP6_OFFSET        /* tcp6 port offset */
> +#define IP6_ADDR_SIZE   32                  /* ipv6 saddr + daddr */
>  #define TCP_PORT_SIZE   4                   /* sport + dport */
>  #define TCP_WINDOW      65535
>  
>  /* IPv4 max payload, 16 bits in the header */
>  #define MAX_IP4_PAYLOAD  (65535 - sizeof(struct ip_header))
>  
> +/* ip6 max payload, payload in ipv6 don't include the  header */
> +#define MAX_IP6_PAYLOAD  65535
>  #define MAX_VIRTIO_IP_PAYLOAD  (65535 + IP_OFFSET)
>  
>  /* Purge coalesced packets timer interval */
> @@ -1856,6 +1863,42 @@ static int32_t virtio_net_rsc_try_coalesce4(NetRscChain *chain,
>                                      o_data, &o_ip->ip_len, MAX_IP4_PAYLOAD);
>  }
>  
> +static int32_t virtio_net_rsc_try_coalesce6(NetRscChain *chain,
> +                        NetRscSeg *seg, const uint8_t *buf, size_t size)
> +{
> +    uint16_t o_ip_len, n_ip_len;    /* len in ip header field */
> +    uint16_t n_tcp_len, o_tcp_len;  /* tcp header len */
> +    uint16_t o_data, n_data;        /* payload without virtio/eth/ip/tcp */
> +    struct ip6_header *n_ip, *o_ip;
> +    struct tcp_header *n_tcp, *o_tcp;
> +
> +    n_ip = (struct ip6_header *)(buf + IP_OFFSET);
> +    n_ip_len = htons(n_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
> +    n_tcp = (struct tcp_header *)(((uint8_t *)n_ip)\
> +                                    + sizeof(struct ip6_header));
> +    n_tcp_len = (htons(n_tcp->th_offset_flags) & 0xF000) >> 10;
> +    n_data = n_ip_len - n_tcp_len;
> +
> +    o_ip = (struct ip6_header *)(seg->buf + IP_OFFSET);
> +    o_ip_len = htons(o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
> +    o_tcp = (struct tcp_header *)(((uint8_t *)o_ip)\
> +                                    + sizeof(struct ip6_header));
> +    o_tcp_len = (htons(o_tcp->th_offset_flags) & 0xF000) >> 10;
> +    o_data = o_ip_len - o_tcp_len;

Like I've replied in previous mails, need a helper or just store
pointers to both ip and tcp in seg.

> +
> +    if (memcmp(&n_ip->ip6_src, &o_ip->ip6_src, sizeof(struct in6_address))
> +        || memcmp(&n_ip->ip6_dst, &o_ip->ip6_dst, sizeof(struct in6_address))
> +        || (n_tcp->th_sport ^ o_tcp->th_sport)
> +        || (n_tcp->th_dport ^ o_tcp->th_dport)) {
> +            return RSC_NO_MATCH;
> +    }

And if you still want to handle coalescing in a layer style, better
delay the check of ports to tcp function.

> +
> +    /* There is a difference between payload lenght in ipv4 and v6,
> +       ip header is excluded in ipv6 */
> +    return virtio_net_rsc_coalesce_tcp(chain, seg, buf,
> +                       n_tcp, n_tcp_len, n_data, o_tcp, o_tcp_len, o_data,
> +                       &o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen, MAX_IP6_PAYLOAD);
> +}
>  
>  /* Pakcets with 'SYN' should bypass, other flag should be sent after drain
>   * to prevent out of order */
> @@ -2015,6 +2058,59 @@ static size_t virtio_net_rsc_receive4(void *opq, NetClientState* nc,
>                                     virtio_net_rsc_try_coalesce4);
>  }
>  
> +static int32_t virtio_net_rsc_filter6(NetRscChain *chain, struct ip6_header *ip,
> +                                      const uint8_t *buf, size_t size)
> +{
> +    uint16_t ip_len;
> +
> +    if (size < (TCP6_OFFSET + sizeof(tcp_header))) {
> +        return RSC_BYPASS;
> +    }
> +
> +    if (0x6 != (0xF & ip->ip6_ctlun.ip6_un1.ip6_un1_flow)) {
> +        return RSC_BYPASS;
> +    }
> +
> +    /* Both option and protocol is checked in this */
> +    if (ip->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_TCP) {
> +        return RSC_BYPASS;
> +    }
> +
> +    /* Sanity check */
> +    ip_len = htons(ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
> +    if (ip_len < sizeof(struct tcp_header)
> +        || ip_len > (size - TCP6_OFFSET)) {
> +        return RSC_BYPASS;
> +    }
> +
> +    return 0;

RSC_WANT?

> +}
> +
> +static size_t virtio_net_rsc_receive6(void *opq, NetClientState* nc,
> +                                      const uint8_t *buf, size_t size)
> +{
> +    int32_t ret;
> +    NetRscChain *chain;
> +    struct ip6_header *ip;
> +
> +    chain = (NetRscChain *)opq;
> +    ip = (struct ip6_header *)(buf + IP_OFFSET);
> +    if (RSC_WANT != virtio_net_rsc_filter6(chain, ip, buf, size)) {
> +        return virtio_net_do_receive(nc, buf, size);
> +    }
> +
> +    ret = virtio_net_rsc_parse_tcp_ctrl((uint8_t *)ip, sizeof(*ip));

Same as IPv4, looks like a layer violation.

> +    if (RSC_BYPASS == ret) {
> +        return virtio_net_do_receive(nc, buf, size);
> +    } else if (RSC_FINAL == ret) {
> +        return virtio_net_rsc_drain_one(chain, nc, buf, size, IP6_ADDR_OFFSET,
> +                        IP6_ADDR_SIZE, TCP6_PORT_OFFSET, TCP_PORT_SIZE);
> +    }
> +
> +    return virtio_net_rsc_callback(chain, nc, buf, size,
> +                                   virtio_net_rsc_try_coalesce6);
> +}
> +
>  static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>                                                  uint16_t proto)
>  {
> @@ -2023,7 +2119,7 @@ static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>      NICState *nic;
>  
>      /* Only handle IPv4/6 */
> -    if (proto != (uint16_t)ETH_P_IP) {
> +    if ((proto != (uint16_t)ETH_P_IP) && (proto != (uint16_t)ETH_P_IPV6)) {
>          return NULL;
>      }
>  
> @@ -2044,7 +2140,11 @@ static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>      chain->proto = proto;
>      chain->drain_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>                                        virtio_net_rsc_purge, chain);
> -    chain->do_receive = virtio_net_rsc_receive4;
> +    if (ETH_P_IP == proto) {
> +        chain->do_receive = virtio_net_rsc_receive4;
> +    } else {
> +        chain->do_receive = virtio_net_rsc_receive6;
> +    }
>  
>      QTAILQ_INIT(&chain->buffers);
>      QTAILQ_INSERT_TAIL(&n->rsc_chains, chain, next);
Wei Xu Feb. 1, 2016, 8:49 a.m. UTC | #2
On 02/01/2016 03:14 PM, Jason Wang wrote:
>
> On 02/01/2016 02:13 AM, wexu@redhat.com wrote:
>> From: Wei Xu <wei@wei-thinkpad.nay.redhat.com>
>>
>> A few more stuffs should be included to support this
>> 1. Corresponding chain lookup
>> 2. Coalescing callback for the protocol chain
>> 3. Filter & Sanity Check.
>>
>> Signed-off-by: Wei Xu <wexu@redhat.com>
>> ---
>>   hw/net/virtio-net.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++-
>>   1 file changed, 102 insertions(+), 2 deletions(-)
>>
>> diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
>> index 9b44762..c9f6bfc 100644
>> --- a/hw/net/virtio-net.c
>> +++ b/hw/net/virtio-net.c
>> @@ -46,12 +46,19 @@
>>   #define TCP4_OFFSET (IP_OFFSET + sizeof(struct ip_header)) /* tcp4 header */
>>   #define TCP4_PORT_OFFSET TCP4_OFFSET        /* tcp4 port offset */
>>   #define IP4_ADDR_SIZE   8                   /* ipv4 saddr + daddr */
>> +
>> +#define IP6_ADDR_OFFSET (IP_OFFSET + 8)     /* ipv6 address start */
>> +#define TCP6_OFFSET (IP_OFFSET + sizeof(struct ip6_header)) /* tcp6 header */
>> +#define TCP6_PORT_OFFSET TCP6_OFFSET        /* tcp6 port offset */
>> +#define IP6_ADDR_SIZE   32                  /* ipv6 saddr + daddr */
>>   #define TCP_PORT_SIZE   4                   /* sport + dport */
>>   #define TCP_WINDOW      65535
>>   
>>   /* IPv4 max payload, 16 bits in the header */
>>   #define MAX_IP4_PAYLOAD  (65535 - sizeof(struct ip_header))
>>   
>> +/* ip6 max payload, payload in ipv6 don't include the  header */
>> +#define MAX_IP6_PAYLOAD  65535
>>   #define MAX_VIRTIO_IP_PAYLOAD  (65535 + IP_OFFSET)
>>   
>>   /* Purge coalesced packets timer interval */
>> @@ -1856,6 +1863,42 @@ static int32_t virtio_net_rsc_try_coalesce4(NetRscChain *chain,
>>                                       o_data, &o_ip->ip_len, MAX_IP4_PAYLOAD);
>>   }
>>   
>> +static int32_t virtio_net_rsc_try_coalesce6(NetRscChain *chain,
>> +                        NetRscSeg *seg, const uint8_t *buf, size_t size)
>> +{
>> +    uint16_t o_ip_len, n_ip_len;    /* len in ip header field */
>> +    uint16_t n_tcp_len, o_tcp_len;  /* tcp header len */
>> +    uint16_t o_data, n_data;        /* payload without virtio/eth/ip/tcp */
>> +    struct ip6_header *n_ip, *o_ip;
>> +    struct tcp_header *n_tcp, *o_tcp;
>> +
>> +    n_ip = (struct ip6_header *)(buf + IP_OFFSET);
>> +    n_ip_len = htons(n_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
>> +    n_tcp = (struct tcp_header *)(((uint8_t *)n_ip)\
>> +                                    + sizeof(struct ip6_header));
>> +    n_tcp_len = (htons(n_tcp->th_offset_flags) & 0xF000) >> 10;
>> +    n_data = n_ip_len - n_tcp_len;
>> +
>> +    o_ip = (struct ip6_header *)(seg->buf + IP_OFFSET);
>> +    o_ip_len = htons(o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
>> +    o_tcp = (struct tcp_header *)(((uint8_t *)o_ip)\
>> +                                    + sizeof(struct ip6_header));
>> +    o_tcp_len = (htons(o_tcp->th_offset_flags) & 0xF000) >> 10;
>> +    o_data = o_ip_len - o_tcp_len;
> Like I've replied in previous mails, need a helper or just store
> pointers to both ip and tcp in seg.
OK.
>
>> +
>> +    if (memcmp(&n_ip->ip6_src, &o_ip->ip6_src, sizeof(struct in6_address))
>> +        || memcmp(&n_ip->ip6_dst, &o_ip->ip6_dst, sizeof(struct in6_address))
>> +        || (n_tcp->th_sport ^ o_tcp->th_sport)
>> +        || (n_tcp->th_dport ^ o_tcp->th_dport)) {
>> +            return RSC_NO_MATCH;
>> +    }
> And if you still want to handle coalescing in a layer style, better
> delay the check of ports to tcp function.
OK.
>
>> +
>> +    /* There is a difference between payload lenght in ipv4 and v6,
>> +       ip header is excluded in ipv6 */
>> +    return virtio_net_rsc_coalesce_tcp(chain, seg, buf,
>> +                       n_tcp, n_tcp_len, n_data, o_tcp, o_tcp_len, o_data,
>> +                       &o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen, MAX_IP6_PAYLOAD);
>> +}
>>   
>>   /* Pakcets with 'SYN' should bypass, other flag should be sent after drain
>>    * to prevent out of order */
>> @@ -2015,6 +2058,59 @@ static size_t virtio_net_rsc_receive4(void *opq, NetClientState* nc,
>>                                      virtio_net_rsc_try_coalesce4);
>>   }
>>   
>> +static int32_t virtio_net_rsc_filter6(NetRscChain *chain, struct ip6_header *ip,
>> +                                      const uint8_t *buf, size_t size)
>> +{
>> +    uint16_t ip_len;
>> +
>> +    if (size < (TCP6_OFFSET + sizeof(tcp_header))) {
>> +        return RSC_BYPASS;
>> +    }
>> +
>> +    if (0x6 != (0xF & ip->ip6_ctlun.ip6_un1.ip6_un1_flow)) {
>> +        return RSC_BYPASS;
>> +    }
>> +
>> +    /* Both option and protocol is checked in this */
>> +    if (ip->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_TCP) {
>> +        return RSC_BYPASS;
>> +    }
>> +
>> +    /* Sanity check */
>> +    ip_len = htons(ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
>> +    if (ip_len < sizeof(struct tcp_header)
>> +        || ip_len > (size - TCP6_OFFSET)) {
>> +        return RSC_BYPASS;
>> +    }
>> +
>> +    return 0;
> RSC_WANT?
Yes, the is new code and not tested.
>
>> +}
>> +
>> +static size_t virtio_net_rsc_receive6(void *opq, NetClientState* nc,
>> +                                      const uint8_t *buf, size_t size)
>> +{
>> +    int32_t ret;
>> +    NetRscChain *chain;
>> +    struct ip6_header *ip;
>> +
>> +    chain = (NetRscChain *)opq;
>> +    ip = (struct ip6_header *)(buf + IP_OFFSET);
>> +    if (RSC_WANT != virtio_net_rsc_filter6(chain, ip, buf, size)) {
>> +        return virtio_net_do_receive(nc, buf, size);
>> +    }
>> +
>> +    ret = virtio_net_rsc_parse_tcp_ctrl((uint8_t *)ip, sizeof(*ip));
> Same as IPv4, looks like a layer violation.
OK.
>
>> +    if (RSC_BYPASS == ret) {
>> +        return virtio_net_do_receive(nc, buf, size);
>> +    } else if (RSC_FINAL == ret) {
>> +        return virtio_net_rsc_drain_one(chain, nc, buf, size, IP6_ADDR_OFFSET,
>> +                        IP6_ADDR_SIZE, TCP6_PORT_OFFSET, TCP_PORT_SIZE);
>> +    }
>> +
>> +    return virtio_net_rsc_callback(chain, nc, buf, size,
>> +                                   virtio_net_rsc_try_coalesce6);
>> +}
>> +
>>   static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>>                                                   uint16_t proto)
>>   {
>> @@ -2023,7 +2119,7 @@ static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>>       NICState *nic;
>>   
>>       /* Only handle IPv4/6 */
>> -    if (proto != (uint16_t)ETH_P_IP) {
>> +    if ((proto != (uint16_t)ETH_P_IP) && (proto != (uint16_t)ETH_P_IPV6)) {
>>           return NULL;
>>       }
>>   
>> @@ -2044,7 +2140,11 @@ static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
>>       chain->proto = proto;
>>       chain->drain_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
>>                                         virtio_net_rsc_purge, chain);
>> -    chain->do_receive = virtio_net_rsc_receive4;
>> +    if (ETH_P_IP == proto) {
>> +        chain->do_receive = virtio_net_rsc_receive4;
>> +    } else {
>> +        chain->do_receive = virtio_net_rsc_receive6;
>> +    }
>>   
>>       QTAILQ_INIT(&chain->buffers);
>>       QTAILQ_INSERT_TAIL(&n->rsc_chains, chain, next);
>
diff mbox

Patch

diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 9b44762..c9f6bfc 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -46,12 +46,19 @@ 
 #define TCP4_OFFSET (IP_OFFSET + sizeof(struct ip_header)) /* tcp4 header */
 #define TCP4_PORT_OFFSET TCP4_OFFSET        /* tcp4 port offset */
 #define IP4_ADDR_SIZE   8                   /* ipv4 saddr + daddr */
+
+#define IP6_ADDR_OFFSET (IP_OFFSET + 8)     /* ipv6 address start */
+#define TCP6_OFFSET (IP_OFFSET + sizeof(struct ip6_header)) /* tcp6 header */
+#define TCP6_PORT_OFFSET TCP6_OFFSET        /* tcp6 port offset */
+#define IP6_ADDR_SIZE   32                  /* ipv6 saddr + daddr */
 #define TCP_PORT_SIZE   4                   /* sport + dport */
 #define TCP_WINDOW      65535
 
 /* IPv4 max payload, 16 bits in the header */
 #define MAX_IP4_PAYLOAD  (65535 - sizeof(struct ip_header))
 
+/* ip6 max payload, payload in ipv6 don't include the  header */
+#define MAX_IP6_PAYLOAD  65535
 #define MAX_VIRTIO_IP_PAYLOAD  (65535 + IP_OFFSET)
 
 /* Purge coalesced packets timer interval */
@@ -1856,6 +1863,42 @@  static int32_t virtio_net_rsc_try_coalesce4(NetRscChain *chain,
                                     o_data, &o_ip->ip_len, MAX_IP4_PAYLOAD);
 }
 
+static int32_t virtio_net_rsc_try_coalesce6(NetRscChain *chain,
+                        NetRscSeg *seg, const uint8_t *buf, size_t size)
+{
+    uint16_t o_ip_len, n_ip_len;    /* len in ip header field */
+    uint16_t n_tcp_len, o_tcp_len;  /* tcp header len */
+    uint16_t o_data, n_data;        /* payload without virtio/eth/ip/tcp */
+    struct ip6_header *n_ip, *o_ip;
+    struct tcp_header *n_tcp, *o_tcp;
+
+    n_ip = (struct ip6_header *)(buf + IP_OFFSET);
+    n_ip_len = htons(n_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
+    n_tcp = (struct tcp_header *)(((uint8_t *)n_ip)\
+                                    + sizeof(struct ip6_header));
+    n_tcp_len = (htons(n_tcp->th_offset_flags) & 0xF000) >> 10;
+    n_data = n_ip_len - n_tcp_len;
+
+    o_ip = (struct ip6_header *)(seg->buf + IP_OFFSET);
+    o_ip_len = htons(o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
+    o_tcp = (struct tcp_header *)(((uint8_t *)o_ip)\
+                                    + sizeof(struct ip6_header));
+    o_tcp_len = (htons(o_tcp->th_offset_flags) & 0xF000) >> 10;
+    o_data = o_ip_len - o_tcp_len;
+
+    if (memcmp(&n_ip->ip6_src, &o_ip->ip6_src, sizeof(struct in6_address))
+        || memcmp(&n_ip->ip6_dst, &o_ip->ip6_dst, sizeof(struct in6_address))
+        || (n_tcp->th_sport ^ o_tcp->th_sport)
+        || (n_tcp->th_dport ^ o_tcp->th_dport)) {
+            return RSC_NO_MATCH;
+    }
+
+    /* There is a difference between payload lenght in ipv4 and v6,
+       ip header is excluded in ipv6 */
+    return virtio_net_rsc_coalesce_tcp(chain, seg, buf,
+                       n_tcp, n_tcp_len, n_data, o_tcp, o_tcp_len, o_data,
+                       &o_ip->ip6_ctlun.ip6_un1.ip6_un1_plen, MAX_IP6_PAYLOAD);
+}
 
 /* Pakcets with 'SYN' should bypass, other flag should be sent after drain
  * to prevent out of order */
@@ -2015,6 +2058,59 @@  static size_t virtio_net_rsc_receive4(void *opq, NetClientState* nc,
                                    virtio_net_rsc_try_coalesce4);
 }
 
+static int32_t virtio_net_rsc_filter6(NetRscChain *chain, struct ip6_header *ip,
+                                      const uint8_t *buf, size_t size)
+{
+    uint16_t ip_len;
+
+    if (size < (TCP6_OFFSET + sizeof(tcp_header))) {
+        return RSC_BYPASS;
+    }
+
+    if (0x6 != (0xF & ip->ip6_ctlun.ip6_un1.ip6_un1_flow)) {
+        return RSC_BYPASS;
+    }
+
+    /* Both option and protocol is checked in this */
+    if (ip->ip6_ctlun.ip6_un1.ip6_un1_nxt != IPPROTO_TCP) {
+        return RSC_BYPASS;
+    }
+
+    /* Sanity check */
+    ip_len = htons(ip->ip6_ctlun.ip6_un1.ip6_un1_plen);
+    if (ip_len < sizeof(struct tcp_header)
+        || ip_len > (size - TCP6_OFFSET)) {
+        return RSC_BYPASS;
+    }
+
+    return 0;
+}
+
+static size_t virtio_net_rsc_receive6(void *opq, NetClientState* nc,
+                                      const uint8_t *buf, size_t size)
+{
+    int32_t ret;
+    NetRscChain *chain;
+    struct ip6_header *ip;
+
+    chain = (NetRscChain *)opq;
+    ip = (struct ip6_header *)(buf + IP_OFFSET);
+    if (RSC_WANT != virtio_net_rsc_filter6(chain, ip, buf, size)) {
+        return virtio_net_do_receive(nc, buf, size);
+    }
+
+    ret = virtio_net_rsc_parse_tcp_ctrl((uint8_t *)ip, sizeof(*ip));
+    if (RSC_BYPASS == ret) {
+        return virtio_net_do_receive(nc, buf, size);
+    } else if (RSC_FINAL == ret) {
+        return virtio_net_rsc_drain_one(chain, nc, buf, size, IP6_ADDR_OFFSET,
+                        IP6_ADDR_SIZE, TCP6_PORT_OFFSET, TCP_PORT_SIZE);
+    }
+
+    return virtio_net_rsc_callback(chain, nc, buf, size,
+                                   virtio_net_rsc_try_coalesce6);
+}
+
 static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
                                                 uint16_t proto)
 {
@@ -2023,7 +2119,7 @@  static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
     NICState *nic;
 
     /* Only handle IPv4/6 */
-    if (proto != (uint16_t)ETH_P_IP) {
+    if ((proto != (uint16_t)ETH_P_IP) && (proto != (uint16_t)ETH_P_IPV6)) {
         return NULL;
     }
 
@@ -2044,7 +2140,11 @@  static NetRscChain *virtio_net_rsc_lookup_chain(NetClientState *nc,
     chain->proto = proto;
     chain->drain_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
                                       virtio_net_rsc_purge, chain);
-    chain->do_receive = virtio_net_rsc_receive4;
+    if (ETH_P_IP == proto) {
+        chain->do_receive = virtio_net_rsc_receive4;
+    } else {
+        chain->do_receive = virtio_net_rsc_receive6;
+    }
 
     QTAILQ_INIT(&chain->buffers);
     QTAILQ_INSERT_TAIL(&n->rsc_chains, chain, next);