Add rte_telemetry_register_cmd_fd_arg() to register a command whose
callback also receives file descriptors passed by the client as
SCM_RIGHTS ancillary data. The callback owns the descriptors and must
close them.

This lets a client open a file itself and hand the descriptor to the
primary process, so DPDK never opens the path. That avoids path and
permission problems and works across container filesystem namespaces.

Existing commands and clients are unaffected. If unsolicited file
descriptor is passed, it is closed.

Signed-off-by: Stephen Hemminger <[email protected]>
---
 doc/guides/rel_notes/release_26_07.rst |   5 ++
 lib/telemetry/rte_telemetry.h          |  66 ++++++++++++++
 lib/telemetry/telemetry.c              | 115 ++++++++++++++++++++++---
 3 files changed, 174 insertions(+), 12 deletions(-)

diff --git a/doc/guides/rel_notes/release_26_07.rst 
b/doc/guides/rel_notes/release_26_07.rst
index b5285af5fe..d7a2df88c1 100644
--- a/doc/guides/rel_notes/release_26_07.rst
+++ b/doc/guides/rel_notes/release_26_07.rst
@@ -141,6 +141,11 @@ New Features
   Added AGENTS.md file for AI review
   and supporting scripts to review patches and documentation.
 
+* **Added telemetry support for passing file descriptors.**
+
+  Add experimental telemetry callback ``rte_telemetry_register_cmd_fd_arg()``
+  to allow command to receive file descriptors passed by client.
+
 
 Removed Items
 -------------
