git: 2d6114f6d26b - main - libifconfig: Add an SR-IOV VF status query
Date: Sun, 09 Aug 2026 06:47:45 UTC
The branch main has been updated by kbowling:
URL: https://cgit.FreeBSD.org/src/commit/?id=2d6114f6d26bf7dfa5ad94e1db9b09ee7108dc7a
commit 2d6114f6d26bf7dfa5ad94e1db9b09ee7108dc7a
Author: Kevin Bowling <kbowling@FreeBSD.org>
AuthorDate: 2026-08-09 02:03:20 +0000
Commit: Kevin Bowling <kbowling@FreeBSD.org>
CommitDate: 2026-08-09 06:46:41 +0000
libifconfig: Add an SR-IOV VF status query
Provide a public helper which retrieves, unpacks, and validates the
versioned VF status nvlist. Validate the required VF indices and the
shape and version of driver-specific extension namespaces while allowing
unknown optional fields.
The ioctl argument is not copied back when the command returns EFBIG.
Start with a practical buffer and grow it geometrically rather than
relying on the required length being observable.
Use the helper in ifconfig so other consumers share the same transport
and validation behavior.
---
lib/libifconfig/Makefile | 2 +-
lib/libifconfig/Symbol.map | 4 ++
lib/libifconfig/libifconfig.c | 129 ++++++++++++++++++++++++++++++++++++++++++
lib/libifconfig/libifconfig.h | 11 ++++
sbin/ifconfig/ifvfstatus.c | 60 +++-----------------
share/mk/src.libnames.mk | 4 +-
6 files changed, 154 insertions(+), 56 deletions(-)
diff --git a/lib/libifconfig/Makefile b/lib/libifconfig/Makefile
index 02629eb88f25..48fc39d921bc 100644
--- a/lib/libifconfig/Makefile
+++ b/lib/libifconfig/Makefile
@@ -1,7 +1,7 @@
LIB= ifconfig
INTERNALLIB= true
-LIBADD= m
+LIBADD= m nv
SHLIBDIR?= /lib
SHLIB_MAJOR= 2
diff --git a/lib/libifconfig/Symbol.map b/lib/libifconfig/Symbol.map
index 2e11ff963909..a793fcdefbda 100644
--- a/lib/libifconfig/Symbol.map
+++ b/lib/libifconfig/Symbol.map
@@ -84,3 +84,7 @@ FBSD_1.6 {
ifconfig_sfp_rev_description;
ifconfig_sfp_rev_symbol;
};
+
+FBSD_1.9 {
+ ifconfig_get_vf_status;
+};
diff --git a/lib/libifconfig/libifconfig.c b/lib/libifconfig/libifconfig.c
index b450c15180db..3818cb689dbf 100644
--- a/lib/libifconfig/libifconfig.c
+++ b/lib/libifconfig/libifconfig.c
@@ -529,6 +529,135 @@ ifconfig_get_ifstatus(ifconfig_handle_t *h, const char *name,
return (ifconfig_ioctlwrap(h, AF_LOCAL, SIOCGIFSTATUS, ifs));
}
+static int
+ifconfig_vf_status_validate(const nvlist_t *status)
+{
+ const nvlist_t * const *vfs;
+ const nvlist_t *extensions, *vf;
+ const char *name;
+ void *cookie;
+ size_t i, num_vfs;
+ int type;
+
+ if (!nvlist_exists_number(status, IFVF_STATUS_VERSION_KEY))
+ return (EBADMSG);
+ if (nvlist_get_number(status, IFVF_STATUS_VERSION_KEY) !=
+ IFVF_STATUS_VERSION)
+ return (EPROTONOSUPPORT);
+ if (!nvlist_exists_nvlist_array(status, IFVF_STATUS_VFS))
+ return (EBADMSG);
+
+ vfs = nvlist_get_nvlist_array(status, IFVF_STATUS_VFS, &num_vfs);
+ for (i = 0; i < num_vfs; i++) {
+ vf = vfs[i];
+ if (!nvlist_exists_number(vf, IFVF_STATUS_INDEX))
+ return (EBADMSG);
+ if (!nvlist_exists(vf, IFVF_STATUS_EXTENSIONS))
+ continue;
+ if (!nvlist_exists_nvlist(vf, IFVF_STATUS_EXTENSIONS))
+ return (EBADMSG);
+ extensions = nvlist_get_nvlist(vf, IFVF_STATUS_EXTENSIONS);
+ cookie = NULL;
+ while ((name = nvlist_next(extensions, &type, &cookie)) != NULL) {
+ if (type != NV_TYPE_NVLIST)
+ return (EBADMSG);
+ if (!nvlist_exists_number(nvlist_get_nvlist(extensions,
+ name), IFVF_STATUS_EXT_VERSION))
+ return (EBADMSG);
+ }
+ }
+ return (0);
+}
+
+int
+ifconfig_get_vf_status(ifconfig_handle_t *h, const char *name,
+ nvlist_t **statusp)
+{
+ struct ifreq ifr;
+ nvlist_t *status;
+ void *buf, *newbuf;
+ size_t namelen;
+ u_int buflen, nextlen;
+ int error;
+
+ if (h == NULL || name == NULL || statusp == NULL) {
+ if (h != NULL)
+ ifconfig_error(h, OTHER, EINVAL);
+ return (-1);
+ }
+ *statusp = NULL;
+ namelen = strnlen(name, IFNAMSIZ);
+ if (namelen == IFNAMSIZ) {
+ ifconfig_error(h, OTHER, ENAMETOOLONG);
+ return (-1);
+ }
+
+ /*
+ * ioctl(2) does not copy an _IOWR argument back to userspace when the
+ * command returns EFBIG, so the kernel's required length is not
+ * observable on a short-buffer error. Start with enough space for the
+ * common case and grow geometrically instead of relying on length.
+ */
+ buflen = 16 * 1024;
+ buf = malloc(buflen);
+ if (buf == NULL) {
+ ifconfig_error(h, OTHER, ENOMEM);
+ return (-1);
+ }
+ for (;;) {
+ memset(&ifr, 0, sizeof(ifr));
+ memcpy(ifr.ifr_name, name, namelen + 1);
+ ifr.ifr_vf_status_nv.buffer = buf;
+ ifr.ifr_vf_status_nv.buf_length = buflen;
+ if (ifconfig_ioctlwrap(h, AF_LOCAL, SIOCGIFVFSTATUS, &ifr) == 0)
+ break;
+ if (ifconfig_err_errno(h) != EFBIG ||
+ buflen == IFR_VF_STATUS_NV_MAXBUFSIZE ||
+ ifr.ifr_vf_status_nv.length >
+ IFR_VF_STATUS_NV_MAXBUFSIZE) {
+ free(buf);
+ return (-1);
+ }
+ if (ifr.ifr_vf_status_nv.length > buflen)
+ nextlen = ifr.ifr_vf_status_nv.length;
+ else if (buflen > IFR_VF_STATUS_NV_MAXBUFSIZE / 2)
+ nextlen = IFR_VF_STATUS_NV_MAXBUFSIZE;
+ else
+ nextlen = buflen * 2;
+ newbuf = realloc(buf, nextlen);
+ if (newbuf == NULL) {
+ free(buf);
+ ifconfig_error(h, OTHER, ENOMEM);
+ return (-1);
+ }
+ buf = newbuf;
+ buflen = nextlen;
+ }
+
+ if (ifr.ifr_vf_status_nv.length == 0 ||
+ ifr.ifr_vf_status_nv.length > buflen) {
+ free(buf);
+ ifconfig_error(h, OTHER, EBADMSG);
+ return (-1);
+ }
+ status = nvlist_unpack(buf, ifr.ifr_vf_status_nv.length, 0);
+ free(buf);
+ if (status == NULL) {
+ ifconfig_error(h, OTHER, EBADMSG);
+ return (-1);
+ }
+ error = ifconfig_vf_status_validate(status);
+ if (error != 0) {
+ nvlist_destroy(status);
+ ifconfig_error(h, OTHER, error);
+ return (-1);
+ }
+
+ ifconfig_error_clear(h);
+ *statusp = status;
+ return (0);
+}
+
int
ifconfig_destroy_interface(ifconfig_handle_t *h, const char *name)
{
diff --git a/lib/libifconfig/libifconfig.h b/lib/libifconfig/libifconfig.h
index 817f52bd094e..130b002252c5 100644
--- a/lib/libifconfig/libifconfig.h
+++ b/lib/libifconfig/libifconfig.h
@@ -26,6 +26,7 @@
#pragma once
+#include <sys/nv.h>
#include <sys/types.h>
#include <net/if.h>
@@ -198,6 +199,16 @@ int ifconfig_get_groups(ifconfig_handle_t *h, const char *name,
int ifconfig_get_ifstatus(ifconfig_handle_t *h, const char *name,
struct ifstat *stat);
+/** Retrieve structured SR-IOV VF status for an interface.
+ * @param h An open ifconfig state object
+ * @param name The PF interface name
+ * @param statusp Return argument. The caller owns the returned nvlist and
+ * must destroy it with nvlist_destroy().
+ * @return 0 on success, -1 on failure
+ */
+int ifconfig_get_vf_status(ifconfig_handle_t *h, const char *name,
+ nvlist_t **statusp);
+
/** Retrieve the interface media information
* @param h An open ifconfig state object
* @param name The interface name
diff --git a/sbin/ifconfig/ifvfstatus.c b/sbin/ifconfig/ifvfstatus.c
index af857944a12a..12f495450260 100644
--- a/sbin/ifconfig/ifvfstatus.c
+++ b/sbin/ifconfig/ifvfstatus.c
@@ -27,9 +27,7 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-#include <sys/ioctl.h>
#include <sys/nv.h>
-#include <sys/socket.h>
#include <net/ethernet.h>
#include <net/if.h>
@@ -37,7 +35,6 @@
#include <err.h>
#include <errno.h>
#include <stdio.h>
-#include <stdlib.h>
#include <string.h>
#include "ifconfig.h"
@@ -63,64 +60,21 @@ vf_status(if_ctx *ctx)
{
const nvlist_t * const *vfs;
const nvlist_t *vf;
- struct ifreq ifr;
const void *mac;
nvlist_t *status;
const char *mode, *state;
size_t maclen, num_vfs;
uint64_t speed;
- void *buf, *newbuf;
- u_int buflen, nextlen;
bool printed;
+ int error;
- buflen = 16 * 1024;
- buf = malloc(buflen);
- if (buf == NULL)
- err(1, "malloc");
- for (;;) {
- memset(&ifr, 0, sizeof(ifr));
- strlcpy(ifr.ifr_name, ctx->ifname, sizeof(ifr.ifr_name));
- ifr.ifr_vf_status_nv.buffer = buf;
- ifr.ifr_vf_status_nv.buf_length = buflen;
- if (ioctl_ctx(ctx, SIOCGIFVFSTATUS, &ifr) == 0)
- break;
- if (errno == EINVAL || errno == ENOTTY || errno == ENXIO ||
- errno == ENOTSUP || errno == EOPNOTSUPP) {
- free(buf);
+ if (ifconfig_get_vf_status(lifh, ctx->ifname, &status) != 0) {
+ error = ifconfig_err_errno(lifh);
+ if (error == EINVAL || error == ENOTTY || error == ENXIO ||
+ error == ENOTSUP || error == EOPNOTSUPP)
return;
- }
- if (errno != EFBIG ||
- buflen == IFR_VF_STATUS_NV_MAXBUFSIZE ||
- ifr.ifr_vf_status_nv.length >
- IFR_VF_STATUS_NV_MAXBUFSIZE) {
- free(buf);
- warn("SIOCGIFVFSTATUS");
- return;
- }
- if (ifr.ifr_vf_status_nv.length > buflen)
- nextlen = ifr.ifr_vf_status_nv.length;
- else if (buflen > IFR_VF_STATUS_NV_MAXBUFSIZE / 2)
- nextlen = IFR_VF_STATUS_NV_MAXBUFSIZE;
- else
- nextlen = buflen * 2;
- newbuf = realloc(buf, nextlen);
- if (newbuf == NULL) {
- free(buf);
- err(1, "realloc");
- }
- buf = newbuf;
- buflen = nextlen;
- }
-
- status = nvlist_unpack(buf, ifr.ifr_vf_status_nv.length, 0);
- free(buf);
- if (status == NULL ||
- !nvlist_exists_number(status, IFVF_STATUS_VERSION_KEY) ||
- nvlist_get_number(status, IFVF_STATUS_VERSION_KEY) !=
- IFVF_STATUS_VERSION ||
- !nvlist_exists_nvlist_array(status, IFVF_STATUS_VFS)) {
- warnx("SIOCGIFVFSTATUS returned an unsupported format");
- nvlist_destroy(status);
+ errno = error;
+ warn("SIOCGIFVFSTATUS");
return;
}
diff --git a/share/mk/src.libnames.mk b/share/mk/src.libnames.mk
index b3c4e1861e24..589049bea777 100644
--- a/share/mk/src.libnames.mk
+++ b/share/mk/src.libnames.mk
@@ -379,7 +379,7 @@ _DP_gmock= gtest
_DP_gmock_main= gmock
_DP_gtest= pthread regex
_DP_gtest_main= gtest
-_DP_ifconfig= m
+_DP_ifconfig= m nv
_DP_ipf= kvm
_DP_iscsiutil= md
_DP_kldelf= elf
@@ -512,7 +512,7 @@ _DP_zpool= md pthread z icp spl nvpair avl umem
_DP_zutil= avl geom m
_DP_be= zfs spl nvpair zfsbootenv
_DP_netmap=
-_DP_ifconfig= m
+_DP_ifconfig= m nv
_DP_pfctl= nv
_DP_krb5ss= edit