This change addresses two pdump teardown failure cases:
1. A requester secondary can time out if a forwarded secondary is slow,
unresponsive, or does not implement rte_pdump_init().
2. After a DISABLE request times out, the requester secondary receives
the reply and releases shared resources, which other slow or
unresponsive secondaries may access, leading to a crash.
Add an opt-in EAL multiprocess request mode that allows a caller to
ignore peers that do not implement the requested action, while preserving
the existing rte_mp_request_sync() behavior for legacy users.
pdump uses this new mode when forwarding enable/disable requests and adds
the following behavior:
- track request generation so stale callbacks cannot act on a newer
enable/disable cycle
- wait for in-flight callbacks to drain before completing disable
- prevent shared resource teardown from racing with delayed secondary
processing
- keep pdump intent-driven without extra consumer-specific workaround
logic
The EAL change is required because the current mp request path treats
a missing action as a failure, which cannot satisfy pdump's needs without
changing behavior for all existing users. The new API is opt-in, so
existing callers of rte_mp_request_sync() keep their current semantics.
Validation:
- Baseline-vs-changes benchmark used DPDK-native test-pmd as primary and
dumpcap as secondary.
- Both runs captured 1024 packets with the same packet-count exit
condition.
- A 5-second perf stat sample on the forwarding primary showed no
meaningful regression: with changes recorded 12,402,055,320 cycles and
17,391,769,084 instructions, versus 12,450,031,900 cycles and
17,499,706,053 instructions on the parent baseline.
- The extra synchronization in the hot path did not show material
overhead in this sample.
Fixes: c3ceb8742295 ("pdump: forward callback enable to secondary process")
Signed-off-by: Pushpendra Kumar <[email protected]>
Cc: [email protected]
---
This V2 patch addresses two pdump teardown failure scenarios:
- A requester secondary can time out if a forwarded secondary is slow,
unresponsive, or does not implement rte_pdump_init().
- A delayed DISABLE reply can race with requester-side teardown, which may
lead to crashes if peer processes still access shared pdump resources.
To fix this end-to-end:
- EAL adds an opt-in MP request mode to ignore peers with no matching action,
while preserving legacy rte_mp_request_sync() behavior.
- pdump uses that mode, tracks generation, and waits for in-flight callbacks
to drain before completing disable.
- dumpcap and pdump app cleanup paths are fail-closed when disable does not
complete successfully.
If this direction is accepted, I can split from the next revision into a
focused EAL patch followed by pdump and app-user updates.
.mailmap | 1 +
app/dumpcap/main.c | 23 ++-
app/pdump/main.c | 32 +++-
doc/guides/rel_notes/release_26_07.rst | 7 +
lib/eal/common/eal_common_proc.c | 37 +++-
lib/eal/include/rte_eal.h | 33 ++++
lib/eal/windows/eal_mp.c | 9 +
lib/pdump/rte_pdump.c | 241 ++++++++++++++++++++++---
lib/pdump/rte_pdump.h | 40 +++-
9 files changed, 379 insertions(+), 44 deletions(-)
diff --git a/.mailmap b/.mailmap
index c5bc728fae..c1313c7148 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1332,6 +1332,7 @@ Przemyslaw Gierszynski <[email protected]>
Przemyslaw Patynowski <[email protected]>
Przemyslaw Zegan <[email protected]>
Pu Xu <[email protected]>
+Pushpendra Kumar <[email protected]>
Qi Fu <[email protected]>
Qi Zhang <[email protected]>
Qian Hao <[email protected]>
diff --git a/app/dumpcap/main.c b/app/dumpcap/main.c
index 46a6cb251e..f421a17034 100644
--- a/app/dumpcap/main.c
+++ b/app/dumpcap/main.c
@@ -521,14 +521,33 @@ static void
cleanup_pdump_resources(void)
{
struct interface *intf;
+ int ret;
+ bool disable_failed = false;
TAILQ_FOREACH(intf, &interfaces, next) {
- rte_pdump_disable(intf->port,
- RTE_PDUMP_ALL_QUEUES, RTE_PDUMP_FLAG_RXTX);
+ ret = rte_pdump_disable(intf->port,
+ RTE_PDUMP_ALL_QUEUES,
RTE_PDUMP_FLAG_RXTX);
+ if (ret < 0) {
+ disable_failed = true;
+ fprintf(stderr,
+ "Disable pdump failed on %u:%s: %s\n",
+ intf->port, intf->name,
+ rte_strerror(rte_errno));
+ }
if (intf->opts.promisc_mode)
rte_eth_promiscuous_disable(intf->port);
}
+ if (disable_failed) {
+ /*
+ * Fail-closed: if disable did not complete, keep pdump
action/state
+ * alive and do not uninit shared capture resources.
+ */
+ fprintf(stderr,
+ "Skipping pdump uninit because disable did not complete
on all interfaces\n");
+ return;
+ }
+
rte_pdump_uninit();
}
diff --git a/app/pdump/main.c b/app/pdump/main.c
index 1e62c8adc1..4cfb038395 100644
--- a/app/pdump/main.c
+++ b/app/pdump/main.c
@@ -451,14 +451,17 @@ print_pdump_stats(void)
}
}
-static inline void
+static inline int
disable_pdump(struct pdump_tuples *pt)
{
if (pt->dump_by_type == DEVICE_ID)
- rte_pdump_disable_by_deviceid(pt->device_id, pt->queue,
- pt->dir);
+ return rte_pdump_disable_by_deviceid(pt->device_id, pt->queue,
+ pt->dir);
else if (pt->dump_by_type == PORT_ID)
- rte_pdump_disable(pt->port, pt->queue, pt->dir);
+ return rte_pdump_disable(pt->port, pt->queue, pt->dir);
+
+ rte_errno = EINVAL;
+ return -EINVAL;
}
static inline void
@@ -518,15 +521,30 @@ static void
cleanup_pdump_resources(void)
{
int i;
+ int ret;
+ bool disable_failed = false;
struct pdump_tuples *pt;
char name[RTE_ETH_NAME_MAX_LEN];
- /* disable pdump and free the pdump_tuple resources */
+ /* Disable all callbacks first; freeing shared objects before this is
unsafe. */
for (i = 0; i < num_tuples; i++) {
pt = &pdump_t[i];
- /* remove callbacks */
- disable_pdump(pt);
+ ret = disable_pdump(pt);
+ if (ret < 0) {
+ disable_failed = true;
+ printf("pdump disable failed (tuple=%d, errno=%d: %s); "
+ "skip teardown to avoid stale callback access\n",
+ i, rte_errno, rte_strerror(rte_errno));
+ }
+ }
+
+ if (disable_failed)
+ return;
+
+ /* free the pdump_tuple resources */
+ for (i = 0; i < num_tuples; i++) {
+ pt = &pdump_t[i];
/*
* transmit rest of the enqueued packets of the rings on to
diff --git a/doc/guides/rel_notes/release_26_07.rst
b/doc/guides/rel_notes/release_26_07.rst
index 4ca0a9ac77..1700a56bc7 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -177,6 +177,13 @@ New Features
Added AGENTS.md file for AI review
and supporting scripts to review patches and documentation.
+* **Hardened pdump teardown on disable failure.**
+
+ Hardened pdump request completion and application cleanup behavior so timeout
+ or disable failure does not trigger premature pdump resource teardown.
+ pdump now uses an opt-in MP request mode that ignores peers without the
+ pdump action, while preserving legacy EAL MP behavior for all other users.
+
Removed Items
-------------
diff --git a/lib/eal/common/eal_common_proc.c b/lib/eal/common/eal_common_proc.c
index 06f151818c..5a458c0417 100644
--- a/lib/eal/common/eal_common_proc.c
+++ b/lib/eal/common/eal_common_proc.c
@@ -56,6 +56,7 @@ static struct action_entry_list action_entry_list =
enum mp_type {
MP_MSG, /* Share message with peers, will not block */
MP_REQ, /* Request for information, Will block for a reply */
+ MP_REQ_IGN, /* Request where missing action should return ignore */
MP_REP, /* Response to previously-received request */
MP_IGN, /* Response telling requester to ignore this response */
};
@@ -384,11 +385,11 @@ process_msg(struct mp_msg_internal *m, struct sockaddr_un
*s)
pthread_mutex_unlock(&mp_mutex_action);
if (!action) {
- if (m->type == MP_REQ && !internal_conf->init_complete) {
- /* if this is a request, and init is not yet complete,
- * and callback wasn't registered, we should tell the
- * requester to ignore our existence because we're not
- * yet ready to process this request.
+ if ((m->type == MP_REQ && !internal_conf->init_complete) ||
+ m->type == MP_REQ_IGN) {
+ /*
+ * Ask requester to ignore this peer when action is not
+ * registered either due to early init or explicit
request policy.
*/
struct rte_mp_msg dummy;
@@ -936,7 +937,8 @@ mp_request_async(const char *dst, struct rte_mp_msg *req,
static int
mp_request_sync(const char *dst, struct rte_mp_msg *req,
- struct rte_mp_reply *reply, const struct timespec *ts)
+ struct rte_mp_reply *reply, const struct timespec *ts,
+ enum mp_type req_type)
{
int ret;
pthread_condattr_t attr;
@@ -959,7 +961,7 @@ mp_request_sync(const char *dst, struct rte_mp_msg *req,
return -1;
}
- ret = send_msg(dst, req, MP_REQ);
+ ret = send_msg(dst, req, req_type);
if (ret < 0) {
EAL_LOG(ERR, "Fail to send request %s:%s",
dst, req->name);
@@ -1010,11 +1012,20 @@ RTE_EXPORT_SYMBOL(rte_mp_request_sync)
int
rte_mp_request_sync(struct rte_mp_msg *req, struct rte_mp_reply *reply,
const struct timespec *ts)
+{
+ return rte_mp_request_sync_ex(req, reply, ts, 0);
+}
+
+RTE_EXPORT_SYMBOL(rte_mp_request_sync_ex)
+int
+rte_mp_request_sync_ex(struct rte_mp_msg *req, struct rte_mp_reply *reply,
+ const struct timespec *ts, uint32_t flags)
{
int dir_fd, ret = -1;
DIR *mp_dir;
struct dirent *ent;
struct timespec now, end;
+ enum mp_type req_type = MP_REQ;
const struct internal_config *internal_conf =
eal_get_internal_configuration();
@@ -1027,6 +1038,14 @@ rte_mp_request_sync(struct rte_mp_msg *req, struct
rte_mp_reply *reply,
if (check_input(req) != 0)
goto end;
+ if (flags & ~RTE_MP_REQ_F_IGNORE_NO_ACTION) {
+ rte_errno = EINVAL;
+ goto end;
+ }
+
+ if (flags & RTE_MP_REQ_F_IGNORE_NO_ACTION)
+ req_type = MP_REQ_IGN;
+
if (internal_conf->no_shconf) {
EAL_LOG(DEBUG, "No shared files mode enabled, IPC is disabled");
rte_errno = ENOTSUP;
@@ -1046,7 +1065,7 @@ rte_mp_request_sync(struct rte_mp_msg *req, struct
rte_mp_reply *reply,
/* for secondary process, send request to the primary process only */
if (rte_eal_process_type() == RTE_PROC_SECONDARY) {
pthread_mutex_lock(&pending_requests.lock);
- ret = mp_request_sync(eal_mp_socket_path(), req, reply, &end);
+ ret = mp_request_sync(eal_mp_socket_path(), req, reply, &end,
req_type);
pthread_mutex_unlock(&pending_requests.lock);
goto end;
}
@@ -1086,7 +1105,7 @@ rte_mp_request_sync(struct rte_mp_msg *req, struct
rte_mp_reply *reply,
/* unlocks the mutex while waiting for response,
* locks on receive
*/
- if (mp_request_sync(path, req, reply, &end))
+ if (mp_request_sync(path, req, reply, &end, req_type))
goto unlock_end;
}
ret = 0;
diff --git a/lib/eal/include/rte_eal.h b/lib/eal/include/rte_eal.h
index 7241f3be5d..415b974945 100644
--- a/lib/eal/include/rte_eal.h
+++ b/lib/eal/include/rte_eal.h
@@ -173,6 +173,15 @@ struct rte_mp_reply {
struct rte_mp_msg *msgs; /* caller to free */
};
+/** Request flags for rte_mp_request_sync_ex(). */
+enum rte_mp_request_flags {
+ /**
+ * Ask peers that do not have a registered action to return MP_IGN
+ * instead of causing timeout for this request.
+ */
+ RTE_MP_REQ_F_IGNORE_NO_ACTION = 1u << 0,
+};
+
/**
* Action function typedef used by other components.
*
@@ -292,6 +301,30 @@ int
rte_mp_request_sync(struct rte_mp_msg *req, struct rte_mp_reply *reply,
const struct timespec *ts);
+/**
+ * Send a request to peer processes with explicit request flags.
+ *
+ * This API is equivalent to rte_mp_request_sync() with opt-in behavior
+ * controls provided through @p flags.
+ *
+ * @param req
+ * The req argument contains the customized request message.
+ * @param reply
+ * The reply argument will be for storing all the replied messages;
+ * the caller is responsible for free reply->msgs.
+ * @param ts
+ * The ts argument specifies how long we can wait for the peer(s) to reply.
+ * @param flags
+ * Bitmask of values from enum rte_mp_request_flags.
+ *
+ * @return
+ * - On success, return 0.
+ * - On failure, return -1, and the reason will be stored in rte_errno.
+ */
+int
+rte_mp_request_sync_ex(struct rte_mp_msg *req, struct rte_mp_reply *reply,
+ const struct timespec *ts, uint32_t flags);
+
/**
* Send a request to the peer process and expect a reply in a separate
callback.
*
diff --git a/lib/eal/windows/eal_mp.c b/lib/eal/windows/eal_mp.c
index 6703355318..1066a4d2bc 100644
--- a/lib/eal/windows/eal_mp.c
+++ b/lib/eal/windows/eal_mp.c
@@ -56,10 +56,19 @@ RTE_EXPORT_SYMBOL(rte_mp_request_sync)
int
rte_mp_request_sync(struct rte_mp_msg *req, struct rte_mp_reply *reply,
const struct timespec *ts)
+{
+ return rte_mp_request_sync_ex(req, reply, ts, 0);
+}
+
+RTE_EXPORT_SYMBOL(rte_mp_request_sync_ex)
+int
+rte_mp_request_sync_ex(struct rte_mp_msg *req, struct rte_mp_reply *reply,
+ const struct timespec *ts, uint32_t flags)
{
RTE_SET_USED(req);
RTE_SET_USED(reply);
RTE_SET_USED(ts);
+ RTE_SET_USED(flags);
EAL_LOG_NOT_IMPLEMENTED();
return -1;
}
diff --git a/lib/pdump/rte_pdump.c b/lib/pdump/rte_pdump.c
index ac94efe7ff..3764aeb2d3 100644
--- a/lib/pdump/rte_pdump.c
+++ b/lib/pdump/rte_pdump.c
@@ -3,9 +3,12 @@
*/
#include <stdlib.h>
+#include <errno.h>
#include <eal_export.h>
+#include <rte_eal.h>
#include <rte_alarm.h>
+#include <rte_cycles.h>
#include <rte_mbuf.h>
#include <rte_ethdev.h>
#include <rte_lcore.h>
@@ -68,6 +71,7 @@ struct pdump_request {
const struct rte_bpf_prm *prm;
uint32_t snaplen;
+ uint32_t generation;
};
struct pdump_response {
@@ -81,6 +85,11 @@ struct pdump_bundle {
char peer[];
};
+struct pdump_shared_ctl {
+ RTE_ATOMIC(uint32_t) enabled;
+ RTE_ATOMIC(uint32_t) inflight;
+};
+
static struct pdump_rxtx_cbs {
struct rte_ring *ring;
struct rte_mempool *mp;
@@ -88,11 +97,11 @@ static struct pdump_rxtx_cbs {
const struct rte_bpf *filter;
enum pdump_version ver;
uint32_t snaplen;
+ uint32_t generation;
RTE_ATOMIC(uint32_t) use_count;
} rx_cbs[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT],
tx_cbs[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT];
-
/*
* The packet capture statistics keep track of packets
* accepted, filtered and dropped. These are per-queue
@@ -102,9 +111,59 @@ static const char MZ_RTE_PDUMP_STATS[] = "rte_pdump_stats";
static struct {
struct rte_pdump_stats rx[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT];
struct rte_pdump_stats tx[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT];
+ struct pdump_shared_ctl
rx_ctl[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT];
+ struct pdump_shared_ctl
tx_ctl[RTE_MAX_ETHPORTS][RTE_MAX_QUEUES_PER_PORT];
+ RTE_ATOMIC(uint32_t) generation;
const struct rte_memzone *mz;
} *pdump_stats;
+static int
+pdump_enter(struct pdump_shared_ctl *ctl, const struct pdump_rxtx_cbs *cbs)
+{
+ uint32_t generation = rte_atomic_load_explicit(&pdump_stats->generation,
+
rte_memory_order_relaxed);
+
+ if (rte_atomic_load_explicit(&ctl->enabled, rte_memory_order_relaxed)
== 0 ||
+ cbs->generation != generation)
+ return 0;
+
+ rte_atomic_fetch_add_explicit(&ctl->inflight, 1,
rte_memory_order_acquire);
+
+ /*
+ * Disable/re-enable may race with callback entry. Re-check enabled and
+ * generation after taking inflight reference so disable can wait for
this
+ * callback to drain and stale callbacks become no-op.
+ */
+ generation = rte_atomic_load_explicit(&pdump_stats->generation,
+ rte_memory_order_relaxed);
+ if (rte_atomic_load_explicit(&ctl->enabled, rte_memory_order_relaxed)
!= 0 &&
+ cbs->generation == generation)
+ return 1;
+
+ rte_atomic_fetch_sub_explicit(&ctl->inflight, 1,
rte_memory_order_release);
+ return 0;
+}
+
+static __rte_always_inline void
+pdump_exit(struct pdump_shared_ctl *ctl)
+{
+ rte_atomic_fetch_sub_explicit(&ctl->inflight, 1,
rte_memory_order_release);
+}
+
+static int
+pdump_wait_inflight(struct pdump_shared_ctl *ctl)
+{
+ uint64_t end_tsc = rte_get_timer_cycles() + MP_TIMEOUT_S *
rte_get_timer_hz();
+
+ while (rte_atomic_load_explicit(&ctl->inflight,
rte_memory_order_acquire) != 0) {
+ if (rte_get_timer_cycles() > end_tsc)
+ return -ETIMEDOUT;
+ rte_pause();
+ }
+
+ return 0;
+}
+
static void
pdump_cb_wait(struct pdump_rxtx_cbs *cbs)
{
@@ -224,12 +283,17 @@ pdump_rx(uint16_t port, uint16_t queue,
uint16_t max_pkts __rte_unused, void *user_params)
{
struct pdump_rxtx_cbs *cbs = user_params;
+ struct pdump_shared_ctl *ctl = &pdump_stats->rx_ctl[port][queue];
struct rte_pdump_stats *stats = &pdump_stats->rx[port][queue];
+ if (!pdump_enter(ctl, cbs))
+ return nb_pkts;
+
pdump_cb_hold(cbs);
pdump_copy(port, queue, RTE_PCAPNG_DIRECTION_IN,
pkts, nb_pkts, cbs, stats);
pdump_cb_release(cbs);
+ pdump_exit(ctl);
return nb_pkts;
}
@@ -239,12 +303,17 @@ pdump_tx(uint16_t port, uint16_t queue,
struct rte_mbuf **pkts, uint16_t nb_pkts, void *user_params)
{
struct pdump_rxtx_cbs *cbs = user_params;
+ struct pdump_shared_ctl *ctl = &pdump_stats->tx_ctl[port][queue];
struct rte_pdump_stats *stats = &pdump_stats->tx[port][queue];
+ if (!pdump_enter(ctl, cbs))
+ return nb_pkts;
+
pdump_cb_hold(cbs);
pdump_copy(port, queue, RTE_PCAPNG_DIRECTION_OUT,
pkts, nb_pkts, cbs, stats);
pdump_cb_release(cbs);
+ pdump_exit(ctl);
return nb_pkts;
}
@@ -255,20 +324,43 @@ pdump_register_rx_callbacks(enum pdump_version ver,
uint16_t end_q, uint16_t port, uint16_t queue,
struct rte_ring *ring, struct rte_mempool *mp,
struct rte_bpf *filter,
- uint16_t operation, uint32_t snaplen)
+ uint16_t operation, uint32_t snaplen,
+ uint32_t generation)
{
uint16_t qid;
qid = (queue == RTE_PDUMP_ALL_QUEUES) ? 0 : queue;
for (; qid < end_q; qid++) {
struct pdump_rxtx_cbs *cbs = &rx_cbs[port][qid];
+ struct pdump_shared_ctl *ctl = &pdump_stats->rx_ctl[port][qid];
if (operation == ENABLE) {
+ int ret;
+
if (cbs->cb) {
- PDUMP_LOG_LINE(ERR,
- "rx callback for port=%d queue=%d,
already exists",
- port, qid);
- return -EEXIST;
+ if (cbs->generation == generation &&
+ rte_atomic_load_explicit(&ctl->enabled,
+ rte_memory_order_relaxed) != 0) {
+ PDUMP_LOG_LINE(ERR,
+ "rx callback for port=%d
queue=%d, already exists",
+ port, qid);
+ return -EEXIST;
+ }
+
+ PDUMP_LOG_LINE(DEBUG,
+ "reconciling stale rx callback for
port=%d queue=%d"
+ " old_gen=%u new_gen=%u",
+ port, qid, cbs->generation, generation);
+
+ ret = rte_eth_remove_rx_callback(port, qid,
cbs->cb);
+ if (ret < 0) {
+ PDUMP_LOG_LINE(ERR,
+ "failed to reconcile stale rx
callback, errno=%d",
+ -ret);
+ return ret;
+ }
+ pdump_cb_wait(cbs);
+ cbs->cb = NULL;
}
cbs->use_count = 0;
cbs->ver = ver;
@@ -276,6 +368,7 @@ pdump_register_rx_callbacks(enum pdump_version ver,
cbs->mp = mp;
cbs->snaplen = snaplen;
cbs->filter = filter;
+ cbs->generation = generation;
cbs->cb = rte_eth_add_first_rx_callback(port, qid,
pdump_rx, cbs);
@@ -286,10 +379,15 @@ pdump_register_rx_callbacks(enum pdump_version ver,
return rte_errno;
}
+ rte_atomic_store_explicit(&ctl->inflight, 0,
rte_memory_order_relaxed);
+ rte_atomic_store_explicit(&ctl->enabled, 1,
rte_memory_order_release);
+
memset(&pdump_stats->rx[port][qid], 0, sizeof(struct
rte_pdump_stats));
} else if (operation == DISABLE) {
int ret;
+ rte_atomic_store_explicit(&ctl->enabled, 0,
rte_memory_order_release);
+
if (cbs->cb == NULL) {
PDUMP_LOG_LINE(ERR,
"no existing rx callback for port=%d
queue=%d",
@@ -298,6 +396,11 @@ pdump_register_rx_callbacks(enum pdump_version ver,
}
ret = rte_eth_remove_rx_callback(port, qid, cbs->cb);
if (ret < 0) {
+ /* Keep state coherent: callback is still
registered,
+ * so restore enabled.
+ */
+ rte_atomic_store_explicit(&ctl->enabled, 1,
+ rte_memory_order_release);
PDUMP_LOG_LINE(ERR,
"failed to remove rx callback,
errno=%d",
-ret);
@@ -305,6 +408,10 @@ pdump_register_rx_callbacks(enum pdump_version ver,
}
pdump_cb_wait(cbs);
cbs->cb = NULL;
+
+ ret = pdump_wait_inflight(ctl);
+ if (ret < 0)
+ return ret;
}
}
@@ -316,7 +423,8 @@ pdump_register_tx_callbacks(enum pdump_version ver,
uint16_t end_q, uint16_t port, uint16_t queue,
struct rte_ring *ring, struct rte_mempool *mp,
struct rte_bpf *filter,
- uint16_t operation, uint32_t snaplen)
+ uint16_t operation, uint32_t snaplen,
+ uint32_t generation)
{
uint16_t qid;
@@ -324,13 +432,36 @@ pdump_register_tx_callbacks(enum pdump_version ver,
qid = (queue == RTE_PDUMP_ALL_QUEUES) ? 0 : queue;
for (; qid < end_q; qid++) {
struct pdump_rxtx_cbs *cbs = &tx_cbs[port][qid];
+ struct pdump_shared_ctl *ctl = &pdump_stats->tx_ctl[port][qid];
if (operation == ENABLE) {
+ int ret;
+
if (cbs->cb) {
- PDUMP_LOG_LINE(ERR,
- "tx callback for port=%d queue=%d,
already exists",
- port, qid);
- return -EEXIST;
+ if (cbs->generation == generation &&
+ rte_atomic_load_explicit(&ctl->enabled,
+ rte_memory_order_relaxed) != 0) {
+ PDUMP_LOG_LINE(ERR,
+ "tx callback for port=%d
queue=%d, already exists",
+ port, qid);
+ return -EEXIST;
+ }
+
+ PDUMP_LOG_LINE(DEBUG,
+ "reconciling stale tx callback for
port=%d queue=%d"
+ " old_gen=%u new_gen=%u",
+ port, qid, cbs->generation, generation);
+
+ ret = rte_eth_remove_tx_callback(port, qid,
cbs->cb);
+ if (ret < 0) {
+ PDUMP_LOG_LINE(ERR,
+ "failed to reconcile stale tx
callback, errno=%d",
+ -ret);
+ return ret;
+ }
+
+ pdump_cb_wait(cbs);
+ cbs->cb = NULL;
}
cbs->use_count = 0;
cbs->ver = ver;
@@ -338,6 +469,7 @@ pdump_register_tx_callbacks(enum pdump_version ver,
cbs->mp = mp;
cbs->snaplen = snaplen;
cbs->filter = filter;
+ cbs->generation = generation;
cbs->cb = rte_eth_add_tx_callback(port, qid, pdump_tx,
cbs);
@@ -347,10 +479,15 @@ pdump_register_tx_callbacks(enum pdump_version ver,
rte_errno);
return rte_errno;
}
+
+ rte_atomic_store_explicit(&ctl->inflight, 0,
rte_memory_order_relaxed);
+ rte_atomic_store_explicit(&ctl->enabled, 1,
rte_memory_order_release);
memset(&pdump_stats->tx[port][qid], 0, sizeof(struct
rte_pdump_stats));
} else if (operation == DISABLE) {
int ret;
+ rte_atomic_store_explicit(&ctl->enabled, 0,
rte_memory_order_release);
+
if (cbs->cb == NULL) {
PDUMP_LOG_LINE(ERR,
"no existing tx callback for port=%d
queue=%d",
@@ -359,6 +496,11 @@ pdump_register_tx_callbacks(enum pdump_version ver,
}
ret = rte_eth_remove_tx_callback(port, qid, cbs->cb);
if (ret < 0) {
+ /* Keep state coherent: callback is still
registered,
+ * so restore enabled.
+ */
+ rte_atomic_store_explicit(&ctl->enabled, 1,
+ rte_memory_order_release);
PDUMP_LOG_LINE(ERR,
"failed to remove tx callback,
errno=%d",
-ret);
@@ -367,6 +509,10 @@ pdump_register_tx_callbacks(enum pdump_version ver,
pdump_cb_wait(cbs);
cbs->cb = NULL;
+
+ ret = pdump_wait_inflight(ctl);
+ if (ret < 0)
+ return ret;
}
}
@@ -459,7 +605,8 @@ set_pdump_rxtx_cbs(const struct pdump_request *p)
end_q = (queue == RTE_PDUMP_ALL_QUEUES) ? nb_rx_q : queue + 1;
ret = pdump_register_rx_callbacks(p->ver, end_q, port, queue,
ring, mp, filter,
- operation, p->snaplen);
+ operation, p->snaplen,
+ p->generation);
if (ret < 0)
return ret;
}
@@ -469,7 +616,8 @@ set_pdump_rxtx_cbs(const struct pdump_request *p)
end_q = (queue == RTE_PDUMP_ALL_QUEUES) ? nb_tx_q : queue + 1;
ret = pdump_register_tx_callbacks(p->ver, end_q, port, queue,
ring, mp, filter,
- operation, p->snaplen);
+ operation, p->snaplen,
+ p->generation);
if (ret < 0)
return ret;
}
@@ -477,12 +625,14 @@ set_pdump_rxtx_cbs(const struct pdump_request *p)
return ret;
}
-static void
-pdump_request_to_secondary(const struct pdump_request *req)
+static int
+pdump_request_to_secondary_sync(const struct pdump_request *req)
{
struct rte_mp_msg mp_req = { };
struct rte_mp_reply mp_reply;
struct timespec ts = {.tv_sec = MP_TIMEOUT_S, .tv_nsec = 0};
+ int ret = 0;
+ uint16_t i;
PDUMP_LOG_LINE(DEBUG, "forward req %s to secondary",
pdump_opname(req->op));
@@ -490,14 +640,41 @@ pdump_request_to_secondary(const struct pdump_request
*req)
strlcpy(mp_req.name, PDUMP_MP, sizeof(mp_req.name));
mp_req.len_param = sizeof(*req);
- if (rte_mp_request_sync(&mp_req, &mp_reply, &ts) != 0)
- PDUMP_LOG_LINE(ERR, "rte_mp_request_sync failed");
+ if (rte_mp_request_sync_ex(&mp_req, &mp_reply, &ts,
+ RTE_MP_REQ_F_IGNORE_NO_ACTION) != 0) {
+ PDUMP_LOG_LINE(ERR, "rte_mp_request_sync failed: %s",
+ strerror(rte_errno));
+ return -rte_errno;
+ }
- else if (mp_reply.nb_sent != mp_reply.nb_received)
+ if (mp_reply.nb_sent != mp_reply.nb_received) {
PDUMP_LOG_LINE(ERR, "not all secondary's replied (sent %u recv
%u)",
mp_reply.nb_sent, mp_reply.nb_received);
+ ret = -ETIMEDOUT;
+ }
+
+ for (i = 0; i < mp_reply.nb_received; i++) {
+ struct rte_mp_msg *mp_rep = &mp_reply.msgs[i];
+ const struct pdump_response *resp;
+
+ if (mp_rep->len_param != sizeof(*resp)) {
+ PDUMP_LOG_LINE(ERR, "invalid secondary reply size %u",
mp_rep->len_param);
+ if (ret == 0)
+ ret = -EINVAL;
+ continue;
+ }
+
+ resp = (const struct pdump_response *)mp_rep->param;
+ if (resp->err_value != 0) {
+ PDUMP_LOG_LINE(ERR, "secondary reply failed: op=%u
err=%d",
+ resp->res_op, resp->err_value);
+ if (ret == 0)
+ ret = resp->err_value;
+ }
+ }
free(mp_reply.msgs);
+ return ret;
}
/* Allocate temporary storage for passing state to the alarm thread for
deferred handling */
@@ -556,7 +733,10 @@ pdump_handle_primary_request(const struct rte_mp_msg
*mp_msg, const void *peer)
PDUMP_LOG_LINE(DEBUG, "secondary pdump %s",
pdump_opname(req->op));
/* Can just do it now, no need for interrupt thread */
- ret = set_pdump_rxtx_cbs(req);
+ if (req->op == ENABLE || req->op == DISABLE)
+ ret = set_pdump_rxtx_cbs(req);
+ else
+ ret = -EINVAL;
}
return pdump_send_response(req, ret, peer);
@@ -569,17 +749,29 @@ __pdump_request(void *param)
{
struct pdump_bundle *bundle = param;
struct rte_mp_msg *msg = &bundle->msg;
- const struct pdump_request *req =
- (const struct pdump_request *)msg->param;
+ struct pdump_request *req =
+ (struct pdump_request *)msg->param;
int ret;
PDUMP_LOG_LINE(DEBUG, "primary pdump %s", pdump_opname(req->op));
+ if (req->op == DISABLE) {
+ req->generation =
rte_atomic_fetch_add_explicit(&pdump_stats->generation, 1,
+
rte_memory_order_acq_rel) + 1;
+ } else if (req->op == ENABLE) {
+ req->generation =
rte_atomic_load_explicit(&pdump_stats->generation,
+
rte_memory_order_acquire);
+ }
+
ret = set_pdump_rxtx_cbs(req);
- /* Primary process is responsible for broadcasting request to all
secondaries */
+ /*
+ * Primary process is responsible for broadcasting the request to all
+ * secondaries. The sync request uses opt-in ignore-missing-action mode
so
+ * pdump does not depend on unrelated secondary processes.
+ */
if (ret == 0)
- pdump_request_to_secondary(req);
+ ret = pdump_request_to_secondary_sync(req);
pdump_send_response(req, ret, bundle->peer);
free(bundle);
@@ -659,6 +851,9 @@ rte_pdump_init(void)
pdump_stats = mz->addr;
pdump_stats->mz = mz;
+ if (rte_eal_process_type() == RTE_PROC_PRIMARY)
+ rte_atomic_store_explicit(&pdump_stats->generation, 1,
+ rte_memory_order_release);
return 0;
}
diff --git a/lib/pdump/rte_pdump.h b/lib/pdump/rte_pdump.h
index 1e32d46097..77e5914420 100644
--- a/lib/pdump/rte_pdump.h
+++ b/lib/pdump/rte_pdump.h
@@ -64,7 +64,7 @@ rte_pdump_uninit(void);
* queues of a given port.
* @param flags
* flags specifies RTE_PDUMP_FLAG_RX/RTE_PDUMP_FLAG_TX/RTE_PDUMP_FLAG_RXTX
- * on which packet capturing should be enabled for a given port and queue.
+ * on which packet capturing should be disabled for a given port and queue.
* @param ring
* ring on which captured packets will be enqueued for user.
* @param mp
@@ -72,6 +72,11 @@ rte_pdump_uninit(void);
* @param filter
* Unused should be NULL.
*
+ * @note
+ * In applications that enable capture on multiple interfaces, enable may be
+ * partially applied before an error is returned. Callers should explicitly
+ * unwind partial enable state.
+ *
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
*/
@@ -103,6 +108,11 @@ rte_pdump_enable(uint16_t port, uint16_t queue, uint32_t
flags,
* @param prm
* Use BPF program to run to filter packes (can be NULL)
*
+ * @note
+ * In applications that enable capture on multiple interfaces, enable may be
+ * partially applied before an error is returned. Callers should explicitly
+ * unwind partial enable state.
+ *
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
*/
@@ -124,7 +134,14 @@ rte_pdump_enable_bpf(uint16_t port_id, uint16_t queue,
* queues of a given port.
* @param flags
* flags specifies RTE_PDUMP_FLAG_RX/RTE_PDUMP_FLAG_TX/RTE_PDUMP_FLAG_RXTX
- * on which packet capturing should be enabled for a given port and queue.
+ * on which packet capturing should be disabled for a given port and queue.
+ *
+ * @note
+ * A disable failure (including timeout/no response) means teardown is not
+ * complete across pdump-enabled peers. The caller must not release shared
+ * pdump resources and must not uninitialize pdump for that capture session
+ * until disable succeeds. Releasing resources on disable failure can lead to
+ * crashes in peer processes still accessing those resources.
*
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
@@ -153,6 +170,11 @@ rte_pdump_disable(uint16_t port, uint16_t queue, uint32_t
flags);
* @param filter
* unused should be NULL
*
+ * @note
+ * In applications that enable capture on multiple interfaces, enable may be
+ * partially applied before an error is returned. Callers should explicitly
+ * unwind partial enable state.
+ *
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
*/
@@ -186,6 +208,11 @@ rte_pdump_enable_by_deviceid(char *device_id, uint16_t
queue,
* @param filter
* Use BPF program to run to filter packes (can be NULL)
*
+ * @note
+ * In applications that enable capture on multiple interfaces, enable may be
+ * partially applied before an error is returned. Callers should explicitly
+ * unwind partial enable state.
+ *
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
*/
@@ -210,7 +237,14 @@ rte_pdump_enable_bpf_by_deviceid(const char *device_id,
uint16_t queue,
* queues of a given device id.
* @param flags
* flags specifies RTE_PDUMP_FLAG_RX/RTE_PDUMP_FLAG_TX/RTE_PDUMP_FLAG_RXTX
- * on which packet capturing should be enabled for a given port and queue.
+ * on which packet capturing should be disabled for a given port and queue.
+ *
+ * @note
+ * A disable failure (including timeout/no response) means teardown is not
+ * complete across pdump-enabled peers. The caller must not release shared
+ * pdump resources and must not uninitialize pdump for that capture session
+ * until disable succeeds. Releasing resources on disable failure can lead to
+ * crashes in peer processes still accessing those resources.
*
* @return
* 0 on success, -1 on error, rte_errno is set accordingly.
--
2.43.0