diff --git a/lib/telemetry/rte_telemetry.h b/lib/telemetry/rte_telemetry.h
index 0a58e518f7..3e32d2902b 100644
--- a/lib/telemetry/rte_telemetry.h
+++ b/lib/telemetry/rte_telemetry.h
@@ -325,6 +325,37 @@ typedef int (*telemetry_cb)(const char *cmd, const char 
*params,
 typedef int (*telemetry_arg_cb)(const char *cmd, const char *params, void *arg,
                struct rte_tel_data *info);
 
+/**
+ * This telemetry callback is used when registering a telemetry command with
+ * rte_telemetry_register_cmd_fd_arg().
+ *
+ * It behaves like telemetry_arg_cb, but additionally receives any file
+ * descriptors the client passed alongside the command as SCM_RIGHTS ancillary
+ * data. The callback takes ownership of these descriptors and is responsible
+ * for closing them.
+ *
+ * @param cmd
+ *   The cmd that was requested by the client.
+ * @param params
+ *   Contains data required by the callback function.
+ * @param arg
+ *   The opaque value that was passed to rte_telemetry_register_cmd_fd_arg().
+ * @param fds
+ *   Array of file descriptors received from the client. May be NULL when
+ *   n_fds is zero.
+ * @param n_fds
+ *   Number of file descriptors in the fds array.
+ * @param info
+ *   The information to be returned to the caller.
+ *
+ * @return
+ *   Length of buffer used on success.
+ * @return
+ *   Negative integer on error.
+ */
+typedef int (*telemetry_fd_cb)(const char *cmd, const char *params, void *arg,
+               const int *fds, unsigned int n_fds, struct rte_tel_data *info);
+
 /**
  * Used when registering a command and callback function with telemetry.
  *
@@ -368,6 +399,41 @@ __rte_experimental
 int
 rte_telemetry_register_cmd_arg(const char *cmd, telemetry_arg_cb fn, void 
*arg, const char *help);
 
+/**
+ * Register a command and a file-descriptor-aware callback with telemetry.
+ *
+ * The callback is invoked like rte_telemetry_register_cmd_arg(), but also
+ * receives any file descriptors the client passed alongside the command as
+ * SCM_RIGHTS ancillary data. This lets a client open a file (for example a
+ * capture output file) itself and hand the descriptor to the DPDK process,
+ * which never opens the path - avoiding path and permission concerns and
+ * working across container filesystem namespaces.
+ *
+ * Descriptors sent to a command registered with rte_telemetry_register_cmd()
+ * or rte_telemetry_register_cmd_arg() are rejected and the connection is
+ * closed.
+ *
+ * @param cmd
+ *   The command to register with telemetry.
+ * @param fn
+ *   Callback function to be called when the command is requested.
+ * @param arg
+ *   An opaque value that will be passed to the callback function.
+ * @param help
+ *   Help text for the command.
+ *
+ * @return
+ *   0 on success.
+ * @return
+ *   -EINVAL for invalid parameters failure.
+ * @return
+ *   -ENOMEM for mem allocation failure.
+ */
+__rte_experimental
+int
+rte_telemetry_register_cmd_fd_arg(const char *cmd, telemetry_fd_cb fn, void 
*arg,
+               const char *help);
+
 /**
  * @internal
  * Free a container that has memory allocated.
diff --git a/lib/telemetry/telemetry.c b/lib/telemetry/telemetry.c
index b109d076d4..30d3ae3a13 100644
--- a/lib/telemetry/telemetry.c
+++ b/lib/telemetry/telemetry.c
@@ -29,6 +29,8 @@
 #define MAX_CMD_LEN 56
 #define MAX_OUTPUT_LEN (1024 * 16)
 #define MAX_CONNECTIONS 10
+/* Maximum number of file descriptors a client may pass with one command. */
+#define MAX_FDS 8
 
 #ifndef RTE_EXEC_ENV_WINDOWS
 static void *
@@ -39,6 +41,7 @@ struct cmd_callback {
        char cmd[MAX_CMD_LEN];
        telemetry_cb fn;
        telemetry_arg_cb fn_arg;
+       telemetry_fd_cb fn_fd;
        void *arg;
        char help[RTE_TEL_MAX_STRING_LEN];
 };
@@ -72,15 +75,15 @@ static RTE_ATOMIC(uint16_t) v2_clients;
 #endif /* !RTE_EXEC_ENV_WINDOWS */
 
 static int
-register_cmd(const char *cmd, const char *help,
-            telemetry_cb fn, telemetry_arg_cb fn_arg, void *arg)
+register_cmd(const char *cmd, const char *help, telemetry_cb fn,
+            telemetry_arg_cb fn_arg, telemetry_fd_cb fn_fd, void *arg)
 {
        struct cmd_callback *new_callbacks;
        const char *cmdp = cmd;
        int i = 0;
 
-       if (strlen(cmd) >= MAX_CMD_LEN || (fn == NULL && fn_arg == NULL) || 
cmd[0] != '/'
-                       || strlen(help) >= RTE_TEL_MAX_STRING_LEN)
+       if (strlen(cmd) >= MAX_CMD_LEN || (fn == NULL && fn_arg == NULL && 
fn_fd == NULL)
+                       || cmd[0] != '/' || strlen(help) >= 
RTE_TEL_MAX_STRING_LEN)
                return -EINVAL;
 
        while (*cmdp != '\0') {
@@ -107,6 +110,7 @@ register_cmd(const char *cmd, const char *help,
        strlcpy(callbacks[i].cmd, cmd, MAX_CMD_LEN);
        callbacks[i].fn = fn;
        callbacks[i].fn_arg = fn_arg;
+       callbacks[i].fn_fd = fn_fd;
        callbacks[i].arg = arg;
        strlcpy(callbacks[i].help, help, RTE_TEL_MAX_STRING_LEN);
        num_callbacks++;
@@ -119,14 +123,22 @@ RTE_EXPORT_SYMBOL(rte_telemetry_register_cmd)
 int
 rte_telemetry_register_cmd(const char *cmd, telemetry_cb fn, const char *help)
 {
-       return register_cmd(cmd, help, fn, NULL, NULL);
+       return register_cmd(cmd, help, fn, NULL, NULL, NULL);
 }
 
 RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_telemetry_register_cmd_arg, 24.11)
 int
 rte_telemetry_register_cmd_arg(const char *cmd, telemetry_arg_cb fn, void 
*arg, const char *help)
 {
-       return register_cmd(cmd, help, NULL, fn, arg);
+       return register_cmd(cmd, help, NULL, fn, NULL, arg);
+}
+
+RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_telemetry_register_cmd_fd_arg, 26.07)
+int
+rte_telemetry_register_cmd_fd_arg(const char *cmd, telemetry_fd_cb fn, void 
*arg,
+               const char *help)
+{
+       return register_cmd(cmd, help, NULL, NULL, fn, arg);
 }
 
 #ifndef RTE_EXEC_ENV_WINDOWS
@@ -368,13 +380,70 @@ output_json(const char *cmd, const struct rte_tel_data 
*d, int s)
                TMTY_LOG_LINE(ERR, "Error writing to socket: %s", 
strerror(errno));
 }
 
+/*
+ * Receive a command and any file descriptors the client passed alongside it
+ * as SCM_RIGHTS ancillary data. The payload length is returned (0 if the
+ * client sent an empty message or closed the connection, negative on error).
+ * Descriptors that arrive are returned in fds[]/n_fds and are owned by the
+ * caller. MSG_CTRUNC means more descriptors were sent than the control buffer
+ * could hold; *ctrunc is set so the caller can reject the command, but the
+ * descriptors that did fit are still returned so they can be closed rather
+ * than leaked.
+ */
+static int
+recv_with_fds(int s, char *buf, size_t buf_len, int *fds, unsigned int *n_fds,
+             bool *ctrunc)
+{
+       char cmsgbuf[CMSG_SPACE(sizeof(int) * MAX_FDS)];
+       struct iovec iov = { .iov_base = buf, .iov_len = buf_len };
+       struct msghdr msg = {
+               .msg_iov = &iov,
+               .msg_iovlen = 1,
+               .msg_control = cmsgbuf,
+               .msg_controllen = sizeof(cmsgbuf),
+       };
+       struct cmsghdr *cmsg;
+       int bytes;
+
+       *n_fds = 0;
+       *ctrunc = false;
+
+       bytes = recvmsg(s, &msg, 0);
+       if (bytes < 0)
+               return bytes;
+
+       if (msg.msg_flags & MSG_CTRUNC)
+               *ctrunc = true;
+
+       for (cmsg = CMSG_FIRSTHDR(&msg); cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, 
cmsg)) {
+               if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != 
SCM_RIGHTS)
+                       continue;
+               *n_fds = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
+               memcpy(fds, CMSG_DATA(cmsg), *n_fds * sizeof(int));
+               break;
+       }
+       return bytes;
+}
+
 static void
