|WARNING| examples/rpcapd: demo version of packet capture daemon
dpdklab at iol.unh.edu
dpdklab at iol.unh.edu
Tue Sep 8 23:23:33 CEST 2026
Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/169263
_AI Code Review_
=== Patch Review: series.patch (via Claude) ===
Review date: 2026-09-08
# DPDK rpcapd Example Application Review
## Summary
This patch adds a new example application implementing the rpcap protocol for remote packet capture. The code quality is generally good with proper DPDK idioms and comprehensive documentation. I found several correctness bugs related to resource management and error handling, plus some style issues.
---
## Errors (Must Fix)
### Resource Leak in `parse_opts` - strdup'd EAL Arguments
**File**: `examples/rpcapd/main.c`, function `dpdk_init()`
**Lines**: 854-888
The code allocates strings with `strdup()` and stores them in `eal_argv[]`, but never frees them after `rte_eal_init()` completes. On error paths within the allocation loop, already-allocated strings are also leaked.
**Why it matters**: Memory leak on every invocation. While the process exits soon after, clean resource management should be modeled in example code.
**Fix**:
```c
static int
dpdk_init(void)
{
/* ... existing declarations ... */
int ret;
/* ... existing allocation code ... */
ret = rte_eal_init(eal_argc, eal_argv);
/* Free all allocated strings */
for (unsigned int j = 0; j < i; j++)
free(eal_argv[j]);
free(eal_argv);
if (ret < 0)
rte_exit(EXIT_FAILURE, "EAL init failed: is the primary process running?\n");
/* ... rest of function ... */
}
```
Also handle the error path within the allocation loop:
```c
for (i = 0; i < RTE_DIM(args); i++) {
eal_argv[i] = strdup(args[i]);
if (eal_argv[i] == NULL) {
/* Free what we allocated so far */
for (unsigned int j = 0; j < i; j++)
free(eal_argv[j]);
free(eal_argv);
return -1;
}
}
```
---
### Resource Leak in `stop_capture` - Ring Drain Uses Wrong Free Function
**File**: `examples/rpcapd/main.c`, function `stop_capture()`
**Line**: 383
```c
while ((n = rte_ring_sc_dequeue_burst(s->ring, (void **)pkts,
BURST_SIZE, NULL)) > 0)
rte_pktmbuf_free_bulk(pkts, n);
```
The mbufs in the ring were captured from arbitrary queues and may originate from different mempools (applications can send mbufs from any pool). `rte_pktmbuf_free_bulk()` batches by pool internally and is safe for mixed-pool arrays. This is correct. (No issue - leaving here for clarity.)
---
### Error Path Leak in `handle_startcap` - Missing `close(data_listen)` on Early Return
**File**: `examples/rpcapd/main.c`, function `handle_startcap()`
**Lines**: 442-447
```c
if (rpcap_send_msg(fd, RPCAP_MSG_STARTCAP_REPLY, 0, &reply, sizeof(reply)) < 0) {
close(data_listen);
stop_capture(s);
return -1;
}
```
This path is correct - `data_listen` is closed. However, the next error path (lines 453-457) after `accept()` fails **does not close `data_listen`**:
```c
data_fd = accept(data_listen, NULL, NULL);
close(data_listen);
if (data_fd < 0) {
RTE_LOG(ERR, RPCAPD, "accept on data port: %s\n", strerror(errno));
stop_capture(s); /* BUG: data_listen already closed above, so this is fine */
return -1;
}
```
Actually, `data_listen` **is** closed on line 451 before the `accept()` check. Reviewing again: the code is correct. (No issue.)
---
### Integer Overflow in mempool mbuf_size Calculation
**File**: `examples/rpcapd/main.c`, function `create_capture_mempool()`
**Line**: 349
```c
uint32_t mbuf_size = RTE_PKTMBUF_HEADROOM + snaplen;
```
If `snaplen` is `UINT32_MAX - RTE_PKTMBUF_HEADROOM + 1` or larger, this addition overflows and wraps to a small value, causing `rte_pktmbuf_pool_create()` to allocate undersized mbufs that will be overrun when packets are copied.
**Why it matters**: Silent corruption or crash when capturing with a malicious or buggy client that requests a huge snaplen.
**Fix**:
```c
static struct rte_mempool *
create_capture_mempool(uint16_t port, uint32_t snaplen)
{
char name[RTE_MEMPOOL_NAMESIZE];
uint32_t mbuf_size;
/* Guard against overflow */
if (snaplen > UINT32_MAX - RTE_PKTMBUF_HEADROOM) {
RTE_LOG(ERR, RPCAPD, "snaplen too large\n");
return NULL;
}
mbuf_size = RTE_PKTMBUF_HEADROOM + snaplen;
snprintf(name, sizeof(name), "rpcapd_p_%u_%d", port, getpid());
return rte_pktmbuf_pool_create(name, ring_size * 2, MBUF_CACHE_SIZE, 0,
mbuf_size, rte_socket_id());
}
```
Then handle the NULL return in `handle_startcap()` (it already does at line 433).
---
### Use of `gettimeofday` in Fast Path Produces Stale Timestamps
**File**: `examples/rpcapd/main.c`, function `process_ring()`
**Line**: 500
```c
gettimeofday(&tv, NULL);
struct rpcap_pkthdr pkthdr = {
.timestamp_sec = htonl((uint32_t)tv.tv_sec),
.timestamp_usec = htonl((uint32_t)tv.tv_usec),
/* ... */
};
```
The timestamp is sampled once per burst (outside the loop over `i`) but applied to all packets in the burst. Packets captured at slightly different times get the same timestamp, which can confuse analysis tools that rely on sub-millisecond timing (tcpdump `-ttt`, Wireshark time delta column). This is especially misleading for high packet rates.
**Why it matters**: Timestamp accuracy is degraded; multiple packets incorrectly appear simultaneous.
**Fix**: Move `gettimeofday(&tv, NULL);` **inside** the `for` loop, right before filling `pkthdr`:
```c
for (i = 0; i < n; i++) {
struct rte_mbuf *m = pkts[i];
/* ... */
s->npkt++;
struct rpcap_header hdr = { /* ... */ };
gettimeofday(&tv, NULL); /* <-- moved here */
struct rpcap_pkthdr pkthdr = { /* ... */ };
/* ... */
}
```
---
## Warnings (Should Fix)
### Missing Release Notes for New Example
**File**: `doc/guides/rel_notes/release_26_11.rst`
**Lines**: 58-60
The release notes mention the new example, but the entry should be under the `New Features` section heading (which it is) and follow the required format. The current entry is adequate but could be more descriptive about the protocol and workflow.
Suggestion:
```rst
* **Added rpcapd example for remote packet capture.**
A new example application ``dpdk-rpcapd`` implements the rpcap protocol,
allowing libpcap-based tools (tcpdump, Wireshark) to capture packets live
from DPDK ports over TCP. The application runs as a secondary process and
integrates with the packet capture framework (pdump).
```
---
### Documentation Reference to Non-Existent extcap Doc
**File**: `doc/guides/sample_app_ug/rpcapd.rst`
**Line**: 56 and 197
```rst
see :doc:`extcap`.
```
```rst
* :doc:`extcap` -- Wireshark ``extcap`` plugin
```
These Sphinx `:doc:` references will cause a build warning or error if the `extcap.rst` document does not exist in the same directory. If the extcap documentation is planned for a later patch, note that in a comment or use plain text until the file exists.
**Fix**: Either create a placeholder `doc/guides/sample_app_ug/extcap.rst` (even if minimal), or rewrite the references as text:
```rst
For Wireshark users, the Wireshark ``extcap`` plugin interface
(see Wireshark documentation) is the preferred live-capture path.
```
---
### `listen_addr` Could Be Uninitialized on Early Error
**File**: `examples/rpcapd/main.c`, `parse_opts()` and `main()`
The `listen_addr` global is only initialized in `parse_bind_addr()`, which is called at the end of `parse_opts()`. If option parsing fails and calls `rte_exit()` before that point, or if `dpdk_init()` is called before options are parsed (it isn't, but still), `listen_addr` is uninitialized.
**Fix**: Initialize `listen_addr` to a safe default at declaration:
```c
static struct sockaddr_storage listen_addr = {
.ss_family = AF_INET,
};
static socklen_t listen_addrlen = sizeof(struct sockaddr_in);
```
Then in `parse_opts()`, if no `-b` is given, `parse_bind_addr("127.0.0.1", ...)` will overwrite with the resolved address.
---
### Missing `examples/rpcapd` Directory Creation Check in Meson
**File**: `examples/rpcapd/meson.build`
The meson file is minimal but correct. However, there is no explicit check that `examples/meson.build` actually includes this subdirectory when the conditions are met. The patch adds `'rpcapd',` to the `all_examples` list in `examples/meson.build`, so this is correct. (No issue.)
---
### Statistics Counters Could Overflow 32-Bit `rpcap_stats` Fields
**File**: `examples/rpcapd/main.c`, function `handle_stats()`
**Lines**: 546-555
```c
struct rpcap_stats reply = {
.ifrecv = htonl((uint32_t)es.ipackets),
.ifdrop = htonl((uint32_t)es.ierrors),
/* ... */
};
```
`es.ipackets` and `es.ierrors` are `uint64_t`. Casting to `uint32_t` silently truncates the upper 32 bits. For long-running captures or high-rate interfaces, the stats will wrap after ~4 billion packets.
**Why it matters**: Inaccurate statistics reporting to the client. The rpcap protocol defines these fields as 32-bit, so this is a protocol limitation, not a bug in the code. However, it's worth noting in the documentation.
**Suggested fix**: Add a comment explaining the truncation:
```c
/* rpcap protocol uses 32-bit counters; truncate DPDK's 64-bit stats */
struct rpcap_stats reply = {
.ifrecv = htonl((uint32_t)es.ipackets),
.ifdrop = htonl((uint32_t)es.ierrors),
/* ... */
};
```
And document the limitation in `rpcapd.rst` under **Limitations**.
---
## Info (Consider)
### `recv_full` Busy-Waits on `EINTR` Without Timeout
**File**: `examples/rpcapd/main.c`, function `recv_full()`
**Lines**: 166-179
The loop retries `recv()` indefinitely on `EINTR`. If a signal is delivered repeatedly (malicious or pathological), this could spin. A maximum retry count or poll timeout would make it more robust.
Suggestion: Add a poll/select with timeout before retrying after EINTR, or use `MSG_WAITALL` and handle partial reads explicitly.
---
### `capture_loop` Polls Control Socket Without Timeout
**File**: `examples/rpcapd/main.c`, function `capture_loop()`
**Line**: 535
```c
if (poll(&pfd, 1, 0) > 0 && (pfd.revents & POLLIN))
return 0;
```
The poll timeout is 0 (non-blocking), so the loop spins checking for control messages even when no data is on the ring. The code includes an `idle` counter and `usleep(1000)` after 1000 empty polls, which mitigates this. This is acceptable for an example but could be more efficient with a blocking poll or event-driven design.
No change required (noted as limitation in documentation at line 168).
---
### Magic Number `1000` in `capture_loop` Idle Check
**File**: `examples/rpcapd/main.c`, function `capture_loop()`
**Lines**: 542-543
```c
if (idle++ < 1000)
continue;
usleep(1000);
```
Defining `#define IDLE_POLL_THRESHOLD 1000` would improve readability.
---
### `debug_file` FILE Pointer Intentionally Leaked
**File**: `examples/rpcapd/main.c`, function `main()`
**Lines**: 967-976
Comment at line 969 says "intentionally never closed" which is correct (the kernel reclaims it). No issue - good documentation of intent.
---
### Inconsistent Logging: `NOTICE` vs `WARNING` vs `INFO`
**File**: `examples/rpcapd/main.c`, various functions
- Line 517: `RTE_LOG(NOTICE, ...)` for data connection closed
- Line 315: `RTE_LOG(WARNING, ...)` for sending error to client
- Line 695: `RTE_LOG(INFO, ...)` for client connected
The severity levels are reasonable but could be more consistent. `NOTICE` for data connection close is appropriate since it's a notable event but not an error. No change required.
---
## Style
### Boolean Comparison: `n == 0`, `plen > 0`, etc.
**File**: `examples/rpcapd/main.c`, multiple locations
Per AGENTS.md, explicit comparison against 0 is required for integers. The code already does this correctly throughout (e.g., `if (n < 0)`, `if (plen > 0)`). No issues.
---
### Hard Tabs Present and Correct
The code uses hard tabs for indentation. Verified correct.
---
### Include Order Correct
System headers, then DPDK headers, then local header. Correct at lines 18-50.
---
## Conclusion
This is a well-structured example with comprehensive documentation. The main concerns are:
1. **Memory leak of EAL argv strings** (Error)
2. **Integer overflow in mbuf_size calculation** (Error)
3. **Per-burst timestamps instead of per-packet** (Error - accuracy issue)
4. **Uninitialized `listen_addr` on early exit** (Warning)
5. **Missing extcap.rst documentation** (Warning)
Fix the three errors and the patch will be in good shape. The warnings are minor but should be addressed for production-quality code.
More information about the test-report
mailing list