mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-12-28 08:35:22 +00:00
5729d94917
Signed-off-by: Tim Wiederhake <twiederh@redhat.com> Reviewed-by: Ján Tomko <jtomko@redhat.com> Signed-off-by: Ján Tomko <jtomko@redhat.com>
3667 lines
105 KiB
C
3667 lines
105 KiB
C
/*
|
|
* Copyright (C) 2007-2016 Red Hat, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library. If not, see
|
|
* <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <config.h>
|
|
#include <math.h>
|
|
|
|
#include "virnetdev.h"
|
|
#include "viralloc.h"
|
|
#include "virnetlink.h"
|
|
#include "virmacaddr.h"
|
|
#include "virfile.h"
|
|
#include "virerror.h"
|
|
#include "vircommand.h"
|
|
#include "virpci.h"
|
|
#include "virlog.h"
|
|
#include "virstring.h"
|
|
#include "virutil.h"
|
|
#include "virjson.h"
|
|
|
|
#ifndef WIN32
|
|
# include <sys/ioctl.h>
|
|
#endif
|
|
#include <fcntl.h>
|
|
|
|
#ifdef __linux__
|
|
# include <linux/sockios.h>
|
|
# include <linux/if_vlan.h>
|
|
# define VIR_NETDEV_FAMILY AF_UNIX
|
|
#elif defined(WITH_STRUCT_IFREQ) && defined(AF_LOCAL)
|
|
# define VIR_NETDEV_FAMILY AF_LOCAL
|
|
#else
|
|
# undef WITH_STRUCT_IFREQ
|
|
#endif
|
|
|
|
#if defined(SIOCETHTOOL) && defined(WITH_STRUCT_IFREQ)
|
|
# include <linux/types.h>
|
|
# include <linux/ethtool.h>
|
|
#endif
|
|
|
|
#if WITH_DECL_LINK_ADDR
|
|
# include <sys/sockio.h>
|
|
# include <net/if_dl.h>
|
|
#endif
|
|
|
|
#if WITH_LINUX_DEVLINK_H
|
|
# include <linux/devlink.h>
|
|
#endif
|
|
|
|
#ifndef IFNAMSIZ
|
|
# define IFNAMSIZ 16
|
|
#endif
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_NONE
|
|
|
|
VIR_LOG_INIT("util.netdev");
|
|
|
|
#define PROC_NET_DEV_MCAST "/proc/net/dev_mcast"
|
|
#define MAX_MCAST_SIZE 50*14336
|
|
#define VIR_MCAST_NAME_LEN (IFNAMSIZ + 1)
|
|
#define VIR_MCAST_TOKEN_DELIMS " \n"
|
|
|
|
#if defined(SIOCSIFFLAGS) && defined(WITH_STRUCT_IFREQ)
|
|
# define VIR_IFF_UP IFF_UP
|
|
# define VIR_IFF_PROMISC IFF_PROMISC
|
|
# define VIR_IFF_MULTICAST IFF_MULTICAST
|
|
# define VIR_IFF_ALLMULTI IFF_ALLMULTI
|
|
#else
|
|
# define VIR_IFF_UP 0
|
|
# define VIR_IFF_PROMISC 0
|
|
# define VIR_IFF_MULTICAST 0
|
|
# define VIR_IFF_ALLMULTI 0
|
|
#endif
|
|
|
|
#define RESOURCE_FILE_LEN 4096
|
|
#if WITH_DECL_ETHTOOL_GFEATURES
|
|
# define TX_UDP_TNL 25
|
|
# define GFEATURES_SIZE 2
|
|
# define FEATURE_WORD(blocks, index, field) ((blocks)[(index) / 32U].field)
|
|
# define FEATURE_FIELD_FLAG(index) (1U << (index) % 32U)
|
|
# define FEATURE_BIT_IS_SET(blocks, index, field) \
|
|
(FEATURE_WORD(blocks, index, field) & FEATURE_FIELD_FLAG(index))
|
|
#endif
|
|
|
|
|
|
static virNetDevGenName
|
|
virNetDevGenNames[VIR_NET_DEV_GEN_NAME_LAST] = {
|
|
{-1, VIR_NET_GENERATED_VNET_PREFIX, VIR_MUTEX_INITIALIZER},
|
|
{-1, VIR_NET_GENERATED_MACVTAP_PREFIX, VIR_MUTEX_INITIALIZER},
|
|
{-1, VIR_NET_GENERATED_MACVLAN_PREFIX, VIR_MUTEX_INITIALIZER},
|
|
};
|
|
|
|
typedef enum {
|
|
VIR_MCAST_TYPE_INDEX_TOKEN,
|
|
VIR_MCAST_TYPE_NAME_TOKEN,
|
|
VIR_MCAST_TYPE_USERS_TOKEN,
|
|
VIR_MCAST_TYPE_GLOBAL_TOKEN,
|
|
VIR_MCAST_TYPE_ADDR_TOKEN,
|
|
|
|
VIR_MCAST_TYPE_LAST
|
|
} virMCastType;
|
|
|
|
typedef struct _virNetDevMcastEntry virNetDevMcastEntry;
|
|
struct _virNetDevMcastEntry {
|
|
int idx;
|
|
char name[VIR_MCAST_NAME_LEN];
|
|
int users;
|
|
bool global;
|
|
virMacAddr macaddr;
|
|
};
|
|
|
|
static void
|
|
virNetDevMcastEntryFree(virNetDevMcastEntry *entry)
|
|
{
|
|
g_free(entry);
|
|
}
|
|
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC(virNetDevMcastEntry, virNetDevMcastEntryFree);
|
|
|
|
typedef struct _virNetDevMcastList virNetDevMcastList;
|
|
struct _virNetDevMcastList {
|
|
size_t nentries;
|
|
virNetDevMcastEntry **entries;
|
|
};
|
|
|
|
#if defined(WITH_STRUCT_IFREQ)
|
|
static int virNetDevSetupControlFull(const char *ifname,
|
|
struct ifreq *ifr,
|
|
int domain,
|
|
int type)
|
|
{
|
|
int fd;
|
|
|
|
if (ifr && ifname) {
|
|
memset(ifr, 0, sizeof(*ifr));
|
|
|
|
if (virStrcpyStatic(ifr->ifr_name, ifname) < 0) {
|
|
virReportSystemError(ERANGE,
|
|
_("Network interface name '%s' is too long"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((fd = socket(domain, type, 0)) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot open network interface control socket"));
|
|
return -1;
|
|
}
|
|
|
|
if (virSetInherit(fd, false) < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Cannot set close-on-exec flag for socket"));
|
|
VIR_FORCE_CLOSE(fd);
|
|
return -1;
|
|
}
|
|
|
|
return fd;
|
|
}
|
|
|
|
|
|
int
|
|
virNetDevSetupControl(const char *ifname,
|
|
struct ifreq *ifr)
|
|
{
|
|
return virNetDevSetupControlFull(ifname, ifr, VIR_NETDEV_FAMILY, SOCK_DGRAM);
|
|
}
|
|
#else /* !WITH_STRUCT_IFREQ */
|
|
int
|
|
virNetDevSetupControl(const char *ifname G_GNUC_UNUSED,
|
|
void *ifr G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Network device configuration is not supported "
|
|
"on this platform"));
|
|
return -1;
|
|
}
|
|
#endif /* WITH_STRUCT_IFREQ */
|
|
|
|
|
|
#if defined(SIOCGIFFLAGS) && defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevExists:
|
|
* @ifname
|
|
*
|
|
* Check if the network device @ifname exists
|
|
*
|
|
* Returns 1 if it exists, 0 if it does not, -1 on error
|
|
*/
|
|
int virNetDevExists(const char *ifname)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFFLAGS, &ifr)) {
|
|
if (errno == ENODEV || errno == ENXIO)
|
|
return 0;
|
|
|
|
virReportSystemError(errno, _("Unable to check interface flags for %s"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#else
|
|
int virNetDevExists(const char *ifname)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Unable to check interface %s"), ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(SIOCGIFHWADDR) && defined(SIOCSIFHWADDR) && \
|
|
defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevSetMACInternal:
|
|
* @ifname: interface name to set MTU for
|
|
* @macaddr: MAC address
|
|
* @quiet: true if a failure to set MAC address with
|
|
* errno == EADDRNOTAVAIL || errno == EPERM
|
|
* should be silent (still returns error, but without log)
|
|
*
|
|
* This function sets the @macaddr for a given interface @ifname.
|
|
*
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
static int
|
|
virNetDevSetMACInternal(const char *ifname,
|
|
const virMacAddr *macaddr,
|
|
bool quiet)
|
|
{
|
|
struct ifreq ifr;
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
/* To fill ifr.ifr_hdaddr.sa_family field */
|
|
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
|
|
virReportSystemError(errno, _("Cannot get interface MAC on '%s'"),
|
|
ifname);
|
|
|
|
VIR_DEBUG("SIOCSIFHWADDR %s get MAC - Fail", ifname);
|
|
return -1;
|
|
}
|
|
|
|
virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data);
|
|
|
|
if (ioctl(fd, SIOCSIFHWADDR, &ifr) < 0) {
|
|
|
|
if (quiet &&
|
|
(errno == EADDRNOTAVAIL || errno == EPERM)) {
|
|
VIR_DEBUG("SIOCSIFHWADDR %s MAC=%s - Fail",
|
|
ifname, virMacAddrFormat(macaddr, macstr));
|
|
return -1;
|
|
}
|
|
|
|
virReportSystemError(errno,
|
|
_("Cannot set interface MAC to %s on '%s'"),
|
|
virMacAddrFormat(macaddr, macstr), ifname);
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("SIOCSIFHWADDR %s MAC=%s - Success",
|
|
ifname, virMacAddrFormat(macaddr, macstr));
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#elif defined(SIOCSIFLLADDR) && defined(WITH_STRUCT_IFREQ) && \
|
|
WITH_DECL_LINK_ADDR
|
|
|
|
|
|
static int
|
|
virNetDevSetMACInternal(const char *ifname,
|
|
const virMacAddr *macaddr,
|
|
bool quiet)
|
|
{
|
|
struct ifreq ifr;
|
|
struct sockaddr_dl sdl;
|
|
char mac[VIR_MAC_STRING_BUFLEN + 1] = ":";
|
|
VIR_AUTOCLOSE s = -1;
|
|
|
|
if ((s = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
virMacAddrFormat(macaddr, mac + 1);
|
|
sdl.sdl_len = sizeof(sdl);
|
|
link_addr(mac, &sdl);
|
|
|
|
memcpy(ifr.ifr_addr.sa_data, sdl.sdl_data, VIR_MAC_BUFLEN);
|
|
ifr.ifr_addr.sa_len = VIR_MAC_BUFLEN;
|
|
|
|
if (ioctl(s, SIOCSIFLLADDR, &ifr) < 0) {
|
|
if (quiet &&
|
|
(errno == EADDRNOTAVAIL || errno == EPERM)) {
|
|
VIR_DEBUG("SIOCSIFLLADDR %s MAC=%s - Fail", ifname, mac + 1);
|
|
return -1;
|
|
}
|
|
|
|
virReportSystemError(errno,
|
|
_("Cannot set interface MAC to %s on '%s'"),
|
|
mac + 1, ifname);
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("SIOCSIFLLADDR %s MAC=%s - Success", ifname, mac + 1);
|
|
return 0;
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
static int
|
|
virNetDevSetMACInternal(const char *ifname,
|
|
const virMacAddr *macaddr G_GNUC_UNUSED,
|
|
bool quiet G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot set interface MAC on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
int
|
|
virNetDevSetMAC(const char *ifname,
|
|
const virMacAddr *macaddr)
|
|
{
|
|
return virNetDevSetMACInternal(ifname, macaddr, false);
|
|
}
|
|
|
|
|
|
#if defined(SIOCGIFHWADDR) && defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevGetMAC:
|
|
* @ifname: interface name to set MTU for
|
|
* @macaddr: MAC address
|
|
*
|
|
* This function gets the @macaddr for a given interface @ifname.
|
|
*
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
int virNetDevGetMAC(const char *ifname,
|
|
virMacAddr *macaddr)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot get interface MAC on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
virMacAddrSetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data);
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int virNetDevGetMAC(const char *ifname,
|
|
virMacAddr *macaddr G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot get interface MAC on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(SIOCGIFMTU) && defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevGetMTU:
|
|
* @ifname: interface name get MTU for
|
|
*
|
|
* This function gets the @mtu value set for a given interface @ifname.
|
|
*
|
|
* Returns the MTU value in case of success, or -1 on failure.
|
|
*/
|
|
int virNetDevGetMTU(const char *ifname)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot get interface MTU on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
return ifr.ifr_mtu;
|
|
}
|
|
#else
|
|
int virNetDevGetMTU(const char *ifname)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot get interface MTU on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(SIOCSIFMTU) && defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevSetMTU:
|
|
* @ifname: interface name to set MTU for
|
|
* @mtu: MTU value
|
|
*
|
|
* This function sets the @mtu for a given interface @ifname. Typically
|
|
* used on a tap device to set up for Jumbo Frames.
|
|
*
|
|
* Returns 0 in case of success, or -1 on failure
|
|
*/
|
|
int virNetDevSetMTU(const char *ifname, int mtu)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
ifr.ifr_mtu = mtu;
|
|
|
|
if (ioctl(fd, SIOCSIFMTU, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot set interface MTU on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int virNetDevSetMTU(const char *ifname, int mtu G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot set interface MTU on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* virNetDevSetMTUFromDevice:
|
|
* @ifname: name of the interface whose MTU we want to set
|
|
* @otherifname: name of the interface whose MTU we want to copy
|
|
*
|
|
* Sets the interface mtu to the same MTU as another interface
|
|
*
|
|
* Returns 0 in case of success, or -1 on failure
|
|
*/
|
|
int virNetDevSetMTUFromDevice(const char *ifname,
|
|
const char *otherifname)
|
|
{
|
|
int mtu = virNetDevGetMTU(otherifname);
|
|
|
|
if (mtu < 0)
|
|
return -1;
|
|
|
|
return virNetDevSetMTU(ifname, mtu);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevSetNamespace:
|
|
* @ifname: name of device
|
|
* @pidInNs: PID of process in target net namespace
|
|
*
|
|
* Moves the given device into the target net namespace specified by the given
|
|
* pid using this command:
|
|
* ip link set @iface netns @pidInNs
|
|
*
|
|
* Returns 0 on success or -1 in case of error
|
|
*/
|
|
int virNetDevSetNamespace(const char *ifname, pid_t pidInNs)
|
|
{
|
|
g_autofree char *pid = NULL;
|
|
g_autofree char *phy = NULL;
|
|
g_autofree char *phy_path = NULL;
|
|
g_autoptr(virCommand) cmd = NULL;
|
|
int len;
|
|
|
|
pid = g_strdup_printf("%lld", (long long) pidInNs);
|
|
|
|
/* The 802.11 wireless devices only move together with their PHY. */
|
|
if (virNetDevSysfsFile(&phy_path, ifname, "phy80211/name") < 0)
|
|
return -1;
|
|
|
|
if ((len = virFileReadAllQuiet(phy_path, 1024, &phy)) <= 0) {
|
|
/* Not a wireless device. */
|
|
cmd = virCommandNewArgList("ip", "link",
|
|
"set", ifname, "netns", pid, NULL);
|
|
} else {
|
|
/* Remove a line break. */
|
|
phy[len - 1] = '\0';
|
|
|
|
cmd = virCommandNewArgList("iw", "phy", phy,
|
|
"set", "netns", pid, NULL);
|
|
}
|
|
|
|
if (virCommandRun(cmd, NULL) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if defined(SIOCSIFNAME) && defined(WITH_STRUCT_IFREQ)
|
|
/**
|
|
* virNetDevSetName:
|
|
* @ifname: name of device
|
|
* @newifname: new name of @ifname
|
|
*
|
|
* Changes the name of the given device.
|
|
*
|
|
* Returns 0 on success, -1 on error
|
|
*/
|
|
int virNetDevSetName(const char* ifname, const char *newifname)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
# ifdef WITH_STRUCT_IFREQ_IFR_NEWNAME
|
|
if (virStrcpyStatic(ifr.ifr_newname, newifname) < 0) {
|
|
virReportSystemError(ERANGE,
|
|
_("Network interface name '%s' is too long"),
|
|
newifname);
|
|
return -1;
|
|
}
|
|
# else
|
|
ifr.ifr_data = (caddr_t)newifname;
|
|
# endif
|
|
|
|
if (ioctl(fd, SIOCSIFNAME, &ifr)) {
|
|
virReportSystemError(errno,
|
|
_("Unable to rename '%s' to '%s'"),
|
|
ifname, newifname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int virNetDevSetName(const char* ifname, const char *newifname)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot rename interface '%s' to '%s' on this platform"),
|
|
ifname, newifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#if defined(SIOCSIFFLAGS) && defined(WITH_STRUCT_IFREQ)
|
|
static int
|
|
virNetDevSetIFFlag(const char *ifname, int flag, bool val)
|
|
{
|
|
struct ifreq ifr;
|
|
int ifflags;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot get interface flags on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (val)
|
|
ifflags = ifr.ifr_flags | flag;
|
|
else
|
|
ifflags = ifr.ifr_flags & ~flag;
|
|
|
|
if (ifr.ifr_flags != ifflags) {
|
|
ifr.ifr_flags = ifflags;
|
|
if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot set interface flags on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
static int
|
|
virNetDevSetIFFlag(const char *ifname,
|
|
int flag G_GNUC_UNUSED,
|
|
bool val G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot set interface flags on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
|
|
/**
|
|
* virNetDevSetOnline:
|
|
* @ifname: the interface name
|
|
* @online: true for up, false for down
|
|
*
|
|
* Function to control if an interface is activated (up, true) or not (down, false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevSetOnline(const char *ifname,
|
|
bool online)
|
|
{
|
|
return virNetDevSetIFFlag(ifname, VIR_IFF_UP, online);
|
|
}
|
|
|
|
/**
|
|
* virNetDevSetPromiscuous:
|
|
* @ifname: the interface name
|
|
* @promiscuous: true for receive all packets, false for do not receive
|
|
* all packets
|
|
*
|
|
* Function to control if an interface is to receive all
|
|
* packets (receive all, true) or not (do not receive all, false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevSetPromiscuous(const char *ifname,
|
|
bool promiscuous)
|
|
{
|
|
return virNetDevSetIFFlag(ifname, VIR_IFF_PROMISC, promiscuous);
|
|
}
|
|
|
|
/**
|
|
* virNetDevSetRcvMulti:
|
|
* @ifname: the interface name
|
|
* @:receive true for receive multicast packets, false for do not receive
|
|
* multicast packets
|
|
*
|
|
* Function to control if an interface is to receive multicast
|
|
* packets in which it is interested (receive, true)
|
|
* or not (do not receive, false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevSetRcvMulti(const char *ifname,
|
|
bool receive)
|
|
{
|
|
return virNetDevSetIFFlag(ifname, VIR_IFF_MULTICAST, receive);
|
|
}
|
|
|
|
/**
|
|
* virNetDevSetRcvAllMulti:
|
|
* @ifname: the interface name
|
|
* @:receive true for receive all packets, false for do not receive all packets
|
|
*
|
|
* Function to control if an interface is to receive all multicast
|
|
* packets (receive, true) or not (do not receive, false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevSetRcvAllMulti(const char *ifname,
|
|
bool receive)
|
|
{
|
|
return virNetDevSetIFFlag(ifname, VIR_IFF_ALLMULTI, receive);
|
|
}
|
|
|
|
|
|
#if defined(SIOCGIFFLAGS) && defined(WITH_STRUCT_IFREQ)
|
|
static int
|
|
virNetDevGetIFFlag(const char *ifname, int flag, bool *val)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot get interface flags on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
*val = (ifr.ifr_flags & flag) ? true : false;
|
|
return 0;
|
|
}
|
|
#else
|
|
static int
|
|
virNetDevGetIFFlag(const char *ifname,
|
|
int flag G_GNUC_UNUSED,
|
|
bool *val G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot get interface flags on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* virNetDevGetOnline:
|
|
* @ifname: the interface name
|
|
* @online: where to store the status
|
|
*
|
|
* Function to query if an interface is activated (true) or not (false)
|
|
*
|
|
* Returns 0 in case of success or an errno code in case of failure.
|
|
*/
|
|
int
|
|
virNetDevGetOnline(const char *ifname,
|
|
bool *online)
|
|
{
|
|
return virNetDevGetIFFlag(ifname, VIR_IFF_UP, online);
|
|
}
|
|
|
|
/**
|
|
* virNetDevIsPromiscuous:
|
|
* @ifname: the interface name
|
|
* @promiscuous: where to store the status
|
|
*
|
|
* Function to query if an interface is receiving all packets (true) or
|
|
* not (false)
|
|
*
|
|
* Returns 0 in case of success or an errno code in case of failure.
|
|
*/
|
|
int
|
|
virNetDevGetPromiscuous(const char *ifname,
|
|
bool *promiscuous)
|
|
{
|
|
return virNetDevGetIFFlag(ifname, VIR_IFF_PROMISC, promiscuous);
|
|
}
|
|
|
|
/**
|
|
* virNetDevIsRcvMulti:
|
|
* @ifname: the interface name
|
|
* @receive where to store the status
|
|
*
|
|
* Function to query whether an interface is receiving multicast packets (true)
|
|
* in which it is interested, or not (false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevGetRcvMulti(const char *ifname,
|
|
bool *receive)
|
|
{
|
|
return virNetDevGetIFFlag(ifname, VIR_IFF_MULTICAST, receive);
|
|
}
|
|
|
|
/**
|
|
* virNetDevIsRcvAllMulti:
|
|
* @ifname: the interface name
|
|
* @:receive where to store the status
|
|
*
|
|
* Function to query whether an interface is receiving all multicast
|
|
* packets (receiving, true) or not (is not receiving, false)
|
|
*
|
|
* Returns 0 in case of success or -1 on error.
|
|
*/
|
|
int
|
|
virNetDevGetRcvAllMulti(const char *ifname,
|
|
bool *receive)
|
|
{
|
|
return virNetDevGetIFFlag(ifname, VIR_IFF_ALLMULTI, receive);
|
|
}
|
|
|
|
#if defined(WITH_IF_INDEXTONAME)
|
|
char *virNetDevGetName(int ifindex)
|
|
{
|
|
char name[IFNAMSIZ];
|
|
|
|
memset(&name, 0, sizeof(name));
|
|
|
|
if (!if_indextoname(ifindex, name)) {
|
|
virReportSystemError(errno,
|
|
_("Failed to convert interface index %d to a name"),
|
|
ifindex);
|
|
return NULL;
|
|
}
|
|
|
|
return g_strdup(name);
|
|
}
|
|
#else
|
|
char *virNetDevGetName(int ifindex)
|
|
{
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot get interface name for index '%i'"),
|
|
ifindex);
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* virNetDevGetIndex:
|
|
* @ifname : Name of the interface whose index is to be found
|
|
* @ifindex: Pointer to int where the index will be written into
|
|
*
|
|
* Get the index of an interface given its name.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*/
|
|
#if defined(SIOCGIFINDEX) && defined(WITH_STRUCT_IFREQ)
|
|
int virNetDevGetIndex(const char *ifname, int *ifindex)
|
|
{
|
|
struct ifreq ifreq;
|
|
VIR_AUTOCLOSE fd = socket(VIR_NETDEV_FAMILY, SOCK_DGRAM, 0);
|
|
|
|
if (fd < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to open control socket"));
|
|
return -1;
|
|
}
|
|
|
|
memset(&ifreq, 0, sizeof(ifreq));
|
|
|
|
if (virStrcpyStatic(ifreq.ifr_name, ifname) < 0) {
|
|
virReportSystemError(ERANGE,
|
|
_("invalid interface name %s"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(fd, SIOCGIFINDEX, &ifreq) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to get index for interface %s"), ifname);
|
|
return -1;
|
|
}
|
|
|
|
# ifdef WITH_STRUCT_IFREQ_IFR_INDEX
|
|
*ifindex = ifreq.ifr_index;
|
|
# else
|
|
*ifindex = ifreq.ifr_ifindex;
|
|
# endif
|
|
return 0;
|
|
}
|
|
#else /* ! SIOCGIFINDEX */
|
|
int virNetDevGetIndex(const char *ifname G_GNUC_UNUSED,
|
|
int *ifindex G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get interface index on this platform"));
|
|
return -1;
|
|
}
|
|
#endif /* ! SIOCGIFINDEX */
|
|
|
|
|
|
#if defined(WITH_LIBNL)
|
|
/**
|
|
* virNetDevGetMaster:
|
|
* @ifname: name of interface we're interested in
|
|
* @master: used to return a string containing the name of @ifname's "master"
|
|
* (this is the bridge or bond device that this device is attached to)
|
|
*
|
|
* Returns 0 on success, -1 on failure (if @ifname has no master
|
|
* @master will be NULL, but return value will still be 0 (success)).
|
|
*/
|
|
int
|
|
virNetDevGetMaster(const char *ifname, char **master)
|
|
{
|
|
g_autofree void *nlData = NULL;
|
|
struct nlattr *tb[IFLA_MAX + 1] = {NULL, };
|
|
|
|
*master = NULL;
|
|
|
|
if (virNetlinkDumpLink(ifname, -1, &nlData, tb, 0, 0) < 0)
|
|
return -1;
|
|
|
|
if (tb[IFLA_MASTER]) {
|
|
if (!(*master = virNetDevGetName(*(int *)RTA_DATA(tb[IFLA_MASTER]))))
|
|
return -1;
|
|
}
|
|
|
|
VIR_DEBUG("IFLA_MASTER for %s is %s", ifname, *master ? *master : "(none)");
|
|
return 0;
|
|
}
|
|
|
|
#elif defined(__linux__)
|
|
|
|
/* libnl isn't available, so we can't use netlink.
|
|
* Fall back to using sysfs
|
|
*/
|
|
int
|
|
virNetDevGetMaster(const char *ifname, char **master)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *canonical = NULL;
|
|
|
|
if (virNetDevSysfsFile(&path, ifname, "master") < 0)
|
|
return -1;
|
|
|
|
if (!(canonical = virFileCanonicalizePath(path)))
|
|
return -1;
|
|
|
|
*master = g_path_get_basename(canonical);
|
|
|
|
VIR_DEBUG("IFLA_MASTER for %s is %s", ifname, *master ? *master : "(none)");
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
virNetDevGetMaster(const char *ifname G_GNUC_UNUSED,
|
|
char **master G_GNUC_UNUSED)
|
|
{
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("Unable to get device master from netlink on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif /* defined(WITH_LIBNL) */
|
|
|
|
|
|
#if defined(SIOCGIFVLAN) && defined(WITH_STRUCT_IFREQ) && WITH_DECL_GET_VLAN_VID_CMD
|
|
int virNetDevGetVLanID(const char *ifname, int *vlanid)
|
|
{
|
|
struct vlan_ioctl_args vlanargs = {
|
|
.cmd = GET_VLAN_VID_CMD,
|
|
};
|
|
VIR_AUTOCLOSE fd = socket(PF_PACKET, SOCK_DGRAM, 0);
|
|
|
|
if (fd < 0) {
|
|
virReportSystemError(errno, "%s",
|
|
_("Unable to open control socket"));
|
|
return -1;
|
|
}
|
|
|
|
if (virStrcpyStatic(vlanargs.device1, ifname) < 0) {
|
|
virReportSystemError(ERANGE,
|
|
_("invalid interface name %s"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (ioctl(fd, SIOCGIFVLAN, &vlanargs) != 0) {
|
|
virReportSystemError(errno,
|
|
_("Unable to get VLAN for interface %s"), ifname);
|
|
return -1;
|
|
}
|
|
|
|
*vlanid = vlanargs.u.VID;
|
|
return 0;
|
|
}
|
|
#else /* ! SIOCGIFVLAN */
|
|
int virNetDevGetVLanID(const char *ifname G_GNUC_UNUSED,
|
|
int *vlanid G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get VLAN on this platform"));
|
|
return -1;
|
|
}
|
|
#endif /* ! SIOCGIFVLAN */
|
|
|
|
|
|
/**
|
|
* virNetDevValidateConfig:
|
|
* @ifname: Name of the interface
|
|
* @macaddr: expected MAC address of the interface; not checked if NULL
|
|
* @ifindex: expected index of the interface; not checked if '-1'
|
|
*
|
|
* Determine whether a given interface is still available. If so,
|
|
* it must have the given MAC address and if an interface index is
|
|
* passed, it must also match the interface index.
|
|
*
|
|
* Returns 1 if the config matches, 0 if the config does not match, or interface does not exist, -1 on error
|
|
*/
|
|
#if defined(SIOCGIFHWADDR) && defined(WITH_STRUCT_IFREQ)
|
|
int virNetDevValidateConfig(const char *ifname,
|
|
const virMacAddr *macaddr, int ifindex)
|
|
{
|
|
struct ifreq ifr;
|
|
int idx;
|
|
int rc;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((rc = virNetDevExists(ifname)) < 0)
|
|
return -1;
|
|
if (rc == 0)
|
|
return 0;
|
|
|
|
if (macaddr != NULL) {
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) {
|
|
if (errno == ENODEV)
|
|
return 0;
|
|
|
|
virReportSystemError(errno,
|
|
_("could not get MAC address of interface %s"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (virMacAddrCmpRaw(macaddr,
|
|
(unsigned char *)ifr.ifr_hwaddr.sa_data) != 0)
|
|
return 0;
|
|
}
|
|
|
|
if (ifindex != -1) {
|
|
if (virNetDevGetIndex(ifname, &idx) < 0)
|
|
return -1;
|
|
if (idx != ifindex)
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
#else
|
|
int virNetDevValidateConfig(const char *ifname G_GNUC_UNUSED,
|
|
const virMacAddr *macaddr G_GNUC_UNUSED,
|
|
int ifindex G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to check interface config on this platform"));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
#ifdef __linux__
|
|
|
|
int
|
|
virNetDevSysfsFile(char **pf_sysfs_device_link, const char *ifname,
|
|
const char *file)
|
|
{
|
|
*pf_sysfs_device_link = g_strdup_printf(SYSFS_NET_DIR "%s/%s", ifname, file);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
virNetDevSysfsDeviceFile(char **pf_sysfs_device_link, const char *ifname,
|
|
const char *file)
|
|
{
|
|
*pf_sysfs_device_link = g_strdup_printf(SYSFS_NET_DIR "%s/device/%s", ifname,
|
|
file);
|
|
return 0;
|
|
}
|
|
|
|
|
|
# if defined(WITH_LIBNL)
|
|
|
|
/**
|
|
* Determine if the device path specified in devpath is a PCI Device
|
|
* by resolving the 'subsystem'-link in devpath and looking for
|
|
* 'pci' in the last component. For more information see the rules
|
|
* for accessing sysfs in the kernel docs
|
|
*
|
|
* https://www.kernel.org/doc/html/latest/admin-guide/sysfs-rules.html
|
|
*
|
|
* Returns true if devpath's subsystem is pci, false otherwise.
|
|
*/
|
|
static bool
|
|
virNetDevIsPCIDevice(const char *devpath)
|
|
{
|
|
g_autofree char *subsys_link = NULL;
|
|
g_autofree char *abs_path = NULL;
|
|
g_autofree char *subsys = NULL;
|
|
|
|
subsys_link = g_strdup_printf("%s/subsystem", devpath);
|
|
|
|
if (!virFileExists(subsys_link))
|
|
return false;
|
|
|
|
if (virFileResolveLink(subsys_link, &abs_path) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to resolve device subsystem symlink %s"),
|
|
subsys_link);
|
|
return false;
|
|
}
|
|
|
|
subsys = g_path_get_basename(abs_path);
|
|
return STRPREFIX(subsys, "pci");
|
|
}
|
|
|
|
|
|
static virPCIDevice *
|
|
virNetDevGetPCIDevice(const char *devName)
|
|
{
|
|
g_autofree char *vfSysfsDevicePath = NULL;
|
|
g_autoptr(virPCIDeviceAddress) vfPCIAddr = NULL;
|
|
|
|
if (virNetDevSysfsFile(&vfSysfsDevicePath, devName, "device") < 0)
|
|
return NULL;
|
|
|
|
if (!virNetDevIsPCIDevice(vfSysfsDevicePath))
|
|
return NULL;
|
|
|
|
vfPCIAddr = virPCIGetDeviceAddressFromSysfsLink(vfSysfsDevicePath);
|
|
if (!vfPCIAddr)
|
|
return NULL;
|
|
|
|
return virPCIDeviceNew(vfPCIAddr);
|
|
}
|
|
# endif
|
|
|
|
|
|
/* A wrapper to get content of file from ifname SYSFS_NET_DIR
|
|
*/
|
|
static int
|
|
virNetDevGetSysfsFileValue(const char *ifname,
|
|
const char *fileName,
|
|
char **sysfsFileData)
|
|
{
|
|
g_autofree char *sysfsFile = NULL;
|
|
|
|
*sysfsFileData = NULL;
|
|
|
|
if (virNetDevSysfsFile(&sysfsFile, ifname, fileName) < 0)
|
|
return -1;
|
|
|
|
/* a failure to read just means the driver doesn't support
|
|
* <fileName>, so set success now and ignore the return from
|
|
* virFileReadAllQuiet().
|
|
*/
|
|
|
|
ignore_value(virFileReadAllQuiet(sysfsFile, 1024, sysfsFileData));
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* virNetDevGetPhysPortID:
|
|
*
|
|
* @ifname: name of a netdev
|
|
*
|
|
* @physPortID: pointer to char* that will receive @ifname's
|
|
* phys_port_id from sysfs (null terminated
|
|
* string). Could be NULL if @ifname's net driver doesn't
|
|
* support phys_port_id (most netdev drivers
|
|
* don't). Caller is responsible for freeing the string
|
|
* when finished.
|
|
*
|
|
* Returns 0 on success or -1 on failure.
|
|
*/
|
|
int
|
|
virNetDevGetPhysPortID(const char *ifname,
|
|
char **physPortID)
|
|
{
|
|
return virNetDevGetSysfsFileValue(ifname, "phys_port_id", physPortID);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevGetPhysPortName:
|
|
*
|
|
* @ifname: name of a netdev
|
|
*
|
|
* @physPortName: pointer to char* that will receive @ifname's
|
|
* phys_port_name from sysfs (null terminated
|
|
* string). Could be NULL if @ifname's net driver doesn't
|
|
* support phys_port_name (most netdev drivers
|
|
* don't). Caller is responsible for freeing the string
|
|
* when finished.
|
|
*
|
|
* Returns 0 on success or -1 on failure.
|
|
*/
|
|
int
|
|
virNetDevGetPhysPortName(const char *ifname,
|
|
char **physPortName)
|
|
{
|
|
return virNetDevGetSysfsFileValue(ifname, "phys_port_name", physPortName);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevGetVirtualFunctions:
|
|
*
|
|
* @pfname : name of the physical function interface name
|
|
* @vfname: array that will hold the interface names of the virtual_functions
|
|
* @n_vfname: pointer to the number of virtual functions
|
|
*
|
|
* Returns 0 on success and -1 on failure
|
|
*/
|
|
|
|
int
|
|
virNetDevGetVirtualFunctions(const char *pfname,
|
|
char ***vfname,
|
|
virPCIDeviceAddress ***virt_fns,
|
|
size_t *n_vfname,
|
|
unsigned int *max_vfs)
|
|
{
|
|
int ret = -1;
|
|
size_t i;
|
|
g_autofree char *pf_sysfs_device_link = NULL;
|
|
g_autofree char *pfPhysPortID = NULL;
|
|
|
|
*virt_fns = NULL;
|
|
*n_vfname = 0;
|
|
*max_vfs = 0;
|
|
|
|
if (virNetDevGetPhysPortID(pfname, &pfPhysPortID) < 0)
|
|
goto cleanup;
|
|
|
|
if (virNetDevSysfsFile(&pf_sysfs_device_link, pfname, "device") < 0)
|
|
goto cleanup;
|
|
|
|
if (virPCIGetVirtualFunctions(pf_sysfs_device_link, virt_fns,
|
|
n_vfname, max_vfs) < 0)
|
|
goto cleanup;
|
|
|
|
*vfname = g_new0(char *, *n_vfname);
|
|
|
|
for (i = 0; i < *n_vfname; i++) {
|
|
g_autofree char *pci_sysfs_device_link = NULL;
|
|
|
|
if (virPCIDeviceAddressGetSysfsFile((*virt_fns)[i],
|
|
&pci_sysfs_device_link) < 0) {
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Failed to get PCI SYSFS file"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virPCIGetNetName(pci_sysfs_device_link, 0,
|
|
pfPhysPortID, &((*vfname)[i])) < 0) {
|
|
goto cleanup;
|
|
}
|
|
|
|
if (!(*vfname)[i])
|
|
VIR_INFO("VF does not have an interface name");
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
if (ret < 0) {
|
|
virStringListFreeCount(*vfname, *n_vfname);
|
|
|
|
for (i = 0; i < *n_vfname; i++)
|
|
VIR_FREE((*virt_fns)[i]);
|
|
VIR_FREE(*virt_fns);
|
|
*vfname = NULL;
|
|
*n_vfname = 0;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* virNetDevIsVirtualFunction:
|
|
* @ifname : name of the interface
|
|
*
|
|
* Checks if an interface is a SRIOV virtual function.
|
|
*
|
|
* Returns 1 if interface is SRIOV virtual function, 0 if not and -1 if error
|
|
*
|
|
*/
|
|
int
|
|
virNetDevIsVirtualFunction(const char *ifname)
|
|
{
|
|
g_autofree char *if_sysfs_device_link = NULL;
|
|
|
|
if (virNetDevSysfsFile(&if_sysfs_device_link, ifname, "device") < 0)
|
|
return -1;
|
|
|
|
return virPCIIsVirtualFunction(if_sysfs_device_link);
|
|
}
|
|
|
|
/**
|
|
* virNetDevGetVirtualFunctionIndex
|
|
*
|
|
* @pfname : name of the physical function interface name
|
|
* @vfname : name of the virtual function interface name
|
|
* @vf_index : Pointer to int. Contains vf index of interface upon successful
|
|
* return
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*
|
|
*/
|
|
int
|
|
virNetDevGetVirtualFunctionIndex(const char *pfname, const char *vfname,
|
|
int *vf_index)
|
|
{
|
|
g_autofree char *pf_sysfs_device_link = NULL;
|
|
g_autofree char *vf_sysfs_device_link = NULL;
|
|
|
|
if (virNetDevSysfsFile(&pf_sysfs_device_link, pfname, "device") < 0)
|
|
return -1;
|
|
|
|
if (virNetDevSysfsFile(&vf_sysfs_device_link, vfname, "device") < 0)
|
|
return -1;
|
|
|
|
return virPCIGetVirtualFunctionIndex(pf_sysfs_device_link,
|
|
vf_sysfs_device_link,
|
|
vf_index);
|
|
}
|
|
|
|
/**
|
|
* virNetDevGetPhysicalFunction
|
|
*
|
|
* @ifname : name of the physical function interface name
|
|
* @pfname : Contains sriov physical function for interface ifname
|
|
* upon successful return (might be NULL if the PF has no
|
|
* associated netdev. This is *not* an error)
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*
|
|
*/
|
|
int
|
|
virNetDevGetPhysicalFunction(const char *ifname, char **pfname)
|
|
{
|
|
g_autofree char *physfn_sysfs_path = NULL;
|
|
g_autofree char *vfPhysPortID = NULL;
|
|
|
|
if (virNetDevGetPhysPortID(ifname, &vfPhysPortID) < 0)
|
|
return -1;
|
|
|
|
if (virNetDevSysfsDeviceFile(&physfn_sysfs_path, ifname, "physfn") < 0)
|
|
return -1;
|
|
|
|
if (virPCIGetNetName(physfn_sysfs_path, 0,
|
|
vfPhysPortID, pfname) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevPFGetVF:
|
|
*
|
|
* @pfname: netdev name of the physical function (PF)
|
|
* @vf: virtual function (VF) number for the device of interest
|
|
* @vfname: name of the physical function interface name
|
|
*
|
|
* Finds the netdev name of VF# @vf of SRIOV PF @pfname, and puts it
|
|
* in @vfname. The caller must free @vfname when it's finished with
|
|
* it.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*
|
|
* NB: if the VF has no netdev name, that is *not* considered an
|
|
* error; *vfname simply gets a NULL and the return value is 0
|
|
* (success).
|
|
*/
|
|
int
|
|
virNetDevPFGetVF(const char *pfname, int vf, char **vfname)
|
|
{
|
|
g_autofree char *virtfnName = NULL;
|
|
g_autofree char *virtfnSysfsPath = NULL;
|
|
g_autofree char *pfPhysPortID = NULL;
|
|
|
|
/* a VF may have multiple "ports", each one having its own netdev,
|
|
* and each netdev having a different phys_port_id. Be sure we get
|
|
* the VF netdev with a phys_port_id matchine that of pfname
|
|
*/
|
|
if (virNetDevGetPhysPortID(pfname, &pfPhysPortID) < 0)
|
|
return -1;
|
|
|
|
virtfnName = g_strdup_printf("virtfn%d", vf);
|
|
|
|
/* this provides the path to the VF's directory in sysfs,
|
|
* e.g. "/sys/class/net/enp2s0f0/virtfn3"
|
|
*/
|
|
if (virNetDevSysfsDeviceFile(&virtfnSysfsPath, pfname, virtfnName) < 0)
|
|
return -1;
|
|
|
|
/* and this gets the netdev name associated with it, which is a
|
|
* directory entry in [virtfnSysfsPath]/net,
|
|
* e.g. "/sys/class/net/enp2s0f0/virtfn3/net/enp2s11f4" - in this
|
|
* example the VF for enp2s0f0 vf#3 is "enp2s11f4". (If the VF
|
|
* isn't bound to a netdev driver, it won't have a netdev name,
|
|
* and vfname will be NULL).
|
|
*/
|
|
return virPCIGetNetName(virtfnSysfsPath, 0, pfPhysPortID, vfname);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevGetVirtualFunctionInfo:
|
|
* @vfname: name of the virtual function interface
|
|
* @pfname: name of the physical function
|
|
* @vf: vf index
|
|
*
|
|
* Returns 0 on success, -errno on failure.
|
|
*
|
|
*/
|
|
int
|
|
virNetDevGetVirtualFunctionInfo(const char *vfname, char **pfname,
|
|
int *vf)
|
|
{
|
|
int ret = -1;
|
|
|
|
if (virNetDevGetPhysicalFunction(vfname, pfname) < 0)
|
|
return -1;
|
|
|
|
if (!*pfname) {
|
|
/* The SRIOV standard does not require VF netdevs to have the
|
|
* netdev assigned to a PF, but our method of retrieving
|
|
* VFINFO does.
|
|
*/
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("The PF device for VF %s has no network device name, cannot get virtual function info"),
|
|
vfname);
|
|
return -1;
|
|
}
|
|
|
|
if (virNetDevGetVirtualFunctionIndex(*pfname, vfname, vf) < 0)
|
|
goto cleanup;
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0)
|
|
VIR_FREE(*pfname);
|
|
return ret;
|
|
}
|
|
|
|
#else /* !__linux__ */
|
|
int
|
|
virNetDevGetPhysPortID(const char *ifname G_GNUC_UNUSED,
|
|
char **physPortID)
|
|
{
|
|
/* this actually should never be called, and is just here to
|
|
* satisfy the linker.
|
|
*/
|
|
*physPortID = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virNetDevGetPhysPortName(const char *ifname G_GNUC_UNUSED,
|
|
char **physPortName)
|
|
{
|
|
/* this actually should never be called, and is just here to
|
|
* satisfy the linker.
|
|
*/
|
|
*physPortName = NULL;
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
virNetDevGetVirtualFunctions(const char *pfname G_GNUC_UNUSED,
|
|
char ***vfname G_GNUC_UNUSED,
|
|
virPCIDeviceAddress ***virt_fns G_GNUC_UNUSED,
|
|
size_t *n_vfname G_GNUC_UNUSED,
|
|
unsigned int *max_vfs G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get virtual functions on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevIsVirtualFunction(const char *ifname G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to check virtual function status on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevGetVirtualFunctionIndex(const char *pfname G_GNUC_UNUSED,
|
|
const char *vfname G_GNUC_UNUSED,
|
|
int *vf_index G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get virtual function index on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevGetPhysicalFunction(const char *ifname G_GNUC_UNUSED,
|
|
char **pfname G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get physical function status on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevPFGetVF(const char *pfname G_GNUC_UNUSED,
|
|
int vf G_GNUC_UNUSED,
|
|
char **vfname G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get virtual function name on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevGetVirtualFunctionInfo(const char *vfname G_GNUC_UNUSED,
|
|
char **pfname G_GNUC_UNUSED,
|
|
int *vf G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get virtual function info on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
virNetDevSysfsFile(char **pf_sysfs_device_link G_GNUC_UNUSED,
|
|
const char *ifname G_GNUC_UNUSED,
|
|
const char *file G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get sysfs info on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif /* !__linux__ */
|
|
#if defined(WITH_LIBNL)
|
|
|
|
|
|
static virMacAddr zeroMAC = { .addr = { 0, 0, 0, 0, 0, 0 } };
|
|
|
|
/* if a net driver doesn't allow setting MAC to all 0, try setting
|
|
* to this (the only bit that is set is the "locally administered" bit")
|
|
*/
|
|
static virMacAddr altZeroMAC = { .addr = { 2, 0, 0, 0, 0, 0 } };
|
|
|
|
|
|
static struct nla_policy ifla_vf_policy[IFLA_VF_MAX+1] = {
|
|
[IFLA_VF_MAC] = { .type = NLA_UNSPEC,
|
|
.maxlen = sizeof(struct ifla_vf_mac) },
|
|
[IFLA_VF_VLAN] = { .type = NLA_UNSPEC,
|
|
.maxlen = sizeof(struct ifla_vf_vlan) },
|
|
[IFLA_VF_STATS] = { .type = NLA_NESTED },
|
|
};
|
|
|
|
|
|
static struct nla_policy ifla_vfstats_policy[IFLA_VF_STATS_MAX+1] = {
|
|
[IFLA_VF_STATS_RX_PACKETS] = { .type = NLA_U64 },
|
|
[IFLA_VF_STATS_TX_PACKETS] = { .type = NLA_U64 },
|
|
[IFLA_VF_STATS_RX_BYTES] = { .type = NLA_U64 },
|
|
[IFLA_VF_STATS_TX_BYTES] = { .type = NLA_U64 },
|
|
[IFLA_VF_STATS_BROADCAST] = { .type = NLA_U64 },
|
|
[IFLA_VF_STATS_MULTICAST] = { .type = NLA_U64 },
|
|
};
|
|
|
|
|
|
static int
|
|
virNetDevSetVfConfig(const char *ifname, int vf,
|
|
const virMacAddr *macaddr, int vlanid,
|
|
bool *allowRetry)
|
|
{
|
|
int rc = -1;
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
g_autofree struct nlmsghdr *resp = NULL;
|
|
struct nlmsgerr *err;
|
|
unsigned int recvbuflen = 0;
|
|
struct nl_msg *nl_msg;
|
|
struct nlattr *vfinfolist, *vfinfo;
|
|
struct ifinfomsg ifinfo = {
|
|
.ifi_family = AF_UNSPEC,
|
|
.ifi_index = -1,
|
|
};
|
|
|
|
if (!macaddr && vlanid < 0)
|
|
return -1;
|
|
|
|
nl_msg = virNetlinkMsgNew(RTM_SETLINK, NLM_F_REQUEST);
|
|
|
|
if (nlmsg_append(nl_msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO) < 0)
|
|
goto buffer_too_small;
|
|
|
|
if (ifname &&
|
|
nla_put(nl_msg, IFLA_IFNAME, strlen(ifname)+1, ifname) < 0)
|
|
goto buffer_too_small;
|
|
|
|
|
|
if (!(vfinfolist = nla_nest_start(nl_msg, IFLA_VFINFO_LIST)))
|
|
goto buffer_too_small;
|
|
|
|
if (!(vfinfo = nla_nest_start(nl_msg, IFLA_VF_INFO)))
|
|
goto buffer_too_small;
|
|
|
|
if (macaddr) {
|
|
struct ifla_vf_mac ifla_vf_mac = {
|
|
.vf = vf,
|
|
.mac = { 0, },
|
|
};
|
|
|
|
virMacAddrGetRaw(macaddr, ifla_vf_mac.mac);
|
|
|
|
if (nla_put(nl_msg, IFLA_VF_MAC, sizeof(ifla_vf_mac),
|
|
&ifla_vf_mac) < 0)
|
|
goto buffer_too_small;
|
|
}
|
|
|
|
if (vlanid >= 0) {
|
|
struct ifla_vf_vlan ifla_vf_vlan = {
|
|
.vf = vf,
|
|
.vlan = vlanid,
|
|
.qos = 0,
|
|
};
|
|
|
|
if (nla_put(nl_msg, IFLA_VF_VLAN, sizeof(ifla_vf_vlan),
|
|
&ifla_vf_vlan) < 0)
|
|
goto buffer_too_small;
|
|
}
|
|
|
|
nla_nest_end(nl_msg, vfinfo);
|
|
nla_nest_end(nl_msg, vfinfolist);
|
|
|
|
if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, 0, 0,
|
|
NETLINK_ROUTE, 0) < 0)
|
|
goto cleanup;
|
|
|
|
if (recvbuflen < NLMSG_LENGTH(0) || resp == NULL)
|
|
goto malformed_resp;
|
|
|
|
switch (resp->nlmsg_type) {
|
|
case NLMSG_ERROR:
|
|
err = (struct nlmsgerr *)NLMSG_DATA(resp);
|
|
if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
|
|
goto malformed_resp;
|
|
|
|
/* if allowRetry is true and the error was EINVAL, then
|
|
* silently return a failure so the caller can retry with a
|
|
* different MAC address
|
|
*/
|
|
if (err->error == -EINVAL && *allowRetry &&
|
|
macaddr && !virMacAddrCmp(macaddr, &zeroMAC)) {
|
|
goto cleanup;
|
|
} else if (err->error) {
|
|
/* other errors are permanent */
|
|
virReportSystemError(-err->error,
|
|
_("Cannot set interface MAC/vlanid to %s/%d "
|
|
"for ifname %s vf %d"),
|
|
(macaddr
|
|
? virMacAddrFormat(macaddr, macstr)
|
|
: "(unchanged)"),
|
|
vlanid,
|
|
ifname ? ifname : "(unspecified)",
|
|
vf);
|
|
*allowRetry = false; /* no use retrying */
|
|
goto cleanup;
|
|
}
|
|
break;
|
|
|
|
case NLMSG_DONE:
|
|
break;
|
|
|
|
default:
|
|
goto malformed_resp;
|
|
}
|
|
|
|
rc = 0;
|
|
cleanup:
|
|
VIR_DEBUG("RTM_SETLINK %s vf %d MAC=%s vlanid=%d - %s",
|
|
ifname, vf,
|
|
macaddr ? virMacAddrFormat(macaddr, macstr) : "(unchanged)",
|
|
vlanid, rc < 0 ? "Fail" : "Success");
|
|
|
|
nlmsg_free(nl_msg);
|
|
return rc;
|
|
|
|
malformed_resp:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed netlink response message"));
|
|
goto cleanup;
|
|
|
|
buffer_too_small:
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("allocated netlink buffer is too small"));
|
|
goto cleanup;
|
|
}
|
|
|
|
/**
|
|
* virNetDevParseVfInfo:
|
|
* Get the VF interface information from kernel by netlink, To make netlink
|
|
* parsing logic easy to maintain, extending this function to get some new
|
|
* data is better than add a new function.
|
|
*/
|
|
static int
|
|
virNetDevParseVfInfo(struct nlattr **tb, int32_t vf, virMacAddr *mac,
|
|
int *vlanid, virDomainInterfaceStatsPtr stats)
|
|
{
|
|
int rc = -1;
|
|
struct ifla_vf_mac *vf_mac;
|
|
struct ifla_vf_vlan *vf_vlan;
|
|
struct nlattr *tb_vf_info = {NULL, };
|
|
struct nlattr *tb_vf[IFLA_VF_MAX+1];
|
|
struct nlattr *tb_vf_stats[IFLA_VF_STATS_MAX+1];
|
|
int rem;
|
|
|
|
if (!tb[IFLA_VFINFO_LIST]) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("missing IFLA_VF_INFO in netlink response"));
|
|
return rc;
|
|
}
|
|
|
|
nla_for_each_nested(tb_vf_info, tb[IFLA_VFINFO_LIST], rem) {
|
|
if (nla_type(tb_vf_info) != IFLA_VF_INFO)
|
|
continue;
|
|
|
|
if (nla_parse_nested(tb_vf, IFLA_VF_MAX, tb_vf_info,
|
|
ifla_vf_policy)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("error parsing IFLA_VF_INFO"));
|
|
return rc;
|
|
}
|
|
|
|
if (mac && tb_vf[IFLA_VF_MAC]) {
|
|
vf_mac = RTA_DATA(tb_vf[IFLA_VF_MAC]);
|
|
if (vf_mac && vf_mac->vf == vf) {
|
|
virMacAddrSetRaw(mac, vf_mac->mac);
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
if (vlanid && tb_vf[IFLA_VF_VLAN]) {
|
|
vf_vlan = RTA_DATA(tb_vf[IFLA_VF_VLAN]);
|
|
if (vf_vlan && vf_vlan->vf == vf) {
|
|
*vlanid = vf_vlan->vlan;
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
if (stats && tb_vf[IFLA_VF_STATS] && tb_vf[IFLA_VF_MAC]) {
|
|
vf_mac = RTA_DATA(tb_vf[IFLA_VF_MAC]);
|
|
if (vf_mac && vf_mac->vf == vf) {
|
|
rc = nla_parse_nested(tb_vf_stats, IFLA_VF_STATS_MAX,
|
|
tb_vf[IFLA_VF_STATS],
|
|
ifla_vfstats_policy);
|
|
if (rc < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("error parsing IFLA_VF_STATS"));
|
|
return rc;
|
|
}
|
|
|
|
stats->rx_bytes = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_RX_BYTES]);
|
|
stats->tx_bytes = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_TX_BYTES]);
|
|
stats->rx_packets = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_RX_PACKETS]);
|
|
stats->tx_packets = nla_get_u64(tb_vf_stats[IFLA_VF_STATS_TX_PACKETS]);
|
|
rc = 0;
|
|
}
|
|
}
|
|
|
|
if (rc == 0)
|
|
break;
|
|
}
|
|
if (rc < 0)
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("couldn't find IFLA_VF_INFO for VF %d "
|
|
"in netlink response"), vf);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
virNetDevGetVfConfig(const char *ifname, int vf, virMacAddr *mac,
|
|
int *vlanid)
|
|
{
|
|
g_autofree void *nlData = NULL;
|
|
struct nlattr *tb[IFLA_MAX + 1] = {NULL, };
|
|
int ifindex = -1;
|
|
|
|
if (virNetlinkDumpLink(ifname, ifindex, &nlData, tb, 0, 0) < 0)
|
|
return -1;
|
|
|
|
return virNetDevParseVfInfo(tb, vf, mac, vlanid, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevVFInterfaceStats:
|
|
* @vfAddr: PCI address of a VF
|
|
* @stats: returns stats of the VF interface
|
|
*
|
|
* Get the VF interface from kernel by netlink.
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
virNetDevVFInterfaceStats(virPCIDeviceAddress *vfAddr,
|
|
virDomainInterfaceStatsPtr stats)
|
|
{
|
|
g_autofree void *nlData = NULL;
|
|
struct nlattr *tb[IFLA_MAX + 1] = {NULL, };
|
|
g_autofree char *vfSysfsPath = NULL;
|
|
g_autofree char *pfname = NULL;
|
|
int vf = -1;
|
|
|
|
if (virPCIDeviceAddressGetSysfsFile(vfAddr, &vfSysfsPath) < 0)
|
|
return -1;
|
|
|
|
if (!virPCIIsVirtualFunction(vfSysfsPath)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, _("'%s' is not a VF device"), vfSysfsPath);
|
|
return -1;
|
|
}
|
|
|
|
if (virPCIGetVirtualFunctionInfo(vfSysfsPath, -1, &pfname, &vf) < 0)
|
|
return -1;
|
|
|
|
if (virNetlinkDumpLink(pfname, -1, &nlData, tb, 0, 0) < 0)
|
|
return -1;
|
|
|
|
return virNetDevParseVfInfo(tb, vf, NULL, NULL, stats);
|
|
}
|
|
|
|
|
|
# define VIR_NETDEV_KEYNAME_ADMIN_MAC "adminMac"
|
|
# define VIR_NETDEV_KEYNAME_VLAN_TAG "vlanTag"
|
|
# define VIR_NETDEV_KEYNAME_MAC "mac"
|
|
|
|
/**
|
|
* virNetDevSaveNetConfig:
|
|
* @linkdev: name of the interface
|
|
* @vf: vf index if linkdev is a pf
|
|
* @stateDir: directory to store old net config
|
|
* @saveVlan: false if we shouldn't attempt to save vlan tag info
|
|
* (eg for interfaces using 802.1Qbg, since it handles
|
|
* vlan tags internally)
|
|
*
|
|
* Save current MAC address and (if linkdev itself is a VF, or if @vf
|
|
* >= 0) the "admin MAC address" and vlan tag the device described by
|
|
* @linkdev:@vf to @stateDir. (the "admin MAC address" is stored in
|
|
* the PF, and is what the VF MAC will be initialized to the next time
|
|
* its driver is reloaded (either on host or guest).
|
|
*
|
|
* File Format:
|
|
*
|
|
* The file is in json format and will contain 1 or more of the
|
|
* following values:
|
|
*
|
|
* "mac" - VF MAC address (or missing if VF has no host net driver)
|
|
* "vlanTag" - a single vlan tag id
|
|
* "adminMac" - admin MAC address (stored in the PF)
|
|
*
|
|
* For example:
|
|
*
|
|
* {"mac": "9A:11:22:33:44:55",
|
|
* "vlanTag": "42",
|
|
* "adminMac": "00:00:00:00:00:00"
|
|
* }
|
|
*
|
|
* File Name:
|
|
*
|
|
* If the device is a VF and we're allowed to save vlan tag info, the
|
|
* file will be named ${pfDevName_vf#{vf} (e.g. "enp2s0f0_vf5") and
|
|
* will contain at least "adminMac" and "vlanTag" (if the device was bound
|
|
* to a net driver on the host prior to use, it will also have "mac"..
|
|
* If the device isn't a VF, or we're not allowed to save vlan tag
|
|
* info, the file will be named ${linkdev} (e.g. "enp3s0f0") and will
|
|
* contain just linkdev's MAC address.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*
|
|
*/
|
|
int
|
|
virNetDevSaveNetConfig(const char *linkdev, int vf,
|
|
const char *stateDir,
|
|
bool saveVlan)
|
|
{
|
|
const char *pfDevName = NULL;
|
|
g_autofree char *pfDevOrig = NULL;
|
|
g_autofree char *vfDevOrig = NULL;
|
|
virMacAddr oldMAC;
|
|
char MACStr[VIR_MAC_STRING_BUFLEN];
|
|
int oldVlanTag = -1;
|
|
g_autofree char *filePath = NULL;
|
|
g_autofree char *fileStr = NULL;
|
|
g_autoptr(virJSONValue) configJSON = NULL;
|
|
|
|
if (vf >= 0) {
|
|
/* linkdev is the PF */
|
|
pfDevName = linkdev;
|
|
|
|
/* linkdev should get the VF's netdev name (or NULL if none) */
|
|
if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
|
|
return -1;
|
|
|
|
linkdev = vfDevOrig;
|
|
saveVlan = true;
|
|
|
|
} else if (virNetDevIsVirtualFunction(linkdev) == 1) {
|
|
/* when vf is -1, linkdev might be a standard netdevice (not
|
|
* SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
|
|
* it to PF + VFname
|
|
*/
|
|
|
|
if (virNetDevGetVirtualFunctionInfo(linkdev, &pfDevOrig, &vf) < 0)
|
|
return -1;
|
|
pfDevName = pfDevOrig;
|
|
}
|
|
|
|
if (pfDevName) {
|
|
bool pfIsOnline;
|
|
|
|
/* Assure that PF is online before trying to use it to set
|
|
* anything up for this VF. It *should* be online already,
|
|
* but if it isn't online the changes made to the VF via the
|
|
* PF won't take effect, yet there will be no error
|
|
* reported. In the case that the PF isn't online, we need to
|
|
* fail and report the error, rather than automatically
|
|
* setting it online, since setting an unconfigured interface
|
|
* online automatically turns on IPv6 autoconfig, which may
|
|
* not be what the admin expects, so we require them to
|
|
* explicitly enable the PF in the host system network config.
|
|
*/
|
|
if (virNetDevGetOnline(pfDevName, &pfIsOnline) < 0)
|
|
return -1;
|
|
|
|
if (!pfIsOnline) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to configure VF %d of PF '%s' "
|
|
"because the PF is not online. Please "
|
|
"change host network config to put the "
|
|
"PF online."),
|
|
vf, pfDevName);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
configJSON = virJSONValueNewObject();
|
|
|
|
/* if there is a PF, it's now in pfDevName, and linkdev is either
|
|
* the VF's name, or NULL (if the VF isn't bound to a net driver
|
|
* on the host)
|
|
*/
|
|
|
|
if (pfDevName && saveVlan) {
|
|
filePath = g_strdup_printf("%s/%s_vf%d", stateDir, pfDevName, vf);
|
|
|
|
/* get admin MAC and vlan tag */
|
|
if (virNetDevGetVfConfig(pfDevName, vf, &oldMAC, &oldVlanTag) < 0)
|
|
return -1;
|
|
|
|
if (virJSONValueObjectAppendString(configJSON,
|
|
VIR_NETDEV_KEYNAME_ADMIN_MAC,
|
|
virMacAddrFormat(&oldMAC, MACStr)) < 0 ||
|
|
virJSONValueObjectAppendNumberInt(configJSON,
|
|
VIR_NETDEV_KEYNAME_VLAN_TAG,
|
|
oldVlanTag) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
filePath = g_strdup_printf("%s/%s", stateDir, linkdev);
|
|
}
|
|
|
|
if (linkdev) {
|
|
if (virNetDevGetMAC(linkdev, &oldMAC) < 0)
|
|
return -1;
|
|
|
|
/* for interfaces with no pfDevName (i.e. not a VF, this will
|
|
* be the only value in the file.
|
|
*/
|
|
if (virJSONValueObjectAppendString(configJSON, VIR_NETDEV_KEYNAME_MAC,
|
|
virMacAddrFormat(&oldMAC, MACStr)) < 0)
|
|
return -1;
|
|
}
|
|
|
|
if (!(fileStr = virJSONValueToString(configJSON, true)))
|
|
return -1;
|
|
|
|
if (virFileWriteStr(filePath, fileStr, O_CREAT|O_TRUNC|O_WRONLY) < 0) {
|
|
virReportSystemError(errno, _("Unable to preserve mac/vlan tag "
|
|
"for device = %s, vf = %d"), linkdev, vf);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevReadNetConfig:
|
|
* @linkdev: name of the interface
|
|
* @vf: vf index if linkdev is a pf
|
|
* @stateDir: directory where net config is stored
|
|
* @adminMAC: returns admin MAC to store in the PF (if this is a VF)
|
|
* @MAC: returns MAC to set on device immediately
|
|
*
|
|
* Read saved MAC address and (if linkdev itself is a VF, or if @vf >=
|
|
* 0) "admin MAC address" and vlan tag of the device described by
|
|
* @linkdev:@vf from a file in @stateDir. (see virNetDevSaveNetConfig
|
|
* for details of file name and format).
|
|
*
|
|
* Returns 0 on success, -1 on failure. It is *NOT* considered failure
|
|
* if no file is found to read. In that case, adminMAC, vlan, and MAC
|
|
* are set to NULL, and success is returned.
|
|
*
|
|
* The caller MUST free adminMAC, vlan, and MAC when it is finished
|
|
* with them (they will be NULL if they weren't found in the file)
|
|
*
|
|
*/
|
|
int
|
|
virNetDevReadNetConfig(const char *linkdev, int vf,
|
|
const char *stateDir,
|
|
virMacAddr **adminMAC,
|
|
virNetDevVlan **vlan,
|
|
virMacAddr **MAC)
|
|
{
|
|
int ret = -1;
|
|
const char *pfDevName = NULL;
|
|
g_autofree char *pfDevOrig = NULL;
|
|
g_autofree char *vfDevOrig = NULL;
|
|
g_autofree char *filePath = NULL;
|
|
g_autofree char *fileStr = NULL;
|
|
g_autoptr(virJSONValue) configJSON = NULL;
|
|
const char *MACStr = NULL;
|
|
const char *adminMACStr = NULL;
|
|
int vlanTag = -1;
|
|
|
|
*adminMAC = NULL;
|
|
*vlan = NULL;
|
|
*MAC = NULL;
|
|
|
|
if (vf >= 0) {
|
|
/* linkdev is the PF */
|
|
pfDevName = linkdev;
|
|
|
|
/* linkdev should get the VF's netdev name (or NULL if none) */
|
|
if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
|
|
goto cleanup;
|
|
|
|
linkdev = vfDevOrig;
|
|
|
|
} else if (virNetDevIsVirtualFunction(linkdev) == 1) {
|
|
/* when vf is -1, linkdev might be a standard netdevice (not
|
|
* SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
|
|
* it to PF + VFname
|
|
*/
|
|
|
|
if (virNetDevGetVirtualFunctionInfo(linkdev, &pfDevOrig, &vf) < 0)
|
|
goto cleanup;
|
|
pfDevName = pfDevOrig;
|
|
}
|
|
|
|
/* if there is a PF, it's now in pfDevName, and linkdev is either
|
|
* the VF's name, or NULL (if the VF isn't bound to a net driver
|
|
* on the host)
|
|
*/
|
|
|
|
if (pfDevName) {
|
|
filePath = g_strdup_printf("%s/%s_vf%d", stateDir, pfDevName, vf);
|
|
|
|
if (linkdev && !virFileExists(filePath)) {
|
|
/* the device may have been stored in a file named for the
|
|
* VF due to saveVlan == false (or an older version of
|
|
* libvirt), so reset filePath and pfDevName so we'll try
|
|
* the other filename.
|
|
*/
|
|
VIR_FREE(filePath);
|
|
pfDevName = NULL;
|
|
}
|
|
}
|
|
|
|
if (!pfDevName)
|
|
filePath = g_strdup_printf("%s/%s", stateDir, linkdev);
|
|
|
|
if (!virFileExists(filePath)) {
|
|
/* having no file to read is not necessarily an error, so we
|
|
* just return success, but with MAC, adminMAC, and vlan set to NULL
|
|
*/
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virFileReadAll(filePath, 128, &fileStr) < 0)
|
|
goto cleanup;
|
|
|
|
if (strchr("0123456789abcdefABCDEF", fileStr[0])) {
|
|
const char *vlanStr = NULL;
|
|
|
|
/* old version of file - just two lines of text. Line 1 is the
|
|
* MAC address (or if line 2 is present, line 1 is adminMAC),
|
|
* and line 2 (if present) is the vlan tag
|
|
*/
|
|
|
|
if ((vlanStr = strchr(fileStr, '\n'))) {
|
|
char *endptr;
|
|
|
|
/* if there are 2 lines, the first is adminMAC */
|
|
adminMACStr = fileStr;
|
|
vlanStr++;
|
|
|
|
if ((virStrToLong_i(vlanStr, &endptr, 10, &vlanTag) < 0) ||
|
|
(endptr && *endptr != '\n' && *endptr != 0)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse vlan tag '%s' from file '%s'"),
|
|
vlanStr, filePath);
|
|
goto cleanup;
|
|
}
|
|
} else {
|
|
/* if there is only one line, it is MAC */
|
|
MACStr = fileStr;
|
|
}
|
|
} else {
|
|
/* if it doesn't start with a hex digit, it is a modern
|
|
* version of the config file - JSON format as described in
|
|
* preamble to virNetDevSaveNetConfig()
|
|
*/
|
|
if (!(configJSON = virJSONValueFromString(fileStr))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("invalid json in net device saved "
|
|
"config file '%s': '%.60s'"),
|
|
filePath, fileStr);
|
|
goto cleanup;
|
|
}
|
|
|
|
MACStr = virJSONValueObjectGetString(configJSON,
|
|
VIR_NETDEV_KEYNAME_MAC);
|
|
adminMACStr = virJSONValueObjectGetString(configJSON,
|
|
VIR_NETDEV_KEYNAME_ADMIN_MAC);
|
|
ignore_value(virJSONValueObjectGetNumberInt(configJSON,
|
|
VIR_NETDEV_KEYNAME_VLAN_TAG,
|
|
&vlanTag));
|
|
|
|
if (!(MACStr || adminMACStr)) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("network device saved config file '%s' "
|
|
"has unexpected contents, missing both "
|
|
"'MAC' and 'adminMAC': '%.60s'"),
|
|
filePath, fileStr);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (MACStr) {
|
|
*MAC = g_new0(virMacAddr, 1);
|
|
|
|
if (virMacAddrParse(MACStr, *MAC) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse MAC address '%s' from file '%s'"),
|
|
MACStr, filePath);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (adminMACStr) {
|
|
*adminMAC = g_new0(virMacAddr, 1);
|
|
|
|
if (virMacAddrParse(adminMACStr, *adminMAC) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("cannot parse MAC address '%s' from file '%s'"),
|
|
adminMACStr, filePath);
|
|
goto cleanup;
|
|
}
|
|
}
|
|
|
|
if (vlanTag != -1) {
|
|
/* construct a simple virNetDevVlan object with a single tag */
|
|
*vlan = g_new0(virNetDevVlan, 1);
|
|
(*vlan)->tag = g_new0(unsigned int, 1);
|
|
(*vlan)->nTags = 1;
|
|
(*vlan)->tag[0] = vlanTag;
|
|
}
|
|
|
|
/* we won't need the file again */
|
|
unlink(filePath);
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0) {
|
|
VIR_FREE(*adminMAC);
|
|
VIR_FREE(*MAC);
|
|
VIR_FREE(*vlan);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevSetNetConfig:
|
|
* @linkdev: name of the interface
|
|
* @vf: vf index if linkdev is a PF
|
|
* @adminMAC: new admin MAC address (will be stored in PF and
|
|
* used for next initialization of VF driver)
|
|
* @vlan: new vlan tag info (or NULL)
|
|
* @MAC: new MAC address to set on the device immediately
|
|
* @setVlan: true to enable setting vlan tag (even if @vlan is NULL,
|
|
* the interface vlan tag will be set to 0).
|
|
*
|
|
*
|
|
* Set new MAC address and (optionally) admin MAC and vlan tag of
|
|
* @linkdev VF# @vf.
|
|
*
|
|
* Returns 0 on success, -1 on failure
|
|
*
|
|
*/
|
|
int
|
|
virNetDevSetNetConfig(const char *linkdev, int vf,
|
|
const virMacAddr *adminMAC,
|
|
const virNetDevVlan *vlan,
|
|
const virMacAddr *MAC,
|
|
bool setVlan)
|
|
{
|
|
char MACStr[VIR_MAC_STRING_BUFLEN];
|
|
const char *pfDevName = NULL;
|
|
g_autofree char *pfDevOrig = NULL;
|
|
g_autofree char *vfDevOrig = NULL;
|
|
int vlanTag = -1;
|
|
g_autoptr(virPCIDevice) vfPCIDevice = NULL;
|
|
|
|
if (vf >= 0) {
|
|
/* linkdev is the PF */
|
|
pfDevName = linkdev;
|
|
|
|
/* linkdev should get the VF's netdev name (or NULL if none) */
|
|
if (virNetDevPFGetVF(pfDevName, vf, &vfDevOrig) < 0)
|
|
return -1;
|
|
|
|
linkdev = vfDevOrig;
|
|
|
|
} else if (virNetDevIsVirtualFunction(linkdev) == 1) {
|
|
/* when vf is -1, linkdev might be a standard netdevice (not
|
|
* SRIOV), or it might be an SRIOV VF. If it's a VF, normalize
|
|
* it to PF + VFname
|
|
*/
|
|
|
|
if (virNetDevGetVirtualFunctionInfo(linkdev, &pfDevOrig, &vf))
|
|
return -1;
|
|
pfDevName = pfDevOrig;
|
|
}
|
|
|
|
|
|
if (!pfDevName) {
|
|
/* if it's not SRIOV, then we can't set the admin MAC address
|
|
* or vlan tag
|
|
*/
|
|
if (adminMAC) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("admin MAC can only be set for SR-IOV VFs, but "
|
|
"%s is not a VF"), linkdev);
|
|
return -1;
|
|
}
|
|
|
|
if (vlan) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
_("vlan can only be set for SR-IOV VFs, but "
|
|
"%s is not a VF"), linkdev);
|
|
return -1;
|
|
}
|
|
|
|
} else {
|
|
if (vlan) {
|
|
if (vlan->nTags != 1 || vlan->trunk) {
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED, "%s",
|
|
_("vlan trunking is not supported "
|
|
"by SR-IOV network devices"));
|
|
return -1;
|
|
}
|
|
|
|
if (!setVlan) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("vlan tag set for interface %s but "
|
|
"caller requested it not be set"));
|
|
return -1;
|
|
}
|
|
|
|
vlanTag = vlan->tag[0];
|
|
|
|
} else if (setVlan) {
|
|
vlanTag = 0; /* assure any existing vlan tag is reset */
|
|
}
|
|
}
|
|
|
|
if (MAC) {
|
|
int setMACrc;
|
|
|
|
if (!linkdev) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("VF %d of PF '%s' is not bound to a net driver, "
|
|
"so its MAC address cannot be set to %s"),
|
|
vf, pfDevName, virMacAddrFormat(MAC, MACStr));
|
|
return -1;
|
|
}
|
|
|
|
setMACrc = virNetDevSetMACInternal(linkdev, MAC, !!pfDevOrig);
|
|
if (setMACrc < 0) {
|
|
bool allowRetry = false;
|
|
int retries = 100;
|
|
|
|
/* if pfDevOrig == NULL, this isn't a VF, so we've failed */
|
|
if (!pfDevOrig ||
|
|
(errno != EADDRNOTAVAIL && errno != EPERM))
|
|
return -1;
|
|
|
|
/* Otherwise this is a VF, and virNetDevSetMAC failed with
|
|
* EADDRNOTAVAIL/EPERM, which could be due to the
|
|
* "administratively set" flag being set in the PF for
|
|
* this VF. When this happens, we can attempt to use an
|
|
* alternate method to set the VF MAC: first set it into
|
|
* the admin MAC for this VF in the PF, then unbind/rebind
|
|
* the VF from its net driver. This causes the VF's MAC to
|
|
* be initialized to whatever was stored in the admin MAC.
|
|
*/
|
|
|
|
if (virNetDevSetVfConfig(pfDevName, vf,
|
|
MAC, vlanTag, &allowRetry) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* admin MAC is set, now we need to construct a virPCIDevice
|
|
* object so we can call virPCIDeviceRebind()
|
|
*/
|
|
if (!(vfPCIDevice = virNetDevGetPCIDevice(linkdev)))
|
|
return -1;
|
|
|
|
/* Rebind the device. This should set the proper MAC address */
|
|
if (virPCIDeviceRebind(vfPCIDevice) < 0)
|
|
return -1;
|
|
|
|
/* Wait until virNetDevGetIndex for the VF netdev returns success.
|
|
* This indicates that the device is ready to be used. If we don't
|
|
* wait, then upcoming operations on the VF may fail.
|
|
*/
|
|
while (retries-- > 0 && !virNetDevExists(linkdev))
|
|
g_usleep(1000);
|
|
}
|
|
|
|
if (pfDevOrig && setMACrc == 0) {
|
|
/* if pfDevOrig is set, it that the caller was *really*
|
|
* only interested in setting the MAC of the VF itself,
|
|
* *not* the admin MAC via the PF. In those cases, the
|
|
* adminMAC was only provided in case we need to set the
|
|
* VF's MAC by temporarily unbinding/rebinding the VF's
|
|
* net driver with the admin MAC set to the desired MAC,
|
|
* and then want to restore the admin MAC to its original
|
|
* setting when we're finished. We would only need to do
|
|
* that if the virNetDevSetMAC() above had failed; since
|
|
* setMACrc == 0, we know it didn't fail and we don't need
|
|
* to set the adminMAC, so we are NULLing it out here to
|
|
* avoid that below.
|
|
|
|
* (NB: since setting the admin MAC sets the
|
|
* "administratively set" flag for the VF in the PF's
|
|
* driver, which prevents any future changes to the VF's
|
|
* MAC address, we want to avoid setting the admin MAC as
|
|
* much as possible.)
|
|
*/
|
|
adminMAC = NULL;
|
|
}
|
|
}
|
|
|
|
if (adminMAC || vlanTag >= 0) {
|
|
/* Set vlanTag and admin MAC using an RTM_SETLINK request sent to
|
|
* PFdevname+VF#, if mac != NULL this will set the "admin MAC" via
|
|
* the PF, *not* the actual VF MAC - the admin MAC only takes
|
|
* effect the next time the VF's driver is initialized (either in
|
|
* guest or host). if there is a vlanTag to set, it will take
|
|
* effect immediately though.
|
|
*/
|
|
bool allowRetry = true;
|
|
|
|
if (virNetDevSetVfConfig(pfDevName, vf,
|
|
adminMAC, vlanTag, &allowRetry) < 0) {
|
|
/* allowRetry will still be true if the failure was due to
|
|
* trying to set the MAC address to all 0. In that case,
|
|
* we can retry with "altZeroMAC", which is just an all-0 MAC
|
|
* with the "locally administered" bit set.
|
|
*/
|
|
if (!allowRetry)
|
|
return -1;
|
|
|
|
allowRetry = false;
|
|
if (virNetDevSetVfConfig(pfDevName, vf,
|
|
&altZeroMAC, vlanTag, &allowRetry) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#else /* defined(WITH_LIBNL) */
|
|
|
|
|
|
int
|
|
virNetDevSaveNetConfig(const char *linkdev G_GNUC_UNUSED,
|
|
int vf G_GNUC_UNUSED,
|
|
const char *stateDir G_GNUC_UNUSED,
|
|
bool saveVlan G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to save net device config on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virNetDevReadNetConfig(const char *linkdev G_GNUC_UNUSED,
|
|
int vf G_GNUC_UNUSED,
|
|
const char *stateDir G_GNUC_UNUSED,
|
|
virMacAddr **adminMAC G_GNUC_UNUSED,
|
|
virNetDevVlan **vlan G_GNUC_UNUSED,
|
|
virMacAddr **MAC G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to read net device config on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virNetDevSetNetConfig(const char *linkdev G_GNUC_UNUSED,
|
|
int vf G_GNUC_UNUSED,
|
|
const virMacAddr *adminMAC G_GNUC_UNUSED,
|
|
const virNetDevVlan *vlan G_GNUC_UNUSED,
|
|
const virMacAddr *MAC G_GNUC_UNUSED,
|
|
bool setVlan G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to set net device config on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
int
|
|
virNetDevVFInterfaceStats(virPCIDeviceAddress *vfAddr G_GNUC_UNUSED,
|
|
virDomainInterfaceStatsPtr stats G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to get VF net device stats on this platform"));
|
|
return -1;
|
|
}
|
|
|
|
|
|
#endif /* defined(WITH_LIBNL) */
|
|
|
|
VIR_ENUM_IMPL(virNetDevIfState,
|
|
VIR_NETDEV_IF_STATE_LAST,
|
|
"" /* value of zero means no state */,
|
|
"unknown", "notpresent",
|
|
"down", "lowerlayerdown",
|
|
"testing", "dormant", "up",
|
|
);
|
|
|
|
VIR_ENUM_IMPL(virNetDevFeature,
|
|
VIR_NET_DEV_FEAT_LAST,
|
|
"rx",
|
|
"tx",
|
|
"sg",
|
|
"tso",
|
|
"gso",
|
|
"gro",
|
|
"lro",
|
|
"rxvlan",
|
|
"txvlan",
|
|
"ntuple",
|
|
"rxhash",
|
|
"rdma",
|
|
"txudptnl",
|
|
"switchdev",
|
|
);
|
|
|
|
#ifdef __linux__
|
|
int
|
|
virNetDevGetLinkInfo(const char *ifname,
|
|
virNetDevIfLink *lnk)
|
|
{
|
|
g_autofree char *path = NULL;
|
|
g_autofree char *buf = NULL;
|
|
char *tmp;
|
|
int tmp_state;
|
|
unsigned int tmp_speed;
|
|
|
|
if (virNetDevSysfsFile(&path, ifname, "operstate") < 0)
|
|
return -1;
|
|
|
|
/* The device may have been removed or moved by the time we got here.
|
|
* Obviously attempting to get LinkInfo on a no longer existing device
|
|
* is useless, so stop processing. If we got here via the udev monitor
|
|
* a remove or move event will follow and we will be able to get valid
|
|
* LinkInfo at that time */
|
|
if (!virFileExists(path)) {
|
|
VIR_INFO("The interface '%s' was removed before we could query it.",
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (virFileReadAll(path, 1024, &buf) < 0) {
|
|
virReportSystemError(errno,
|
|
_("unable to read: %s"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (!(tmp = strchr(buf, '\n'))) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse: %s"),
|
|
buf);
|
|
return -1;
|
|
}
|
|
|
|
*tmp = '\0';
|
|
|
|
/* We shouldn't allow 0 here, because
|
|
* virInterfaceState enum starts from 1. */
|
|
if ((tmp_state = virNetDevIfStateTypeFromString(buf)) <= 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse: %s"),
|
|
buf);
|
|
return -1;
|
|
}
|
|
|
|
lnk->state = tmp_state;
|
|
|
|
/* Shortcut to avoid some kernel issues. If link is not up several drivers
|
|
* report several misleading values. While igb reports 65535, realtek goes
|
|
* with 10. To avoid muddying XML with insane values, don't report link
|
|
* speed if that's the case. */
|
|
if (lnk->state != VIR_NETDEV_IF_STATE_UP) {
|
|
lnk->speed = 0;
|
|
return 0;
|
|
}
|
|
|
|
VIR_FREE(path);
|
|
VIR_FREE(buf);
|
|
|
|
if (virNetDevSysfsFile(&path, ifname, "speed") < 0)
|
|
return -1;
|
|
|
|
if (virFileReadAllQuiet(path, 1024, &buf) < 0) {
|
|
/* Some devices doesn't report speed, in which case we get EINVAL */
|
|
if (errno == EINVAL)
|
|
return 0;
|
|
virReportSystemError(errno,
|
|
_("unable to read: %s"),
|
|
path);
|
|
return -1;
|
|
}
|
|
|
|
if (virStrToLong_ui(buf, &tmp, 10, &tmp_speed) < 0 ||
|
|
*tmp != '\n') {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("Unable to parse: %s"),
|
|
buf);
|
|
return -1;
|
|
}
|
|
|
|
lnk->speed = tmp_speed;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
virNetDevGetLinkInfo(const char *ifname,
|
|
virNetDevIfLink *lnk)
|
|
{
|
|
/* Port me */
|
|
VIR_DEBUG("Getting link info on %s is not implemented on this platform",
|
|
ifname);
|
|
lnk->speed = lnk->state = 0;
|
|
return 0;
|
|
}
|
|
#endif /* defined(__linux__) */
|
|
|
|
|
|
#if defined(SIOCADDMULTI) && defined(WITH_STRUCT_IFREQ) && \
|
|
defined(WITH_STRUCT_IFREQ_IFR_HWADDR)
|
|
/**
|
|
* virNetDevAddMulti:
|
|
* @ifname: interface name to which to add multicast MAC address
|
|
* @macaddr: MAC address
|
|
*
|
|
* This function adds the @macaddr to the multicast list for a given interface
|
|
* @ifname.
|
|
*
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
int virNetDevAddMulti(const char *ifname,
|
|
virMacAddr *macaddr)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
|
|
virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data);
|
|
|
|
if (ioctl(fd, SIOCADDMULTI, &ifr) < 0) {
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
virReportSystemError(errno,
|
|
_("Cannot add multicast MAC %s on '%s' interface"),
|
|
virMacAddrFormat(macaddr, macstr), ifname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int virNetDevAddMulti(const char *ifname G_GNUC_UNUSED,
|
|
virMacAddr *macaddr G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to add address to interface "
|
|
"multicast list on this platform"));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
#if defined(SIOCDELMULTI) && defined(WITH_STRUCT_IFREQ) && \
|
|
defined(WITH_STRUCT_IFREQ_IFR_HWADDR)
|
|
/**
|
|
* virNetDevDelMulti:
|
|
* @ifname: interface name from which to delete the multicast MAC address
|
|
* @macaddr: MAC address
|
|
*
|
|
* This function deletes the @macaddr from the multicast list for a given
|
|
* interface @ifname.
|
|
*
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
int virNetDevDelMulti(const char *ifname,
|
|
virMacAddr *macaddr)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
ifr.ifr_hwaddr.sa_family = AF_UNSPEC;
|
|
virMacAddrGetRaw(macaddr, (unsigned char *)ifr.ifr_hwaddr.sa_data);
|
|
|
|
if (ioctl(fd, SIOCDELMULTI, &ifr) < 0) {
|
|
char macstr[VIR_MAC_STRING_BUFLEN];
|
|
virReportSystemError(errno,
|
|
_("Cannot add multicast MAC %s on '%s' interface"),
|
|
virMacAddrFormat(macaddr, macstr), ifname);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int virNetDevDelMulti(const char *ifname G_GNUC_UNUSED,
|
|
virMacAddr *macaddr G_GNUC_UNUSED)
|
|
{
|
|
virReportSystemError(ENOSYS, "%s",
|
|
_("Unable to delete address from interface "
|
|
"multicast list on this platform"));
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
static int virNetDevParseMcast(char *buf, virNetDevMcastEntry *mcast)
|
|
{
|
|
int ifindex;
|
|
int num;
|
|
char *next;
|
|
char *token;
|
|
char *saveptr;
|
|
char *endptr;
|
|
|
|
for (ifindex = VIR_MCAST_TYPE_INDEX_TOKEN, next = buf; ifindex < VIR_MCAST_TYPE_LAST; ifindex++,
|
|
next = NULL) {
|
|
token = strtok_r(next, VIR_MCAST_TOKEN_DELIMS, &saveptr);
|
|
|
|
if (token == NULL) {
|
|
virReportSystemError(EINVAL,
|
|
_("failed to parse multicast address from '%s'"),
|
|
buf);
|
|
return -1;
|
|
}
|
|
|
|
switch ((virMCastType)ifindex) {
|
|
case VIR_MCAST_TYPE_INDEX_TOKEN:
|
|
if (virStrToLong_i(token, &endptr, 10, &num) < 0) {
|
|
virReportSystemError(EINVAL,
|
|
_("Failed to parse interface index from '%s'"),
|
|
buf);
|
|
return -1;
|
|
|
|
}
|
|
mcast->idx = num;
|
|
break;
|
|
case VIR_MCAST_TYPE_NAME_TOKEN:
|
|
if (virStrcpy(mcast->name, token, VIR_MCAST_NAME_LEN) < 0) {
|
|
virReportSystemError(EINVAL,
|
|
_("Failed to parse network device name from '%s'"),
|
|
buf);
|
|
return -1;
|
|
}
|
|
break;
|
|
case VIR_MCAST_TYPE_USERS_TOKEN:
|
|
if (virStrToLong_i(token, &endptr, 10, &num) < 0) {
|
|
virReportSystemError(EINVAL,
|
|
_("Failed to parse users from '%s'"),
|
|
buf);
|
|
return -1;
|
|
|
|
}
|
|
mcast->users = num;
|
|
break;
|
|
case VIR_MCAST_TYPE_GLOBAL_TOKEN:
|
|
if (virStrToLong_i(token, &endptr, 10, &num) < 0) {
|
|
virReportSystemError(EINVAL,
|
|
_("Failed to parse users from '%s'"),
|
|
buf);
|
|
return -1;
|
|
|
|
}
|
|
mcast->global = num;
|
|
break;
|
|
case VIR_MCAST_TYPE_ADDR_TOKEN:
|
|
if (virMacAddrParseHex((const char*)token,
|
|
&mcast->macaddr) < 0) {
|
|
virReportSystemError(EINVAL,
|
|
_("Failed to parse MAC address from '%s'"),
|
|
buf);
|
|
}
|
|
break;
|
|
|
|
/* coverity[dead_error_begin] */
|
|
case VIR_MCAST_TYPE_LAST:
|
|
break;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void virNetDevMcastListClear(virNetDevMcastList *mcast)
|
|
{
|
|
size_t i;
|
|
|
|
for (i = 0; i < mcast->nentries; i++)
|
|
VIR_FREE(mcast->entries[i]);
|
|
VIR_FREE(mcast->entries);
|
|
mcast->nentries = 0;
|
|
}
|
|
|
|
|
|
static int virNetDevGetMcastList(const char *ifname,
|
|
virNetDevMcastList *mcast)
|
|
{
|
|
char *cur = NULL;
|
|
g_autofree char *buf = NULL;
|
|
char *next = NULL;
|
|
int len;
|
|
g_autoptr(virNetDevMcastEntry) entry = NULL;
|
|
|
|
mcast->entries = NULL;
|
|
mcast->nentries = 0;
|
|
|
|
/* Read entire multicast table into memory */
|
|
if ((len = virFileReadAll(PROC_NET_DEV_MCAST, MAX_MCAST_SIZE, &buf)) <= 0)
|
|
return -1;
|
|
|
|
cur = buf;
|
|
while (cur) {
|
|
if (!entry)
|
|
entry = g_new0(virNetDevMcastEntry, 1);
|
|
|
|
next = strchr(cur, '\n');
|
|
if (next)
|
|
next++;
|
|
if (virNetDevParseMcast(cur, entry))
|
|
return -1;
|
|
|
|
/* Only return global multicast MAC addresses for
|
|
* specified interface */
|
|
if (entry->global && STREQ(ifname, entry->name)) {
|
|
if (VIR_APPEND_ELEMENT(mcast->entries, mcast->nentries, entry))
|
|
return -1;
|
|
} else {
|
|
memset(entry, 0, sizeof(virNetDevMcastEntry));
|
|
}
|
|
cur = next && ((next - buf) < len) ? next : NULL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
VIR_ENUM_IMPL(virNetDevRxFilterMode,
|
|
VIR_NETDEV_RX_FILTER_MODE_LAST,
|
|
"none",
|
|
"normal",
|
|
"all",
|
|
);
|
|
|
|
|
|
static int virNetDevGetMulticastTable(const char *ifname,
|
|
virNetDevRxFilter *filter)
|
|
{
|
|
size_t i;
|
|
int ret = -1;
|
|
virNetDevMcastList mcast;
|
|
filter->multicast.nTable = 0;
|
|
filter->multicast.table = NULL;
|
|
|
|
if (virNetDevGetMcastList(ifname, &mcast) < 0)
|
|
goto cleanup;
|
|
|
|
if (mcast.nentries > 0) {
|
|
filter->multicast.table = g_new0(virMacAddr, mcast.nentries);
|
|
|
|
for (i = 0; i < mcast.nentries; i++) {
|
|
virMacAddrSet(&filter->multicast.table[i],
|
|
&mcast.entries[i]->macaddr);
|
|
}
|
|
|
|
filter->multicast.nTable = mcast.nentries;
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
virNetDevMcastListClear(&mcast);
|
|
return ret;
|
|
}
|
|
|
|
|
|
virNetDevRxFilter *
|
|
virNetDevRxFilterNew(void)
|
|
{
|
|
virNetDevRxFilter *filter;
|
|
|
|
filter = g_new0(virNetDevRxFilter, 1);
|
|
return filter;
|
|
}
|
|
|
|
|
|
void
|
|
virNetDevRxFilterFree(virNetDevRxFilter *filter)
|
|
{
|
|
if (filter) {
|
|
g_free(filter->name);
|
|
g_free(filter->unicast.table);
|
|
g_free(filter->multicast.table);
|
|
g_free(filter->vlan.table);
|
|
g_free(filter);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevGetRxFilter:
|
|
* This function supplies the RX filter list for a given device interface
|
|
*
|
|
* @ifname: Name of the interface
|
|
* @filter: The RX filter list
|
|
*
|
|
* Returns 0 or -1 on failure.
|
|
*/
|
|
int virNetDevGetRxFilter(const char *ifname,
|
|
virNetDevRxFilter **filter)
|
|
{
|
|
int ret = -1;
|
|
bool receive = false;
|
|
virNetDevRxFilter *fil = virNetDevRxFilterNew();
|
|
|
|
if (!fil)
|
|
goto cleanup;
|
|
|
|
if (virNetDevGetMAC(ifname, &fil->mac))
|
|
goto cleanup;
|
|
|
|
if (virNetDevGetMulticastTable(ifname, fil))
|
|
goto cleanup;
|
|
|
|
if (virNetDevGetPromiscuous(ifname, &fil->promiscuous))
|
|
goto cleanup;
|
|
|
|
if (virNetDevGetRcvAllMulti(ifname, &receive))
|
|
goto cleanup;
|
|
|
|
if (receive) {
|
|
fil->multicast.mode = VIR_NETDEV_RX_FILTER_MODE_ALL;
|
|
} else {
|
|
if (virNetDevGetRcvMulti(ifname, &receive))
|
|
goto cleanup;
|
|
|
|
if (receive)
|
|
fil->multicast.mode = VIR_NETDEV_RX_FILTER_MODE_NORMAL;
|
|
else
|
|
fil->multicast.mode = VIR_NETDEV_RX_FILTER_MODE_NONE;
|
|
}
|
|
|
|
ret = 0;
|
|
cleanup:
|
|
if (ret < 0) {
|
|
virNetDevRxFilterFree(fil);
|
|
fil = NULL;
|
|
}
|
|
|
|
*filter = fil;
|
|
return ret;
|
|
}
|
|
|
|
#if defined(SIOCETHTOOL) && defined(WITH_STRUCT_IFREQ)
|
|
|
|
/**
|
|
* virNetDevRDMAFeature
|
|
* This function checks for the availability of RDMA feature
|
|
* and add it to bitmap
|
|
*
|
|
* @ifname: name of the interface
|
|
* @out: add RDMA feature if exist to bitmap
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
virNetDevRDMAFeature(const char *ifname,
|
|
virBitmap **out)
|
|
{
|
|
g_autofree char *eth_devpath = NULL;
|
|
g_autofree char *eth_res_buf = NULL;
|
|
g_autoptr(DIR) dirp = NULL;
|
|
struct dirent *dp;
|
|
|
|
if (!virFileExists(SYSFS_INFINIBAND_DIR))
|
|
return 0;
|
|
|
|
if (virDirOpen(&dirp, SYSFS_INFINIBAND_DIR) < 0)
|
|
return -1;
|
|
|
|
eth_devpath = g_strdup_printf(SYSFS_NET_DIR "%s/device/resource", ifname);
|
|
|
|
/* If /sys/class/net/<ifname>/device/resource doesn't exist it is not a PCI
|
|
* device and therefore it will not have RDMA. */
|
|
if (!virFileExists(eth_devpath)) {
|
|
return 0;
|
|
}
|
|
|
|
if (virFileReadAll(eth_devpath, RESOURCE_FILE_LEN, ð_res_buf) < 0)
|
|
return -1;
|
|
|
|
while (virDirRead(dirp, &dp, SYSFS_INFINIBAND_DIR) > 0) {
|
|
g_autofree char *ib_res_buf = NULL;
|
|
g_autofree char *ib_devpath = g_strdup_printf(SYSFS_INFINIBAND_DIR "%s/device/resource",
|
|
dp->d_name);
|
|
|
|
if (virFileReadAll(ib_devpath, RESOURCE_FILE_LEN, &ib_res_buf) > 0 &&
|
|
STREQ(eth_res_buf, ib_res_buf)) {
|
|
ignore_value(virBitmapSetBit(*out, VIR_NET_DEV_FEAT_RDMA));
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevSendEthtoolIoctl
|
|
* This function sends ethtool ioctl request
|
|
*
|
|
* @fd: socket to operate on
|
|
* @ifr: struct ifreq with the command
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
virNetDevSendEthtoolIoctl(const char *ifname, int fd, struct ifreq *ifr)
|
|
{
|
|
int ret = -1;
|
|
|
|
ret = ioctl(fd, SIOCETHTOOL, ifr);
|
|
if (ret != 0) {
|
|
switch (errno) {
|
|
case EINVAL: /* kernel doesn't support SIOCETHTOOL */
|
|
VIR_DEBUG("ethtool ioctl: invalid request on %s", ifname);
|
|
break;
|
|
case EOPNOTSUPP: /* kernel doesn't support specific feature */
|
|
VIR_DEBUG("ethtool ioctl: request not supported on %s", ifname);
|
|
break;
|
|
default:
|
|
virReportSystemError(errno, _("ethtool ioctl error on %s"), ifname);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct virNetDevEthtoolFeatureCmd {
|
|
const int cmd;
|
|
const virNetDevFeature feat;
|
|
};
|
|
|
|
|
|
/**
|
|
* virNetDevFeatureAvailable
|
|
* This function checks for the availability of a network device feature
|
|
*
|
|
* @fd: socket to operate on
|
|
* @ifr: struct ifreq with the command
|
|
* @cmd: reference to an ethtool command structure
|
|
*
|
|
* Returns true if the feature is available, false otherwise.
|
|
*/
|
|
static bool
|
|
virNetDevFeatureAvailable(const char *ifname, int fd, struct ifreq *ifr, struct ethtool_value *cmd)
|
|
{
|
|
ifr->ifr_data = (void*)cmd;
|
|
if (virNetDevSendEthtoolIoctl(ifname, fd, ifr) == 0 &&
|
|
cmd->data > 0)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
|
|
static void
|
|
virNetDevGetEthtoolFeatures(const char *ifname,
|
|
virBitmap *bitmap,
|
|
int fd,
|
|
struct ifreq *ifr)
|
|
{
|
|
size_t i;
|
|
struct ethtool_value cmd = { 0 };
|
|
|
|
/* legacy ethtool getters */
|
|
struct virNetDevEthtoolFeatureCmd ethtool_cmds[] = {
|
|
{ETHTOOL_GRXCSUM, VIR_NET_DEV_FEAT_GRXCSUM},
|
|
{ETHTOOL_GTXCSUM, VIR_NET_DEV_FEAT_GTXCSUM},
|
|
{ETHTOOL_GSG, VIR_NET_DEV_FEAT_GSG},
|
|
{ETHTOOL_GTSO, VIR_NET_DEV_FEAT_GTSO},
|
|
# if WITH_DECL_ETHTOOL_GGSO
|
|
{ETHTOOL_GGSO, VIR_NET_DEV_FEAT_GGSO},
|
|
# endif
|
|
# if WITH_DECL_ETHTOOL_GGRO
|
|
{ETHTOOL_GGRO, VIR_NET_DEV_FEAT_GGRO},
|
|
# endif
|
|
};
|
|
|
|
# if WITH_DECL_ETHTOOL_GFLAGS
|
|
/* ethtool masks */
|
|
struct virNetDevEthtoolFeatureCmd flags[] = {
|
|
# if WITH_DECL_ETH_FLAG_LRO
|
|
{ETH_FLAG_LRO, VIR_NET_DEV_FEAT_LRO},
|
|
# endif
|
|
# if WITH_DECL_ETH_FLAG_TXVLAN
|
|
{ETH_FLAG_RXVLAN, VIR_NET_DEV_FEAT_RXVLAN},
|
|
{ETH_FLAG_TXVLAN, VIR_NET_DEV_FEAT_TXVLAN},
|
|
# endif
|
|
# if WITH_DECL_ETH_FLAG_NTUBLE
|
|
{ETH_FLAG_NTUPLE, VIR_NET_DEV_FEAT_NTUPLE},
|
|
# endif
|
|
# if WITH_DECL_ETH_FLAG_RXHASH
|
|
{ETH_FLAG_RXHASH, VIR_NET_DEV_FEAT_RXHASH},
|
|
# endif
|
|
};
|
|
# endif
|
|
|
|
for (i = 0; i < G_N_ELEMENTS(ethtool_cmds); i++) {
|
|
cmd.cmd = ethtool_cmds[i].cmd;
|
|
if (virNetDevFeatureAvailable(ifname, fd, ifr, &cmd))
|
|
ignore_value(virBitmapSetBit(bitmap, ethtool_cmds[i].feat));
|
|
}
|
|
|
|
# if WITH_DECL_ETHTOOL_GFLAGS
|
|
cmd.cmd = ETHTOOL_GFLAGS;
|
|
if (virNetDevFeatureAvailable(ifname, fd, ifr, &cmd)) {
|
|
for (i = 0; i < G_N_ELEMENTS(flags); i++) {
|
|
if (cmd.data & flags[i].cmd)
|
|
ignore_value(virBitmapSetBit(bitmap, flags[i].feat));
|
|
}
|
|
}
|
|
# endif
|
|
}
|
|
|
|
|
|
# if defined(WITH_LIBNL) && WITH_DECL_DEVLINK_CMD_ESWITCH_GET
|
|
|
|
/**
|
|
* virNetDevGetFamilyId:
|
|
* This function supplies the devlink family id
|
|
*
|
|
* @family_name: the name of the family to query
|
|
* @family_id: family ID
|
|
*
|
|
* Returns: 0 if no family was found,
|
|
* 1 if family was found (@family_id is set),
|
|
* -1 otherwise
|
|
*/
|
|
static int
|
|
virNetDevGetFamilyId(const char *family_name,
|
|
uint32_t *family_id)
|
|
{
|
|
struct nl_msg *nl_msg = NULL;
|
|
g_autofree struct nlmsghdr *resp = NULL;
|
|
struct genlmsghdr gmsgh = {
|
|
.cmd = CTRL_CMD_GETFAMILY,
|
|
.version = DEVLINK_GENL_VERSION,
|
|
.reserved = 0,
|
|
};
|
|
struct nlattr *tb[CTRL_ATTR_MAX + 1] = {NULL, };
|
|
unsigned int recvbuflen;
|
|
int ret = -1;
|
|
|
|
nl_msg = virNetlinkMsgNew(GENL_ID_CTRL, NLM_F_REQUEST | NLM_F_ACK);
|
|
|
|
if (nlmsg_append(nl_msg, &gmsgh, sizeof(gmsgh), NLMSG_ALIGNTO) < 0)
|
|
goto cleanup;
|
|
|
|
if (nla_put_string(nl_msg, CTRL_ATTR_FAMILY_NAME, family_name) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("allocated netlink buffer is too small"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, 0, 0, NETLINK_GENERIC, 0) < 0)
|
|
goto cleanup;
|
|
|
|
if (nlmsg_parse(resp, sizeof(struct nlmsghdr), tb, CTRL_ATTR_MAX, NULL) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed netlink response message"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (tb[CTRL_ATTR_FAMILY_ID] == NULL) {
|
|
ret = 0;
|
|
goto cleanup;
|
|
}
|
|
|
|
*family_id = *(uint32_t *)RTA_DATA(tb[CTRL_ATTR_FAMILY_ID]);
|
|
ret = 1;
|
|
|
|
cleanup:
|
|
nlmsg_free(nl_msg);
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevSwitchdevFeature
|
|
* This function checks for the availability of Switchdev feature
|
|
* and add it to bitmap
|
|
*
|
|
* @ifname: name of the interface
|
|
* @out: add Switchdev feature if exist to bitmap
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
static int
|
|
virNetDevSwitchdevFeature(const char *ifname,
|
|
virBitmap **out)
|
|
{
|
|
struct nl_msg *nl_msg = NULL;
|
|
g_autofree struct nlmsghdr *resp = NULL;
|
|
unsigned int recvbuflen;
|
|
struct nlattr *tb[DEVLINK_ATTR_MAX + 1] = {NULL, };
|
|
g_autoptr(virPCIDevice) pci_device_ptr = NULL;
|
|
struct genlmsghdr gmsgh = {
|
|
.cmd = DEVLINK_CMD_ESWITCH_GET,
|
|
.version = DEVLINK_GENL_VERSION,
|
|
.reserved = 0,
|
|
};
|
|
const char *pci_name;
|
|
g_autofree char *pfname = NULL;
|
|
int is_vf = -1;
|
|
int ret = -1;
|
|
uint32_t family_id;
|
|
int rv;
|
|
|
|
if ((is_vf = virNetDevIsVirtualFunction(ifname)) < 0)
|
|
return ret;
|
|
|
|
if (is_vf == 1 && virNetDevGetPhysicalFunction(ifname, &pfname) < 0)
|
|
return ret;
|
|
|
|
pci_device_ptr = pfname ? virNetDevGetPCIDevice(pfname) :
|
|
virNetDevGetPCIDevice(ifname);
|
|
/* No PCI device, then no feature bit to check/add */
|
|
if (pci_device_ptr == NULL)
|
|
return 0;
|
|
|
|
if ((rv = virNetDevGetFamilyId(DEVLINK_GENL_NAME, &family_id)) <= 0)
|
|
return rv;
|
|
|
|
nl_msg = virNetlinkMsgNew(family_id, NLM_F_REQUEST | NLM_F_ACK);
|
|
|
|
if (nlmsg_append(nl_msg, &gmsgh, sizeof(gmsgh), NLMSG_ALIGNTO) < 0)
|
|
goto cleanup;
|
|
|
|
pci_name = virPCIDeviceGetName(pci_device_ptr);
|
|
|
|
if (nla_put(nl_msg, DEVLINK_ATTR_BUS_NAME, strlen("pci")+1, "pci") < 0 ||
|
|
nla_put(nl_msg, DEVLINK_ATTR_DEV_NAME, strlen(pci_name)+1, pci_name) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("allocated netlink buffer is too small"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (virNetlinkCommand(nl_msg, &resp, &recvbuflen, 0, 0, NETLINK_GENERIC, 0) < 0)
|
|
goto cleanup;
|
|
|
|
if (nlmsg_parse(resp, sizeof(struct genlmsghdr), tb, DEVLINK_ATTR_MAX, NULL) < 0) {
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
_("malformed netlink response message"));
|
|
goto cleanup;
|
|
}
|
|
|
|
if (tb[DEVLINK_ATTR_ESWITCH_MODE] &&
|
|
*(int *)RTA_DATA(tb[DEVLINK_ATTR_ESWITCH_MODE]) == DEVLINK_ESWITCH_MODE_SWITCHDEV) {
|
|
ignore_value(virBitmapSetBit(*out, VIR_NET_DEV_FEAT_SWITCHDEV));
|
|
}
|
|
|
|
ret = 0;
|
|
|
|
cleanup:
|
|
nlmsg_free(nl_msg);
|
|
return ret;
|
|
}
|
|
# else
|
|
static int
|
|
virNetDevSwitchdevFeature(const char *ifname G_GNUC_UNUSED,
|
|
virBitmap **out G_GNUC_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
# endif
|
|
|
|
|
|
# if WITH_DECL_ETHTOOL_GFEATURES
|
|
/**
|
|
* virNetDevGFeatureAvailable
|
|
* This function checks for the availability of a network device gfeature
|
|
*
|
|
* @fd: socket to operate on
|
|
* @ifr: struct ifreq with the command
|
|
* @cmd: reference to an ethtool command structure
|
|
*
|
|
* Returns true if the feature is available, false otherwise.
|
|
*/
|
|
static bool
|
|
virNetDevGFeatureAvailable(const char *ifname,
|
|
int fd,
|
|
struct ifreq *ifr,
|
|
struct ethtool_gfeatures *cmd)
|
|
{
|
|
ifr->ifr_data = (void*)cmd;
|
|
if (virNetDevSendEthtoolIoctl(ifname, fd, ifr) == 0)
|
|
return !!FEATURE_BIT_IS_SET(cmd->features, TX_UDP_TNL, active);
|
|
return false;
|
|
}
|
|
|
|
|
|
static int
|
|
virNetDevGetEthtoolGFeatures(const char *ifname,
|
|
virBitmap *bitmap,
|
|
int fd,
|
|
struct ifreq *ifr)
|
|
{
|
|
g_autofree struct ethtool_gfeatures *g_cmd = NULL;
|
|
|
|
g_cmd = g_malloc0(sizeof(struct ethtool_gfeatures) +
|
|
sizeof(struct ethtool_get_features_block) * GFEATURES_SIZE);
|
|
|
|
g_cmd->cmd = ETHTOOL_GFEATURES;
|
|
g_cmd->size = GFEATURES_SIZE;
|
|
if (virNetDevGFeatureAvailable(ifname, fd, ifr, g_cmd))
|
|
ignore_value(virBitmapSetBit(bitmap, VIR_NET_DEV_FEAT_TXUDPTNL));
|
|
return 0;
|
|
}
|
|
# else
|
|
static int
|
|
virNetDevGetEthtoolGFeatures(const char *ifname G_GNUC_UNUSED,
|
|
virBitmap *bitmap G_GNUC_UNUSED,
|
|
int fd G_GNUC_UNUSED,
|
|
struct ifreq *ifr G_GNUC_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
# endif
|
|
|
|
|
|
# if WITH_DECL_ETHTOOL_SCOALESCE && WITH_DECL_ETHTOOL_GCOALESCE
|
|
/**
|
|
* virNetDevSetCoalesce:
|
|
* @ifname: interface name to modify
|
|
* @coalesce: Coalesce settings to set or update
|
|
* @update: Whether this is an update for existing settings or not
|
|
*
|
|
* This function sets the various coalesce settings for a given interface
|
|
* @ifname and updates them back into @coalesce.
|
|
*
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
int virNetDevSetCoalesce(const char *ifname,
|
|
virNetDevCoalesce *coalesce,
|
|
bool update)
|
|
{
|
|
struct ifreq ifr;
|
|
struct ethtool_coalesce coal = {0};
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
if (!coalesce && !update)
|
|
return 0;
|
|
|
|
if (coalesce) {
|
|
coal = (struct ethtool_coalesce) {
|
|
.rx_max_coalesced_frames = coalesce->rx_max_coalesced_frames,
|
|
.rx_coalesce_usecs_irq = coalesce->rx_coalesce_usecs_irq,
|
|
.rx_max_coalesced_frames_irq = coalesce->rx_max_coalesced_frames_irq,
|
|
.tx_coalesce_usecs = coalesce->tx_coalesce_usecs,
|
|
.tx_max_coalesced_frames = coalesce->tx_max_coalesced_frames,
|
|
.tx_coalesce_usecs_irq = coalesce->tx_coalesce_usecs_irq,
|
|
.tx_max_coalesced_frames_irq = coalesce->tx_max_coalesced_frames_irq,
|
|
.stats_block_coalesce_usecs = coalesce->stats_block_coalesce_usecs,
|
|
.use_adaptive_rx_coalesce = coalesce->use_adaptive_rx_coalesce,
|
|
.use_adaptive_tx_coalesce = coalesce->use_adaptive_tx_coalesce,
|
|
.pkt_rate_low = coalesce->pkt_rate_low,
|
|
.rx_coalesce_usecs_low = coalesce->rx_coalesce_usecs_low,
|
|
.rx_max_coalesced_frames_low = coalesce->rx_max_coalesced_frames_low,
|
|
.tx_coalesce_usecs_low = coalesce->tx_coalesce_usecs_low,
|
|
.tx_max_coalesced_frames_low = coalesce->tx_max_coalesced_frames_low,
|
|
.pkt_rate_high = coalesce->pkt_rate_high,
|
|
.rx_coalesce_usecs_high = coalesce->rx_coalesce_usecs_high,
|
|
.rx_max_coalesced_frames_high = coalesce->rx_max_coalesced_frames_high,
|
|
.tx_coalesce_usecs_high = coalesce->tx_coalesce_usecs_high,
|
|
.tx_max_coalesced_frames_high = coalesce->tx_max_coalesced_frames_high,
|
|
.rate_sample_interval = coalesce->rate_sample_interval,
|
|
};
|
|
}
|
|
|
|
coal.cmd = ETHTOOL_SCOALESCE;
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
ifr.ifr_data = (void *) &coal;
|
|
|
|
if (virNetDevSendEthtoolIoctl(ifname, fd, &ifr) < 0) {
|
|
virReportSystemError(errno,
|
|
_("Cannot set coalesce info on '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
|
|
if (coalesce) {
|
|
coal = (struct ethtool_coalesce) {
|
|
.cmd = ETHTOOL_GCOALESCE,
|
|
};
|
|
|
|
/* Don't fail if the update itself fails */
|
|
if (virNetDevSendEthtoolIoctl(ifname, fd, &ifr) == 0) {
|
|
coalesce->rx_max_coalesced_frames = coal.rx_max_coalesced_frames;
|
|
coalesce->rx_coalesce_usecs_irq = coal.rx_coalesce_usecs_irq;
|
|
coalesce->rx_max_coalesced_frames_irq = coal.rx_max_coalesced_frames_irq;
|
|
coalesce->tx_coalesce_usecs = coal.tx_coalesce_usecs;
|
|
coalesce->tx_max_coalesced_frames = coal.tx_max_coalesced_frames;
|
|
coalesce->tx_coalesce_usecs_irq = coal.tx_coalesce_usecs_irq;
|
|
coalesce->tx_max_coalesced_frames_irq = coal.tx_max_coalesced_frames_irq;
|
|
coalesce->stats_block_coalesce_usecs = coal.stats_block_coalesce_usecs;
|
|
coalesce->use_adaptive_rx_coalesce = coal.use_adaptive_rx_coalesce;
|
|
coalesce->use_adaptive_tx_coalesce = coal.use_adaptive_tx_coalesce;
|
|
coalesce->pkt_rate_low = coal.pkt_rate_low;
|
|
coalesce->rx_coalesce_usecs_low = coal.rx_coalesce_usecs_low;
|
|
coalesce->rx_max_coalesced_frames_low = coal.rx_max_coalesced_frames_low;
|
|
coalesce->tx_coalesce_usecs_low = coal.tx_coalesce_usecs_low;
|
|
coalesce->tx_max_coalesced_frames_low = coal.tx_max_coalesced_frames_low;
|
|
coalesce->pkt_rate_high = coal.pkt_rate_high;
|
|
coalesce->rx_coalesce_usecs_high = coal.rx_coalesce_usecs_high;
|
|
coalesce->rx_max_coalesced_frames_high = coal.rx_max_coalesced_frames_high;
|
|
coalesce->tx_coalesce_usecs_high = coal.tx_coalesce_usecs_high;
|
|
coalesce->tx_max_coalesced_frames_high = coal.tx_max_coalesced_frames_high;
|
|
coalesce->rate_sample_interval = coal.rate_sample_interval;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
# else
|
|
int virNetDevSetCoalesce(const char *ifname,
|
|
virNetDevCoalesce *coalesce,
|
|
bool update)
|
|
{
|
|
if (!coalesce && !update)
|
|
return 0;
|
|
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot set coalesce info on interface '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
# endif
|
|
|
|
|
|
/**
|
|
* virNetDevGetFeatures:
|
|
* This function gets the nic offloads features available for ifname
|
|
*
|
|
* @ifname: name of the interface
|
|
* @out: bitmap of the available virNetDevFeature feature bits
|
|
*
|
|
* Returns 0 on success or if called from session mode, -1 on failure.
|
|
* If called from session mode, an empty bitmap is returned.
|
|
*/
|
|
int
|
|
virNetDevGetFeatures(const char *ifname,
|
|
virBitmap **out)
|
|
{
|
|
struct ifreq ifr;
|
|
VIR_AUTOCLOSE fd = -1;
|
|
|
|
*out = virBitmapNew(VIR_NET_DEV_FEAT_LAST);
|
|
|
|
if ((fd = virNetDevSetupControl(ifname, &ifr)) < 0)
|
|
return -1;
|
|
|
|
virNetDevGetEthtoolFeatures(ifname, *out, fd, &ifr);
|
|
|
|
if (virNetDevGetEthtoolGFeatures(ifname, *out, fd, &ifr) < 0)
|
|
return -1;
|
|
|
|
if (virNetDevRDMAFeature(ifname, out) < 0)
|
|
return -1;
|
|
|
|
if (virNetDevSwitchdevFeature(ifname, out) < 0)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int
|
|
virNetDevGetFeatures(const char *ifname G_GNUC_UNUSED,
|
|
virBitmap **out G_GNUC_UNUSED)
|
|
{
|
|
VIR_DEBUG("Getting network device features on %s is not implemented on this platform",
|
|
ifname);
|
|
return 0;
|
|
}
|
|
|
|
int virNetDevSetCoalesce(const char *ifname,
|
|
virNetDevCoalesce *coalesce,
|
|
bool update)
|
|
{
|
|
if (!coalesce && !update)
|
|
return 0;
|
|
|
|
virReportSystemError(ENOSYS,
|
|
_("Cannot set coalesce info on interface '%s'"),
|
|
ifname);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
|
|
/**
|
|
* virNetDevRunEthernetScript:
|
|
* @ifname: the interface name
|
|
* @script: the script name
|
|
*
|
|
* This function executes script for new tap device created by libvirt.
|
|
* Returns 0 in case of success or -1 on failure
|
|
*/
|
|
int
|
|
virNetDevRunEthernetScript(const char *ifname, const char *script)
|
|
{
|
|
g_autoptr(virCommand) cmd = NULL;
|
|
|
|
/* Not a bug! Previously we did accept script="" as a NO-OP. */
|
|
if (STREQ(script, ""))
|
|
return 0;
|
|
|
|
cmd = virCommandNew(script);
|
|
virCommandAddArgFormat(cmd, "%s", ifname);
|
|
virCommandClearCaps(cmd);
|
|
#ifdef CAP_NET_ADMIN
|
|
virCommandAllowCap(cmd, CAP_NET_ADMIN);
|
|
#endif
|
|
virCommandAddEnvPassCommon(cmd);
|
|
|
|
return virCommandRun(cmd, NULL);
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevSetRootQDisc:
|
|
* @ifname: the interface name
|
|
* @qdisc: queueing discipline to set
|
|
*
|
|
* For given interface @ifname set its root queueing discipline
|
|
* to @qdisc. This can be used to replace the default qdisc
|
|
* (usually pfifo_fast or whatever is set in
|
|
* /proc/sys/net/core/default_qdisc) with different qdisc.
|
|
*
|
|
* Returns: 0 on success,
|
|
* -1 if failed to exec tc (with error reported)
|
|
* -2 if tc failed (with no error reported)
|
|
*/
|
|
int
|
|
virNetDevSetRootQDisc(const char *ifname,
|
|
const char *qdisc)
|
|
{
|
|
g_autoptr(virCommand) cmd = NULL;
|
|
g_autofree char *outbuf = NULL;
|
|
g_autofree char *errbuf = NULL;
|
|
int status;
|
|
|
|
/* Ideally, we would have a netlink implementation and just
|
|
* call it here. But honestly, I tried and failed miserably.
|
|
* Fallback to spawning tc. */
|
|
cmd = virCommandNewArgList(TC, "qdisc", "add", "dev", ifname,
|
|
"root", "handle", "0:", qdisc,
|
|
NULL);
|
|
|
|
virCommandAddEnvString(cmd, "LC_ALL=C");
|
|
virCommandSetOutputBuffer(cmd, &outbuf);
|
|
virCommandSetErrorBuffer(cmd, &errbuf);
|
|
|
|
if (virCommandRun(cmd, &status) < 0)
|
|
return -1;
|
|
|
|
if (status != 0) {
|
|
VIR_DEBUG("Setting qdisc failed: output='%s' err='%s'", outbuf, errbuf);
|
|
return -2;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevReserveName:
|
|
* @name: name of an existing network device
|
|
*
|
|
* Reserve a network device name, so that any new network device
|
|
* created with an autogenerated name will use a number higher
|
|
* than the number in the given device name.
|
|
*
|
|
* Returns nothing.
|
|
*/
|
|
void
|
|
virNetDevReserveName(const char *name)
|
|
{
|
|
unsigned int id;
|
|
const char *idstr = NULL;
|
|
virNetDevGenNameType type;
|
|
|
|
if (!name)
|
|
return;
|
|
|
|
if (STRPREFIX(name, VIR_NET_GENERATED_VNET_PREFIX))
|
|
type = VIR_NET_DEV_GEN_NAME_VNET;
|
|
else if (STRPREFIX(name, VIR_NET_GENERATED_MACVTAP_PREFIX))
|
|
type = VIR_NET_DEV_GEN_NAME_MACVTAP;
|
|
else if (STRPREFIX(name, VIR_NET_GENERATED_MACVLAN_PREFIX))
|
|
type = VIR_NET_DEV_GEN_NAME_MACVLAN;
|
|
else
|
|
return;
|
|
|
|
VIR_INFO("marking device in use: '%s'", name);
|
|
|
|
idstr = name + strlen(virNetDevGenNames[type].prefix);
|
|
|
|
if (virStrToLong_ui(idstr, NULL, 10, &id) >= 0) {
|
|
virMutexLock(&virNetDevGenNames[type].mutex);
|
|
|
|
if (virNetDevGenNames[type].lastID < (int)id)
|
|
virNetDevGenNames[type].lastID = id;
|
|
|
|
virMutexUnlock(&virNetDevGenNames[type].mutex);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* virNetDevGenerateName:
|
|
* @ifname: pointer to pointer to string which can be a template,
|
|
* NULL or user-provided name.
|
|
* @type: type of the network device
|
|
*
|
|
* generate a new (currently unused) name for a new network device based
|
|
* on @ifname. If string pointed by @ifname is a template, replace %d
|
|
* with the reserved id; if that string is NULL, just generate a new
|
|
* name. Keep trying new values until one is found that doesn't already
|
|
* exist, or we've tried 10000 different names. Once a usable name is
|
|
* found, replace the template with the actual name.
|
|
*
|
|
* Note: if string pointed by @ifname is NOT a template or NULL, leave
|
|
* it unchanged and return it directly.
|
|
*
|
|
* Returns 0 on success, -1 on failure.
|
|
*/
|
|
int
|
|
virNetDevGenerateName(char **ifname, virNetDevGenNameType type)
|
|
{
|
|
int id;
|
|
const char *prefix = virNetDevGenNames[type].prefix;
|
|
double maxIDd = pow(10, IFNAMSIZ - 1 - strlen(prefix));
|
|
int maxID = INT_MAX;
|
|
int attempts = 0;
|
|
|
|
/* The @ifname is not a template, leave it unchanged. */
|
|
if (*ifname &&
|
|
(strchr(*ifname, '%') != strrchr(*ifname, '%') ||
|
|
strstr(*ifname, "%d") == NULL)) {
|
|
return 0;
|
|
}
|
|
|
|
if (maxIDd <= (double)INT_MAX)
|
|
maxID = (int)maxIDd;
|
|
|
|
do {
|
|
g_autofree char *try = NULL;
|
|
|
|
virMutexLock(&virNetDevGenNames[type].mutex);
|
|
|
|
id = ++virNetDevGenNames[type].lastID;
|
|
|
|
/* reset before overflow */
|
|
if (virNetDevGenNames[type].lastID >= maxID)
|
|
virNetDevGenNames[type].lastID = -1;
|
|
|
|
virMutexUnlock(&virNetDevGenNames[type].mutex);
|
|
|
|
if (*ifname)
|
|
try = g_strdup_printf(*ifname, id);
|
|
else
|
|
try = g_strdup_printf("%s%d", prefix, id);
|
|
|
|
if (!virNetDevExists(try)) {
|
|
g_free(*ifname);
|
|
*ifname = g_steal_pointer(&try);
|
|
return 0;
|
|
}
|
|
} while (++attempts < 10000);
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
_("no unused %s names available"),
|
|
prefix);
|
|
return -1;
|
|
}
|