|WARNING| [v4,4/4] app/rpcapd: add host list option

dpdklab at iol.unh.edu dpdklab at iol.unh.edu
Thu Oct 1 05:30:43 CEST 2026


Test-Label: ai-code-review
Test-Status: WARNING
https://dpdk.org/patch/170446

_AI Code Review_

=== Patch Review: series.patch (via Claude) ===
Review date: 2026-10-01

# DPDK Patch Review

## PATCH 1/4: pcapng: add API to read back capture mbuf header

### Errors

None identified.

### Warnings

None identified.

### Info

- **New experimental API properly tagged**: The new function `rte_pcapng_pkt_info()` is correctly marked with `__rte_experimental` and uses `RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_pcapng_pkt_info, 26.11)`.
- **Release notes updated**: The patch adds appropriate documentation in `doc/guides/rel_notes/release_26_11.rst`.
- **Test coverage added**: The patch includes comprehensive tests in `app/test/test_pcapng.c` covering nominal cases, truncation, VLAN tag restoration, and error handling.
- **API is well documented**: Good Doxygen comments explaining behavior, including the note about VLAN tag insertion potentially making the capture larger than the original.

---

## PATCH 2/4: app/rpcapd: remote pcap daemon

### Errors

1. **Non-const function pointer array** (`main.c:165`, `main.c:233`):
   ```c
   struct conn ctrl = { .fd = ctrl_fd };
   struct session s = { .data.fd = -1 };
   ```
   These are not function pointers, so this guideline does not apply. No error here.

2. **Resource leak on error path** (`capture.c:232`):
   ```c
   s->ring = create_capture_ring(s->port);
   s->mp = create_capture_mempool(s->port, s->snaplen);
   if (s->ring == NULL || s->mp == NULL) {
       RPCAPD_LOG(ERR, "ring/mempool alloc failed: %s",
           rte_strerror(rte_errno));
       stop_capture(s);
       return rpcap_send_error(c, 0, "DPDK alloc failed");
   }
   ```
   If only one of `s->ring` or `s->mp` is non-NULL, `stop_capture()` will free it correctly. However, there is a potential issue: if `create_capture_mempool()` succeeds but the subsequent `open_data_listener()` fails, the code calls `stop_capture()` which will clean up properly. This is actually correct. No error.

3. **Missing error check** (`capture.c:295`):
   ```c
   if (send_timeout > 0) {
       struct timeval tv = {
           .tv_sec = send_timeout,
       };
       if (setsockopt(data_fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
           RPCAPD_LOG(NOTICE, "cannot set data send timeout: %s",
                      strerror(errno));
   }
   ```
   The error is logged but the operation continues. This is acceptable for a non-critical socket option. No error.

4. **Missing bounds check** (`filter.c:74`):
   ```c
   for (i = 0; i < nitems; i++) {
       if (recv_full(c, &winsn, sizeof(winsn)) < 0) {
           free(insns);
           return -1;
       }
       insns[i].code = rte_be_to_cpu_16(winsn.code);
       insns[i].jt   = winsn.jt;
       insns[i].jf   = winsn.jf;
       insns[i].k    = rte_be_to_cpu_32(winsn.k);
   }
   ```
   The loop is bounded by `nitems`, which was already checked to be <= `MAX_FILTER_INSNS` and to fit within `plen`. The `insns` array was allocated with `calloc(nitems, ...)`. This is correctly bounded. No error.

5. **Potential use-after-free** (`capture.c:391`):
   ```c
   TEST_ASSERT(memcmp(data, rte_pktmbuf_mtod(&mbfs.mb[0], const void *),
                      rte_pktmbuf_data_len(&mbfs.mb[0])) == 0,
               "packet data is not at data_offset");
   ```
   This is in a test, not production code. The test constructs `mbfs` at the start of `test_pkt_info()` and the mbuf `mc` is not freed until later. No use-after-free. No error.

**Actually, re-reading the resource leak checks:**

