]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
netmap: packet stall
authorBill Meeks <bmeeks8@bellsouth.net>
Thu, 23 Feb 2023 16:12:56 +0000 (11:12 -0500)
committerVictor Julien <vjulien@oisf.net>
Tue, 28 Mar 2023 09:32:22 +0000 (11:32 +0200)
- Fix packet processing stall under high load when using netmap in IPS mode.
- Detect and generate Fatal Error exit for rare case when hardware NIC exposes
unmatched RX/TX queue counts. This is rare, but would result in some traffic
bypassing Suricata since it assumes NIC queue counts are symmetrical.
- Fix instance of missing unlock call for netmap device list when exiting due
to an error condition.
- Clean up existing code comments and add additional ones to better document
the new netmap v14 API code.

src/runmode-netmap.c
src/source-netmap.c

index 85227e85ccff54bd39e9a088bcc4d97bbac8d0ce..6111ac5dab62b3abb82b7b1d2986335d2b051e04 100644 (file)
 #include "util-ioctl.h"
 #include "util-byte.h"
 
-#if HAVE_NETMAP && USE_NEW_NETMAP_API
+#if HAVE_NETMAP
 #define NETMAP_WITH_LIBS
+#ifdef DEBUG
+#define DEBUG_NETMAP_USER
+#endif
 #include <net/netmap_user.h>
 #endif /* HAVE_NETMAP */
 
@@ -105,6 +108,7 @@ static void NetmapDerefConfig(void *conf)
 static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
         ConfNode *if_root, ConfNode *if_default)
 {
+    char base_name[IFNAMSIZ];
     ns->threads = 0;
     ns->promisc = true;
     ns->checksum_mode = CHECKSUM_VALIDATION_AUTO;
@@ -124,17 +128,12 @@ static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
         }
     }
 
-#ifdef USE_NEW_NETMAP_API
-    /* we will need the base interface name for later */
-    char base_name[IFNAMSIZ];
+    /* we need the base interface name for later */
     strlcpy(base_name, ns->iface, sizeof(base_name));
     if (strlen(base_name) > 0 &&
             (base_name[strlen(base_name) - 1] == '^' || base_name[strlen(base_name) - 1] == '*')) {
         base_name[strlen(base_name) - 1] = '\0';
     }
-#else
-    char *base_name = ns->iface;
-#endif
 
     /* prefixed with netmap or vale means it's not a real interface
      * and we don't check offloading. */
@@ -158,7 +157,8 @@ static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
                 iface);
         goto finalize;
 
