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.
This commit is contained in:
Kevin Bowling committed 2026-08-08 23:46:41 -07:00
1 parent 2c04cfa148
commit 2d6114f6d2
6 files changed
+154 -56

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
LIB= ifconfig
INTERNALLIB= true
LIBADD= m
LIBADD= m nv
SHLIBDIR?= /lib
SHLIB_MAJOR= 2
+4
View File
@@ -84,3 +84,7 @@ FBSD_1.6 {
ifconfig_sfp_rev_description;
ifconfig_sfp_rev_symbol;
};
FBSD_1.9 {
ifconfig_get_vf_status;
};
+129
View File
@@ -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)
{
+11
View File
@@ -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
+7 -53
View File
@@ -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;
}
+2 -2
View File
@@ -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