-perform_command(const struct cmd_callback *cb, const char *cmd, const char 
*param, int s)
+close_fds(const int *fds, unsigned int n_fds)
+{
+       unsigned int i;
+
+       for (i = 0; i < n_fds; i++)
+               close(fds[i]);
+}
+
+static void
+perform_command(const struct cmd_callback *cb, const char *cmd, const char 
*param,
+               const int *fds, unsigned int n_fds, int s)
 {
        struct rte_tel_data data = {0};
        int ret;
 
-       if (cb->fn_arg != NULL)
+       if (cb->fn_fd != NULL)
+               ret = cb->fn_fd(cmd, param, cb->arg, fds, n_fds, &data);
+       else if (cb->fn_arg != NULL)
                ret = cb->fn_arg(cmd, param, cb->arg, &data);
        else
                ret = cb->fn(cmd, param, &data);
@@ -412,8 +481,11 @@ client_handler(void *sock_id)
        }
 
        /* receive data is not null terminated */
-       int bytes = read(s, buffer, sizeof(buffer) - 1);
-       while (bytes > 0) {
+       int fds[MAX_FDS];
+       unsigned int n_fds = 0;
+       bool ctrunc = false;
+       int bytes = recv_with_fds(s, buffer, sizeof(buffer) - 1, fds, &n_fds, 
&ctrunc);
+       while (bytes > 0 || (bytes == 0 && n_fds > 0)) {
                buffer[bytes] = 0;
                const char *cmd = strtok(buffer, ",");
                const char *param = strtok(NULL, "\0");
@@ -429,9 +501,28 @@ client_handler(void *sock_id)
                                }
                        rte_spinlock_unlock(&callback_sl);
                }
-               perform_command(&cb, cmd, param, s);
 
-               bytes = read(s, buffer, sizeof(buffer) - 1);
+               /*
+                * File descriptors go only to a command that registered to
+                * receive them. A command that did not, or a truncated control
+                * message, is a client error: close the descriptors and drop 
the
+                * connection rather than silently discarding them.
+                */
+               if (n_fds > 0 && (cb.fn_fd == NULL || ctrunc)) {
+                       TMTY_LOG_LINE(ERR,
+                               "Closing connection: %u file descriptor(s) 
passed to '%s'%s",
+                               n_fds, cmd ? cmd : "(none)",
+                               ctrunc ? " (truncated)" : " which does not 
accept them");
+                       close_fds(fds, n_fds);
+                       break;
+               }
+
+               /* an fd-aware callback takes ownership of the descriptors */
+               perform_command(&cb, cmd, param, fds, n_fds, s);
+
+               n_fds = 0;
+               ctrunc = false;
+               bytes = recv_with_fds(s, buffer, sizeof(buffer) - 1, fds, 
&n_fds, &ctrunc);
        }
 exit:
        close(s);
-- 
2.53.0

Reply via email to