In `handle_startcap()` in `capture.c`:
```c
s->ring = create_capture_ring(s->port);
s->mp = create_capture_mempool(s->port, s->snaplen);
if (s->ring == NULL || s->mp == NULL) {
    RPCAPD_LOG(ERR, "ring/mempool alloc failed: %s",
        rte_strerror(rte_errno));
    stop_capture(s);
    return rpcap_send_error(c, 0, "DPDK alloc failed");
}

data_listen = open_data_listener(&data_port);
if (data_listen < 0) {
    stop_capture(s);
    return rpcap_send_error(c, 0, "data port setup failed");
}
```
If `open_data_listener()` fails, `stop_capture()` is called. Looking at `stop_capture()`:
```c
void
stop_capture(struct session *s)
{
    if (s->ring != NULL) {
        while ((n = rte_ring_sc_dequeue_burst(...)) > 0)
            rte_pktmbuf_free_bulk(pkts, n);
        rte_ring_free(s->ring);
        s->ring = NULL;
    }
    if (s->mp != NULL) {
        rte_mempool_free(s->mp);
        s->mp = NULL;
    }
    // ...
}
```
This correctly frees both `ring` and `mp` if they are non-NULL. No leak.

However, there is a different pattern later:
```c
if (rte_eth_promiscuous_get(s->port) != 1) {
    if (rte_eth_promiscuous_enable(s->port) == 0)
        s->promisc_set = true;
    else
        RPCAPD_LOG(NOTICE, "cannot enable promiscuous mode on %s",
            s->name);
}
```
If promiscuous enable fails, the code logs a notice but continues. This is acceptable: the capture can still run, just not in promiscuous mode. No error.

Further down:
```c
if (rte_pdump_enable_bpf(s->port, RTE_PDUMP_ALL_QUEUES,
                         s->pdump_flags | RTE_PDUMP_FLAG_PCAPNG,
                         s->snaplen, s->ring, s->mp, s->prm) < 0) {
    RPCAPD_LOG(ERR, "rte_pdump_enable_bpf port %u failed: %s",
        s->port, rte_strerror(rte_errno));
    close(data_listen);
    stop_capture(s);
    return rpcap_send_error(c, 0, "cannot enable capture");
}
```
If `rte_pdump_enable_bpf()` fails, `data_listen` is closed and `stop_capture()` is called. Correct cleanup. No leak.

Then:
```c
if (rpcap_send_msg(c, RPCAP_MSG_STARTCAP_REPLY, 0, &reply, sizeof(reply)) < 0) {
    close(data_listen);
    stop_capture(s);
    return -1;
}
```
If the send fails, `data_listen` is closed and `stop_capture()` is called. Correct cleanup.

```c
data_fd = accept_from(data_listen, &s->peer, DATA_ACCEPT_TIMEOUT_MS);
close(data_listen);
if (data_fd < 0) {
    stop_capture(s);
    return -1;
}
```
If `accept_from()` fails, `data_listen` is already closed, `stop_capture()` is called. Correct cleanup. No leak.

All error paths in `handle_startcap()` correctly clean up resources. No errors identified.

### Warnings

1. **Missing release notes for internal helper** (`rpcapd.h`, `timestamp_init()`):
   The function `timestamp_init()` is internal to the application and does not require release notes. No warning.

2. **Static function pointer arrays** (`main.c:386`):
   ```c
   static const struct option long_options[] = {
       { "port",         required_argument, NULL, 'p' },
       // ...
   };
   ```
   This is not a function pointer array; it's an array of `struct option`. No warning.

3. **Using `malloc()` instead of `rte_zmalloc_socket()` for queue-related structures**:
   Searching for queue/ring/descriptor allocations:
   - `capture.c:114`: `rte_ring_create()` is used (correct).
   - `capture.c:126`: `rte_pktmbuf_pool_create()` is used (correct).
   - `filter.c:66`: `calloc(nitems, sizeof(*insns))` allocates a temporary array for BPF instructions, not a queue structure. This is process-private and does not need hugepage backing. No warning.
   - `session.c:24`: `realloc(buf, ...)` for building the interface list response. Process-private, not a queue. No warning.

4. **Non-unique global variable prefix**:
   Checking global variables in the installed application (not a library, so less strict):
   - `main.c`: `listen_port`, `ring_size`, `send_timeout`, `use_tls`, `null_auth_ok`, etc. These are all prefixed with descriptive names or are `static`. The non-static ones (`listen_addr`, `listen_addrlen`, `quit_signal`, `ring_size`, `send_timeout`, `use_tls`, `null_auth_ok`) are application-global and acceptable. However, they lack a consistent prefix like `rpcapd_`. This is a **Warning** for an application where static linking might occur in the future, but since this is an application binary (not a library), it's less critical.

   Since this is an application in `app/` and not a library or driver, the unique prefix guideline is less strictly enforced. However, for best practice, a warning could be issued. Given the context, I'll classify this as **Info** rather than Warning.

