During switchover replay, packets injected through AF_PACKET can come
back from the bridge with a 10-byte virtio_net_hdr even though QEMU
expects a 12-byte merged-rxbuf header. The missing two bytes shift the
Ethernet frame and corrupt the packet seen by the guest.

Detect this case by comparing the EtherType at the expected position
with the value two bytes earlier. When only the shifted position
contains a recognized protocol, reduce the effective host header length
by two for this packet.

Only apply the heuristic while vhost is running, and carry the adjusted
header length through the normal receive path without copying the
buffer.

Signed-off-by: Cindy Lu <[email protected]>
---
 hw/net/virtio-net.c | 54 ++++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 49 insertions(+), 5 deletions(-)

diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
index 616590fb82..29dbe3d8d5 100644
--- a/hw/net/virtio-net.c
+++ b/hw/net/virtio-net.c
@@ -144,6 +144,36 @@ static int vq2q(int queue_index)
     return queue_index / 2;
 }
 
+static bool virtio_net_rx_known_ethertype(uint16_t proto)
+{
+    if (proto <= 1500) {
+        /* IEEE 802.3 length field */
+        return true;
+    }
+
+    switch (proto) {
+    case ETH_P_IP:
+    case ETH_P_IPV6:
+    case ETH_P_ARP:
+    case ETH_P_VLAN:
+    case ETH_P_DVLAN:
+    case 0x0842: /* Wake-on-LAN */
+    case 0x22f0: /* IEEE 802.1Qbe / TSN */
+    case 0x8809: /* Slow protocols / LACP */
+    case 0x8863: /* PPPoE discovery */
+    case 0x8864: /* PPPoE session */
+    case 0x8906: /* FCoE */
+    case 0x8914: /* FCoE Init */
+    case 0x88cc: /* LLDP */
+    case 0x88e1: /* HomePlug AV */
+    case 0x88f7: /* PTP */
+    case 0x8915: /* RoCE */
+        return true;
+    default:
+        return false;
+    }
+}
+
 static void flush_or_purge_queued_packets(NetClientState *nc)
 {
     if (!nc->peer) {
@@ -1780,7 +1810,8 @@ static void receive_header(VirtIONet *n, const struct 
iovec *iov, int iov_cnt,
     }
 }
 
-static int receive_filter(VirtIONet *n, const uint8_t *buf, int size)
+static int receive_filter(VirtIONet *n, const uint8_t *buf, int size,
+                          size_t host_hdr_len)
 {
     static const uint8_t bcast[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
     static const uint8_t vlan[] = {0x81, 0x00};
@@ -1790,7 +1821,7 @@ static int receive_filter(VirtIONet *n, const uint8_t 
*buf, int size)
     if (n->promisc)
         return 1;
 
-    ptr += n->host_hdr_len;
+    ptr += host_hdr_len;
 
     if (!memcmp(&ptr[12], vlan, sizeof(vlan))) {
         int vid = lduw_be_p(ptr + 14) & 0xfff;
@@ -1955,12 +1986,25 @@ static ssize_t virtio_net_receive_rcu(NetClientState 
*nc, const uint8_t *buf,
     QEMU_UNINITIALIZED size_t lens[VIRTQUEUE_MAX_SIZE];
     QEMU_UNINITIALIZED struct iovec mhdr_sg[VIRTQUEUE_MAX_SIZE];
     struct virtio_net_hdr_v1_hash extra_hdr;
+    size_t host_hdr_len = n->host_hdr_len;
     unsigned mhdr_cnt = 0;
     size_t offset, i, guest_offset, j;
     ssize_t err;
 
     memset(&extra_hdr, 0, sizeof(extra_hdr));
 
+    if (n->vhost_started &&
+        host_hdr_len >= 12 &&
+        size >= host_hdr_len + ETH_HLEN) {
+        uint16_t et_at_host = lduw_be_p(buf + host_hdr_len + 12);
+        uint16_t et_at_m2  = lduw_be_p(buf + host_hdr_len + 10);
+
+        if (!virtio_net_rx_known_ethertype(et_at_host) &&
+            virtio_net_rx_known_ethertype(et_at_m2)) {
+            host_hdr_len -= 2;
+        }
+    }
+
     if (n->rss_data.enabled && n->rss_data.enabled_software_rss) {
         int index = virtio_net_process_rss(nc, buf, size, &extra_hdr);
         if (index >= 0) {
@@ -1975,11 +2019,11 @@ static ssize_t virtio_net_receive_rcu(NetClientState 
*nc, const uint8_t *buf,
     q = virtio_net_get_subqueue(nc);
 
     /* hdr_len refers to the header we supply to the guest */
-    if (!virtio_net_has_buffers(q, size + n->guest_hdr_len - n->host_hdr_len)) 
{
+    if (!virtio_net_has_buffers(q, size + n->guest_hdr_len - host_hdr_len)) {
         return 0;
     }
 
-    if (!receive_filter(n, buf, size))
+    if (!receive_filter(n, buf, size, host_hdr_len))
         return size;
 
     offset = i = 0;
@@ -2041,7 +2085,7 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, 
const uint8_t *buf,
                              sizeof(extra_hdr.hash_value) +
                              sizeof(extra_hdr.hash_report));
             }
-            offset = n->host_hdr_len;
+            offset = host_hdr_len;
             total += n->guest_hdr_len;
             guest_offset = n->guest_hdr_len;
         } else {
-- 
2.52.0


Reply via email to