-    /* If there is no setting for current interface use default one as main iface */
+        /* If there is no setting for current interface, use default
+         * one as main iface */
     } else if (if_root == NULL) {
         if_root = if_default;
         if_default = NULL;
@@ -230,7 +230,6 @@ static int ParseNetmapSettings(NetmapIfaceSettings *ns, const char *iface,
     }
 
 finalize:
-
     ns->ips = (ns->copy_mode != NETMAP_COPY_MODE_NONE);
 
 #if !USE_NEW_NETMAP_API
@@ -242,9 +241,8 @@ finalize:
         if (ns->threads_auto) {
             /* As NetmapGetRSSCount used to be broken on Linux,
              * fall back to GetIfaceRSSQueuesNum if needed. */
-            ns->threads = NetmapGetRSSCount(ns->iface);
+            ns->threads = NetmapGetRSSCount(base_name);
             if (ns->threads == 0) {
-                /* need to use base_name of interface here */
                 ns->threads = GetIfaceRSSQueuesNum(base_name);
             }
         }
@@ -310,10 +308,10 @@ static void *ParseNetmapConfig(const char *iface_name)
             if_root = ConfFindDeviceConfig(netmap_node, out_iface);
             ParseNetmapSettings(&aconf->out, out_iface, if_root, if_default);
 #if !USE_NEW_NETMAP_API
-            /* if one side of the IPS peering uses a sw_ring, we will default
-             * to using a single ring/thread on the other side as well. Only
-             * if thread variable is set to 'auto'. So the user can override
-             * this. */
+            /* If one side of the IPS peering uses a sw_ring, we will default
+             * to using a single ring/thread on the other side as well, but
+             * only if the thread variable is set to 'auto'. So, the user can
+             * override this behavior if desired. */
             if (aconf->out.sw_ring && aconf->in.threads_auto) {
                 aconf->out.threads = aconf->in.threads = 1;
             } else if (aconf->in.sw_ring && aconf->out.threads_auto) {
@@ -342,15 +340,17 @@ static void *ParseNetmapConfig(const char *iface_name)
     }
 
 #endif
-    /* netmap needs all offloading to be disabled */
-    if (aconf->in.real) {
-        char base_name[sizeof(aconf->in.iface)];
-        strlcpy(base_name, aconf->in.iface, sizeof(base_name));
-        /* for a sw_ring enabled device name, strip the trailing char */
-        if (aconf->in.sw_ring) {
-            base_name[strlen(base_name) - 1] = '\0';
-        }
+    /* we need the base interface name with any trailing software
+     * ring marker stripped for HW offloading checks */
+    char base_name[sizeof(aconf->in.iface)];
+    strlcpy(base_name, aconf->in.iface, sizeof(base_name));
+    /* for a sw_ring device name, strip the trailing magic char */
+    if (aconf->in.sw_ring && strlen(base_name) > 0) {
+        base_name[strlen(base_name) - 1] = '\0';
+    }
 
+    /* netmap needs all hardware offloading to be disabled */
+    if (aconf->in.real) {
         if (LiveGetOffload() == 0) {
             (void)GetIfaceOffloading(base_name, 1, 1);
         } else {
index 2409e384778a31c9683ba24406a0947befc44d21..0b7ca85ebeeff7bcd3a9d1903cf3d89dc82f77b0 100644 (file)
@@ -131,6 +131,7 @@ typedef struct NetmapDevice_
 {
 #if USE_NEW_NETMAP_API
     struct nmport_d *nmd;
+    struct nm_pkthdr pkthdr;
 #else
     struct nm_desc *nmd;
 #endif
@@ -182,7 +183,57 @@ typedef TAILQ_HEAD(NetmapDeviceList_, NetmapDevice_) NetmapDeviceList;
 static NetmapDeviceList netmap_devlist = TAILQ_HEAD_INITIALIZER(netmap_devlist);
 static SCMutex netmap_devlist_lock = SCMUTEX_INITIALIZER;
 
+static void NetmapDestroyDevice(NetmapDevice *pdev)
+{
+#if USE_NEW_NETMAP_API
+    nmport_close(pdev->nmd);
+#else
+    nm_close(pdev->nmd);
+#endif
+    SCMutexDestroy(&pdev->netmap_dev_lock);
+    SCFree(pdev);
+}
+
+/**
+ * \brief Close or dereference netmap device instance.
+ * \param dev Netmap device instance.
+ * \return Zero on success.
+ */
+static int NetmapClose(NetmapDevice *dev)
+{
+    NetmapDevice *pdev, *tmp;
+
+    SCMutexLock(&netmap_devlist_lock);
+
+    TAILQ_FOREACH_SAFE (pdev, &netmap_devlist, next, tmp) {
+        if (pdev == dev) {
+            pdev->ref--;
+            if (!pdev->ref) {
+                NetmapDestroyDevice(pdev);
+            }
+            SCMutexUnlock(&netmap_devlist_lock);
+            return 0;
+        }
+    }
+
+    SCMutexUnlock(&netmap_devlist_lock);
+    return -1;
+}
+
+/**
+ * \brief Close all open netmap device instances.
+ */
+static void NetmapCloseAll(void)
+{
+    NetmapDevice *pdev, *tmp;
+
+    TAILQ_FOREACH_SAFE (pdev, &netmap_devlist, next, tmp) {
+        NetmapDestroyDevice(pdev);
+    }
+}
+
 /** \brief get RSS RX-queue count
+ *  \ifname Pointer to base interface name (without any host stack suffix)
  *  \retval rx_rings RSS RX queue count or 0 on error
  */
 int NetmapGetRSSCount(const char *ifname)
@@ -194,16 +245,16 @@ int NetmapGetRSSCount(const char *ifname)
     struct nmreq nm_req;
 #endif
     int rx_rings = 0;
-
-#if USE_NEW_NETMAP_API
-    /* we need the base interface name to query queues */
     char base_name[IFNAMSIZ];
+
+    /* we need the base interface name for querying queue count,
+     * so strip any trailing suffix indicating a software ring */
     strlcpy(base_name, ifname, sizeof(base_name));
     if (strlen(base_name) > 0 &&
             (base_name[strlen(base_name) - 1] == '^' || base_name[strlen(base_name) - 1] == '*')) {
         base_name[strlen(base_name) - 1] = '\0';
     }
-#endif
+
     SCMutexLock(&netmap_devlist_lock);
 
     /* open netmap device */
@@ -215,7 +266,7 @@ int NetmapGetRSSCount(const char *ifname)
         goto error_open;
     }
 
-    /* query netmap interface info */
+    /* query netmap interface for ring count */
 #if USE_NEW_NETMAP_API
     memset(&req, 0, sizeof(req));
     memset(&hdr, 0, sizeof(hdr));
@@ -225,7 +276,7 @@ int NetmapGetRSSCount(const char *ifname)
     strlcpy(hdr.nr_name, base_name, sizeof(hdr.nr_name));
 #else
     memset(&nm_req, 0, sizeof(nm_req));
-    strlcpy(nm_req.nr_name, ifname, sizeof(nm_req.nr_name));
+    strlcpy(nm_req.nr_name, base_name, sizeof(nm_req.nr_name));
     nm_req.nr_version = NETMAP_API;
 #endif
 
@@ -234,18 +285,30 @@ int NetmapGetRSSCount(const char *ifname)
 #else
     if (ioctl(fd, NIOCGINFO, &nm_req) != 0) {
 #endif
-        SCLogError(SC_ERR_NETMAP_CREATE, "Couldn't query netmap for info about %s, error %s",
-                ifname, strerror(errno));
+        SCLogError(SC_ERR_NETMAP_CREATE, "Query of netmap HW rings count on %s failed, error %s",
+                base_name, strerror(errno));
         goto error_fd;
     };
 
+        /* check for asymmetrical TX/RX queue counts on interface
+         * and error out if true as that feature is incompatible
+         * with the way Suricata utilizes netmap */
 #if USE_NEW_NETMAP_API
-    /* return RX rings count if it equals TX rings count */
-    if (req.nr_rx_rings == req.nr_tx_rings)
-        rx_rings = req.nr_rx_rings;
+    rx_rings = req.nr_rx_rings;
+    int tx_rings = req.nr_tx_rings;
 #else
     rx_rings = nm_req.nr_rx_rings;
+    int tx_rings = nm_req.nr_tx_rings;
 #endif
+    if (rx_rings != tx_rings) {
+        close(fd);
+        SCMutexUnlock(&netmap_devlist_lock);
+        NetmapCloseAll();
+        FatalError(SC_ERR_FATAL,
+                "HW device %s has an unequal number of RX and TX rings and "
+                "is incompatible with netmap mode in Suricata!",
+                base_name);
+    }
 
 error_fd:
     close(fd);
@@ -254,60 +317,10 @@ error_open:
     return rx_rings;
 }
 
-static void NetmapDestroyDevice(NetmapDevice *pdev)
-{
-#if USE_NEW_NETMAP_API
-    nmport_close(pdev->nmd);
-#else
-    nm_close(pdev->nmd);
-#endif
-    SCMutexDestroy(&pdev->netmap_dev_lock);
-    SCFree(pdev);
-}
-
-/**
- * \brief Close or dereference netmap device instance.
- * \param dev Netmap device instance.
- * \return Zero on success.
- */
-static int NetmapClose(NetmapDevice *dev)
-{
-    NetmapDevice *pdev, *tmp;
-
-    SCMutexLock(&netmap_devlist_lock);
-
-    TAILQ_FOREACH_SAFE (pdev, &netmap_devlist, next, tmp) {
-        if (pdev == dev) {
-            pdev->ref--;
-            if (!pdev->ref) {
-                NetmapDestroyDevice(pdev);
-            }
-            SCMutexUnlock(&netmap_devlist_lock);
-            return 0;
-        }
-    }
-
-    SCMutexUnlock(&netmap_devlist_lock);
-    return -1;
-}
-
-/**
- * \brief Close all open netmap device instances.
- */
-static void NetmapCloseAll(void)
-{
-    NetmapDevice *pdev, *tmp;
-
-    TAILQ_FOREACH_SAFE (pdev, &netmap_devlist, next, tmp) {
-        NetmapDestroyDevice(pdev);
-    }
-}
-
 /**
  * \brief Open interface in netmap mode.
- * \param ifname Interface name.
- * \param promisc Enable promiscuous mode.
- * \param dev Pointer to requested netmap device instance.
+ * \param ns Pointer to Netmap interface settings structure.
+ * \param pdevice Pointer to requested netmap device instance pointer.
  * \param verbose Verbose error logging.
  * \param read Indicates direction: RX or TX
  * \param zerocopy 1 if zerocopy access requested
@@ -399,23 +412,23 @@ static int NetmapOpen(NetmapIfaceSettings *ns, NetmapDevice **pdevice, int verbo
      * The following logic within the "retry" loop builds endpoint names.
      *
      * IPS Mode:
-     * There are two endpoints: one hardware NIC and either a hardware NIC or host stack "NIC".
+     * Has two endpoints: one hardware NIC and either another hardware NIC or the host stack.
      *
      * IDS Mode:
      * One endpoint -- usually a hardware NIC.
      *
-     * IPS mode -- with one endpoint a host stack "NIC":
+     * IPS mode -- with one endpoint the host stack:
      * When using multiple rings/threads, then the open of the initial Ring 0 MUST
      * instruct netmap to open multiple Host Stack rings (as the default is to open only a single
-     * pair). This is also critical for the HW NIC endpoint. This is done by adding
-     * “@conf:host-rings=x” suffix option (where “x” is the number of host rings desired)
-     * to BOTH endpoint nmport_open_desc() calls for ring 0 (hardware and host stack).
+     * pair). This is also critical for the HW NIC endpoint. This is done by adding the
+     * “@conf:host-rings=x” suffix option (where “x” is the number of TX and RX host rings desired)
+     * to BOTH endpoint nmport_open calls for ring 0 (for hardware and host stack).
      * For subsequent additional ring open calls, omit the suffix option specifying host ring count.
      *
      * IPS mode -- both endpoints are hardware NICs:
      * Do NOT pass any suffix option (even for Ring 0). You do not need to tell netmap how many
      * rings, because it already knows the correct value from the NIC driver itself. Specifying a
-     * desired ring count when both ends are Hardware NICs confuses netmap, and it seems to default
+     * desired ring count when both ends are Hardware NICs seems to confuse netmap, and it defaults
      * to using only a single hardware ring. In this scenario, specify only the specific ring number
      * being opened.
      */
@@ -462,8 +475,7 @@ retry:
             SCLogDebug("device with SW-ring enabled (devname): %s", devname);
         } else if (ring == 0 && soft) {
             /* Ring 0 of HW endpoint, and other endpoint is SW stack,
-             * so request SW host stack rings to match HW rings count.
-             */
+             * so request SW host stack rings to match HW rings count */
             snprintf(devname, sizeof(devname), "netmap:%s-%d%s%s@conf:host-rings=%d", ns->iface,
                     ring, strlen(optstr) ? "/" : "", optstr, ns->threads);
             SCLogDebug("device with HW-ring enabled (devname): %s", devname);
@@ -479,28 +491,15 @@ retry:
 
     strlcpy(pdev->ifname, ns->iface, sizeof(pdev->ifname));
 
+    /* attempt to open the port with netmap */
 #if USE_NEW_NETMAP_API
-    /* have the netmap API parse device name and prepare the port descriptor for us */
-    pdev->nmd = nmport_prepare(devname);
-
-    if (pdev->nmd != NULL) {
-        /* For RX devices, set the nr_mode flag we need on the netmap port TX rings prior to opening
-         */
-        if (read) {
-            pdev->nmd->reg.nr_flags |= NR_NO_TX_POLL;
-        }
-
-        /* Now attempt to actually open the netmap port descriptor */
-        if (nmport_open_desc(pdev->nmd) < 0) {
-            /* the open failed, so clean-up the descriptor and fall through to error handler */
-            nmport_close(pdev->nmd);
-            pdev->nmd = NULL;
-        }
-    }
+    pdev->nmd = nmport_open(devname);
 #else
     pdev->nmd = nm_open(devname, NULL, 0, NULL);
 #endif
 
+    /* if failed to open the port on first try, make
+     * some parameter adjustments and try once more */
     if (pdev->nmd == NULL) {
         if (errno == EINVAL) {
             if (opt_z[0] == 'z') {
@@ -514,6 +513,10 @@ retry:
             }
         }
 
+        /* if we get here, attempted port open failed, so
+         * close any previously opened ports and exit with
+         * a Fatal Error */
+        SCMutexUnlock(&netmap_devlist_lock);
         NetmapCloseAll();
         FatalError(SC_ERR_FATAL, "opening devname %s failed: %s", devname, strerror(errno));
     }
@@ -532,9 +535,9 @@ retry:
     pdev->ring = ring;
     SCMutexInit(&pdev->netmap_dev_lock, NULL);
     TAILQ_INSERT_TAIL(&netmap_devlist, pdev, next);
+    *pdevice = pdev;
 
     SCMutexUnlock(&netmap_devlist_lock);
-    *pdevice = pdev;
 
     return 0;
 error:
@@ -597,8 +600,11 @@ static TmEcode ReceiveNetmapThreadInit(ThreadVars *tv, const void *initdata, voi
         ntv->flags |= NETMAP_FLAG_EXCL_RING_ACCESS;
     }
 
-    /* Need to insure open of ring 0 conveys requested ring count for open */
+    /* flag if one endpoint is a host stack ring to insure the open
+     * of ring 0 conveys the requested host stack ring count */
     bool soft = aconf->in.sw_ring || aconf->out.sw_ring;
+
+    /* open the source netmap port */
     if (NetmapOpen(&aconf->in, &ntv->ifsrc, 1, 1, (ntv->flags & NETMAP_FLAG_ZERO_COPY) != 0,
                 soft) != 0) {
         goto error_ntv;
@@ -614,6 +620,7 @@ static TmEcode ReceiveNetmapThreadInit(ThreadVars *tv, const void *initdata, voi
     }
 #endif
 
+    /* open the destination netmap port if not using IDS-only mode */
     if (aconf->in.copy_mode != NETMAP_COPY_MODE_NONE) {
         if (NetmapOpen(&aconf->out, &ntv->ifdst, 1, 0, (ntv->flags & NETMAP_FLAG_ZERO_COPY) != 0,
                     soft) != 0) {
@@ -652,6 +659,8 @@ static TmEcode ReceiveNetmapThreadInit(ThreadVars *tv, const void *initdata, voi
     *data = (void *)ntv;
     aconf->DerefFunc(aconf);
     SCReturnInt(TM_ECODE_OK);
+
+    /* error handling code below */
 error_dst:
     if (aconf->in.copy_mode != NETMAP_COPY_MODE_NONE) {
         NetmapClose(ntv->ifdst);
@@ -669,11 +678,13 @@ error:
 
 /**
  * \brief Output packet to destination interface or drop.
- * \param ntv Thread local variables.
- * \param p Source packet.
+ * \param ntv Pointer to Thread local variables.
+ * \param p Pointer to Packet structure data.
  */
 static TmEcode NetmapWritePacket(NetmapThreadVars *ntv, Packet *p)
 {
+    int write_tries = 0;
+
     if (ntv->copy_mode == NETMAP_COPY_MODE_IPS) {
         if (PACKET_TEST_ACTION(p, ACTION_DROP)) {
             return TM_ECODE_OK;
@@ -681,20 +692,36 @@ static TmEcode NetmapWritePacket(NetmapThreadVars *ntv, Packet *p)
     }
     DEBUG_VALIDATE_BUG_ON(ntv->ifdst == NULL);
 
-    /* Lock the destination netmap ring while writing to it */
+    /* Lock the destination netmap ring while writing to it if required */
     if (ntv->flags & NETMAP_FLAG_EXCL_RING_ACCESS) {
         SCMutexLock(&ntv->ifdst->netmap_dev_lock);
     }
 
-    /* attempt to write the packet into the netmap ring buffer(s) */
+    /* Attempt to write packet's data into the netmap TX ring buffer(s).
+     * A return value of zero from the port inject call indicates the
+     * write failed, but this may be because the ring buffers are full
+     * awaiting processing by the kernel, so we make two more attempts */
+try_write:
 #if USE_NEW_NETMAP_API
     if (nmport_inject(ntv->ifdst->nmd, GET_PKT_DATA(p), GET_PKT_LEN(p)) == 0) {
 #else
     if (nm_inject(ntv->ifdst->nmd, GET_PKT_DATA(p), GET_PKT_LEN(p)) == 0) {
 #endif
+        /* writing the packet failed, but ask kernel to sync TX rings
+         * for us as the ring buffers may simply be full */
+        (void)ioctl(ntv->ifdst->nmd->fd, NIOCTXSYNC, 0);
+
+        /* Try write up to 2 more times before giving up */
+        if (write_tries < 3) {
+            write_tries++;
+            goto try_write;
+        }
+
+        /* if we get here, all write attempts failed, so bail out */
         if (ntv->flags & NETMAP_FLAG_EXCL_RING_ACCESS) {
             SCMutexUnlock(&ntv->ifdst->netmap_dev_lock);
         }
+
         SCLogDebug("failed to send %s -> %s", ntv->ifsrc->ifname, ntv->ifdst->ifname);
         ntv->drops++;
         return TM_ECODE_FAILED;
@@ -703,7 +730,10 @@ static TmEcode NetmapWritePacket(NetmapThreadVars *ntv, Packet *p)
     SCLogDebug("sent successfully: %s(%d)->%s(%d) (%u)", ntv->ifsrc->ifname, ntv->ifsrc->ring,
             ntv->ifdst->ifname, ntv->ifdst->ring, GET_PKT_LEN(p));
 
-    ioctl(ntv->ifdst->nmd->fd, NIOCTXSYNC, 0);
+    /* packet data write succeeded, so ask kernel to sync the TX ring */
+    (void)ioctl(ntv->ifdst->nmd->fd, NIOCTXSYNC, 0);
+
+    /* unlock the netmap device if we needed to lock it */
     if (ntv->flags & NETMAP_FLAG_EXCL_RING_ACCESS) {
         SCMutexUnlock(&ntv->ifdst->netmap_dev_lock);
     }
@@ -712,7 +742,7 @@ static TmEcode NetmapWritePacket(NetmapThreadVars *ntv, Packet *p)
 
 /**
  * \brief Packet release routine.
- * \param p Packet.
+ * \param p Pointer to Packet struct.
  */
 static void NetmapReleasePacket(Packet *p)
 {
@@ -726,14 +756,14 @@ static void NetmapReleasePacket(Packet *p)
 }
 
 #if USE_NEW_NETMAP_API
-static void NetmapProcessPacket(NetmapThreadVars *ntv, const struct nm_pkthdr *ph)
+static void NetmapDispatchPacket(NetmapThreadVars *ntv)
 #else
 static void NetmapCallback(u_char *user, const struct nm_pkthdr *ph, const u_char *d)
 #endif
 {
-
 #if USE_NEW_NETMAP_API
-    const u_char *d = ph->buf;
+    const u_char *d = ntv->ifsrc->pkthdr.buf;
+    const struct nm_pkthdr *ph = &(ntv->ifsrc->pkthdr);
 #else
     NetmapThreadVars *ntv = (NetmapThreadVars *)user;
 #endif
@@ -779,23 +809,24 @@ static void NetmapCallback(u_char *user, const struct nm_pkthdr *ph, const u_cha
 
 /**
  * \brief Copy netmap rings data into Packet structures.
- * \param *d nmport_d (or nm_desc) netmap if structure.
+ * \param *d nmport_d Pointer to netmap port descriptor structure.
  * \param cnt int count of packets to read (-1 = all).
- * \param *ntv NetmapThreadVars.
+ * \param *ntv Pointer to NetmapThreadVars structure.
  */
 #if USE_NEW_NETMAP_API
 static TmEcode NetmapReadPackets(struct nmport_d *d, int cnt, NetmapThreadVars *ntv)
 {
-    struct nm_pkthdr hdr;
     int last_ring = d->last_rx_ring - d->first_rx_ring + 1;
     int cur_ring, got = 0, cur_rx_ring = d->cur_rx_ring;
 
-    memset(&hdr, 0, sizeof(hdr));
-    hdr.flags = NM_MORE_PKTS;
+    memset(&ntv->ifsrc->pkthdr, 0, sizeof(ntv->ifsrc->pkthdr));
+    ntv->ifsrc->pkthdr.flags = NM_MORE_PKTS;
 
     if (cnt == 0)
         cnt = -1;
 
+    /* iterate the available rings and their slots to pull out
+     * the data for processing by the Decode and Detect modules */
     for (cur_ring = 0; cur_ring < last_ring && cnt != got; cur_ring++, cur_rx_ring++) {
         struct netmap_ring *ring;
 
@@ -804,53 +835,54 @@ static TmEcode NetmapReadPackets(struct nmport_d *d, int cnt, NetmapThreadVars *
 
         ring = NETMAP_RXRING(d->nifp, cur_rx_ring);
 
-        /* cycle through the non-empty ring slots to fetch their data */
+        /* cycle through the non-empty ring slots to fetch all the data */
         for (; !nm_ring_empty(ring) && cnt != got; got++) {
             u_int idx, i;
             u_char *oldbuf;
             struct netmap_slot *slot;
 
-            if (hdr.buf) { /* from previous round */
-                NetmapProcessPacket(ntv, &hdr);
+            if (ntv->ifsrc->pkthdr.buf) { /* from previous round */
+                NetmapDispatchPacket(ntv);
             }
 
             i = ring->cur;
             slot = &ring->slot[i];
             idx = slot->buf_idx;
             d->cur_rx_ring = cur_rx_ring;
-            hdr.slot = slot;
-            oldbuf = hdr.buf = (u_char *)NETMAP_BUF(ring, idx);
-            hdr.len = hdr.caplen = slot->len;
-
-            /* loop through the ring slots to get packet data */
+            ntv->ifsrc->pkthdr.slot = slot;
+            oldbuf = ntv->ifsrc->pkthdr.buf = (u_char *)NETMAP_BUF(ring, idx);
+            ntv->ifsrc->pkthdr.len = ntv->ifsrc->pkthdr.caplen = slot->len;
+
+            /* Check the ring slots for more packet data.
+             * A packet can be fragmented across multiple
+             * slots, so check and loop until we find the
+             * slot with the NS_MOREFRAG flag cleared,
+             * signaling the end of the packet's data. */
             while (slot->flags & NS_MOREFRAG) {
-                /* packet can be fragmented across multiple slots, */
-                /* so loop until we find the slot with the flag    */
-                /* cleared, signalling the end of the packet data. */
                 u_char *nbuf;
                 u_int oldlen = slot->len;
                 i = nm_ring_next(ring, i);
                 slot = &ring->slot[i];
-                hdr.len += slot->len;
+                ntv->ifsrc->pkthdr.len += slot->len;
                 nbuf = (u_char *)NETMAP_BUF(ring, slot->buf_idx);
 
                 if (oldbuf != NULL && nbuf - oldbuf == ring->nr_buf_size &&
                         oldlen == ring->nr_buf_size) {
-                    hdr.caplen += slot->len;
+                    ntv->ifsrc->pkthdr.caplen += slot->len;
                     oldbuf = nbuf;
                 } else {
                     oldbuf = NULL;
                 }
             }
 
-            hdr.ts = ring->ts;
+            ntv->ifsrc->pkthdr.ts = ring->ts;
             ring->head = ring->cur = nm_ring_next(ring, i);
         }
     }
 
-    if (hdr.buf) { /* from previous round */
-        hdr.flags = 0;
-        NetmapProcessPacket(ntv, &hdr);
+    if (ntv->ifsrc->pkthdr.buf) { /* from previous round */
+        ntv->ifsrc->pkthdr.flags = 0;
+        NetmapDispatchPacket(ntv);
     }
     return got;
 }
@@ -871,13 +903,18 @@ static TmEcode ReceiveNetmapLoop(ThreadVars *tv, void *data, void *slot)
     fds.fd = ntv->ifsrc->nmd->fd;
     fds.events = POLLIN;
 
-    SCLogDebug("thread %s polling on %d", tv->name, fds.fd);
+    SCLogDebug(
+            "thread %s - RX polling using fd %d for ring %d", tv->name, fds.fd, ntv->ifsrc->ring);
+
+    /* loop waiting for packets to arrive on the netmap source RX ring */
     for(;;) {
+
+        /* exit the read poll() loop if Suricata is shutting down */
         if (unlikely(suricata_ctl_flags != 0)) {
             break;
         }
 
-        /* make sure we have at least one packet in the packet pool,
+        /* make sure we have at least one packet in the packet pool
          * to prevent us from alloc'ing packets at line rate */
         PacketPoolWait();
 
@@ -892,8 +929,6 @@ static TmEcode ReceiveNetmapLoop(ThreadVars *tv, void *data, void *slot)
 
         } else if (r == 0) {
             /* no events, timeout */
-            // SCLogDebug("(%s:%d-%d) Poll timeout", ntv->ifsrc->ifname,
-            //           ntv->src_ring_from, ntv->src_ring_to);
 
             /* sync counters */
             NetmapDumpCounters(ntv);
@@ -909,9 +944,6 @@ static TmEcode ReceiveNetmapLoop(ThreadVars *tv, void *data, void *slot)
                 SCLogError(SC_ERR_NETMAP_READ,
                         "Error reading netmap data via polling from iface '%s': (%d" PRIu32 ") %s",
                         ntv->ifsrc->ifname, errno, strerror(errno));
-                // SCLogError(SC_ERR_NETMAP_READ,
-                //        "Error reading data from iface '%s': (%d" PRIu32 ") %s",
-                //        ntv->ifsrc->ifname, errno, strerror(errno));
             } else if (fds.revents & POLLNVAL) {
                 SCLogError(SC_ERR_NETMAP_READ, "Invalid polling request");
             }