5. **RST documentation style** (`doc/guides/tools/rpcapd.rst`):
   The documentation uses bullet lists for command-line options. The guidelines suggest definition lists for term/description patterns. However, the existing style is consistent with other DPDK tool documentation (e.g., `dumpcap.rst`, `proc_info.rst`). This is acceptable. No warning.

### Info

- **Application structure**: The application is well-structured into separate modules (capture, filter, session, sock) with clear separation of concerns.
- **Error handling**: Comprehensive error handling with logging at appropriate levels.
- **Const correctness**: Many functions correctly use `const` for read-only parameters (e.g., `const struct conn *c`, `const struct session *s`).
- **Documentation**: The tool documentation is thorough, including usage examples and security warnings.

---

## PATCH 3/4: app/rpcapd: add TLS support

### Errors

1. **Missing error check** (`tls.c:89`):
   ```c
   if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0 ||
       setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0) {
       RPCAPD_LOG(NOTICE, "cannot set TLS handshake timeout: %s",
                  strerror(errno));
       return -1;
   }
   ```
   The error is logged and the function returns -1, which will cause the client connection to be rejected. This is correct handling. No error.

2. **Resource leak on error path** (`tls.c:105`):
   ```c
   SSL *ssl = SSL_new(tls_ctx);
   bool timed = set_handshake_timeout(c->fd, TLS_HANDSHAKE_TIMEOUT_SEC) == 0;

   if (ssl == NULL) {
       RPCAPD_LOG(ERR, "SSL_new: %s", tls_strerror());
       return -1;
   }

   if (SSL_set_fd(ssl, c->fd) != 1) {
       RPCAPD_LOG(ERR, "SSL_set_fd: %s", tls_strerror());
       SSL_free(ssl);
       return -1;
   }

   if (SSL_accept(ssl) != 1) {
       // ...
       SSL_free(ssl);
       return -1;
   }
   ```
   If `SSL_accept()` fails, `ssl` is freed. If `SSL_set_fd()` fails, `ssl` is freed. If `SSL_new()` fails, there is no `ssl` to free. All paths are correct. No leak.

3. **Plaintext password over unencrypted connection** (`session.c:213`):
   ```c
   if (c->ssl == NULL && !is_loopback(&s->peer)) {
       free_credential(user);
       free_credential(password);
       RPCAPD_LOG(NOTICE,
                  "refusing password authentication on an unencrypted connection");
       return rpcap_send_error(c, PCAP_ERR_AUTH_FAILED,
                               "this server will not accept a password "
                               "over an unencrypted connection; "
                               "use rpcaps://");
   }
   ```
   The code correctly refuses password authentication over an unencrypted connection unless the peer is loopback. Good security practice. No error.

4. **Credentials not zeroed before free** (`session.c:59`):
   ```c
   static void
   free_credential(char *cred)
   {
       if (cred != NULL) {
           explicit_bzero(cred, strlen(cred));
           free(cred);
       }
   }
   ```
   Credentials are explicitly zeroed using `explicit_bzero()` before being freed. This is correct. No error.

5. **`recv_credential()` potential buffer overflow** (`session.c:169`):
   ```c
   static int
   recv_credential(const struct conn *c, uint32_t len, uint32_t *plen, char **out)
   {
       char *buf;

       if (len > *plen || len > MAX_CREDENTIAL_LEN)
           return -1;

       buf = malloc(len + 1);
       if (buf == NULL)
           return -1;

       if (recv_full(c, buf, len) < 0) {
           explicit_bzero(buf, len);
           free(buf);
           return -1;
       }
       buf[len] = '\0';
       *plen -= len;
       *out = buf;
       return 0;
   }
   ```
   The code checks `len > MAX_CREDENTIAL_LEN` before allocating `len + 1` bytes and reading `len` bytes. The null terminator is placed at `buf[len]`. This is correct. No overflow.

### Warnings

1. **Static function pointer array** (`main.c:395`):
   ```c
   static const struct option long_options[] = {
       { "port",         required_argument, NULL, 'p' },
       { "bind",         required_argument, NULL, 'b' },
       { "null-auth",    no_argument,       NULL, 'n' },
   #ifdef RTE_HAS_OPENSSL
       { "tls",          no_argument,       NULL, 'S' },
       { "cert",         required_argument, NULL, 'X' },
       { "key",          required_argument, NULL, 


More information about the test-report mailing list