libvirt/tests/virnetdevtest.c
Dmitrii Shcherbakov 09cdd16a9b Ignore EPERM on implicit clearing of VF VLAN ID
SmartNIC DPUs may not expose some privileged eswitch operations
to the hypervisor hosts. For example, this happens with Bluefield
devices running in the ECPF (default) mode for security reasons. While
VF MAC address programming is possible via an RTM_SETLINK operation,
trying to set a VLAN ID in the same operation will fail with EPERM.

The equivalent ip link commands below provide an illustration:

1. This works:

sudo ip link set enp130s0f0 vf 2 mac de:ad:be:ef:ca:fe

2. Setting (or clearing) a VLAN fails with EPERM:

sudo ip link set enp130s0f0 vf 2 vlan 0
RTNETLINK answers: Operation not permitted

3. This is what Libvirt attempts to do today (when trying to clear a
   VF VLAN at the same time as programming a VF MAC).

sudo ip link set enp130s0f0 vf 2 vlan 0 mac de:ad:be:ef:ca:fe
RTNETLINK answers: Operation not permitted

If setting an explicit VLAN ID results in an EPERM, clearing a VLAN
(setting a VLAN ID to 0) can be handled gracefully by ignoring the
EPERM error with the rationale being that if we cannot set this state
in the first place, we cannot clear it either.

In order to keep explicit clearing of VLAN ID working as it used to
be passing a NULL pointer for VLAN ID is used.

Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov@canonical.com>
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Michal Privoznik <mprivozn@redhat.com>
2022-02-03 16:52:05 +01:00

349 lines
11 KiB
C

/*
* Copyright (C) 2015 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 "internal.h"
#include "testutils.h"
#include "virnetlink.h"
#define LIBVIRT_VIRNETDEVPRIV_H_ALLOW
#ifdef __linux__
# include "virmock.h"
# include "virnetdevpriv.h"
# define VIR_FROM_THIS VIR_FROM_NONE
struct testVirNetDevGetLinkInfoData {
const char *ifname; /* ifname to get info on */
virNetDevIfState state; /* expected state */
unsigned int speed; /* expected speed */
};
static int
testVirNetDevGetLinkInfo(const void *opaque)
{
const struct testVirNetDevGetLinkInfoData *data = opaque;
virNetDevIfLink lnk;
if (virNetDevGetLinkInfo(data->ifname, &lnk) < 0)
return -1;
if (lnk.state != data->state) {
fprintf(stderr,
"Fetched link state (%s) doesn't match the expected one (%s)",
virNetDevIfStateTypeToString(lnk.state),
virNetDevIfStateTypeToString(data->state));
return -1;
}
if (lnk.speed != data->speed) {
fprintf(stderr,
"Fetched link speed (%u) doesn't match the expected one (%u)",
lnk.speed, data->speed);
return -1;
}
return 0;
}
# if defined(WITH_LIBNL)
int
(*real_virNetDevSendVfSetLinkRequest)(const char *ifname,
int vfInfoType,
const void *payload,
const size_t payloadLen);
int
(*real_virNetDevSetVfMac)(const char *ifname,
int vf,
const virMacAddr *macaddr,
bool *allowRetry);
int
(*real_virNetDevSetVfVlan)(const char *ifname,
int vf,
const int *vlanid);
static void
init_syms(void)
{
VIR_MOCK_REAL_INIT(virNetDevSendVfSetLinkRequest);
VIR_MOCK_REAL_INIT(virNetDevSetVfMac);
VIR_MOCK_REAL_INIT(virNetDevSetVfVlan);
}
int
virNetDevSetVfMac(const char *ifname,
int vf,
const virMacAddr *macaddr,
bool *allowRetry)
{
init_syms();
if (STREQ_NULLABLE(ifname, "fakeiface-macerror")) {
return -EBUSY;
} else if (STREQ_NULLABLE(ifname, "fakeiface-altmacerror")) {
return -EINVAL;
} else if (STREQ_NULLABLE(ifname, "fakeiface-macerror-novlanerror")) {
return -EAGAIN;
} else if (STREQ_NULLABLE(ifname, "fakeiface-macerror-vlanerror")) {
return -ENODEV;
} else if (STREQ_NULLABLE(ifname, "fakeiface-nomacerror-vlanerror")) {
return 0;
} else if (STREQ_NULLABLE(ifname, "fakeiface-nomacerror-novlanerror")) {
return 0;
}
return real_virNetDevSetVfMac(ifname, vf, macaddr, allowRetry);
}
int
virNetDevSetVfVlan(const char *ifname,
int vf,
const int *vlanid)
{
init_syms();
if (STREQ_NULLABLE(ifname, "fakeiface-macerror-vlanerror")) {
return -EPERM;
} else if (STREQ_NULLABLE(ifname, "fakeiface-nomacerror-vlanerror")) {
return -EPERM;
} else if (STREQ_NULLABLE(ifname, "fakeiface-macerror-novlanerror")) {
return 0;
} else if (STREQ_NULLABLE(ifname, "fakeiface-nomacerror-novlanerror")) {
return 0;
}
return real_virNetDevSetVfVlan(ifname, vf, vlanid);
}
int
virNetDevSendVfSetLinkRequest(const char *ifname,
int vfInfoType,
const void *payload,
const size_t payloadLen)
{
init_syms();
if (STREQ_NULLABLE(ifname, "fakeiface-eperm")) {
return -EPERM;
} else if (STREQ_NULLABLE(ifname, "fakeiface-eagain")) {
return -EAGAIN;
} else if (STREQ_NULLABLE(ifname, "fakeiface-einval")) {
return -EINVAL;
} else if (STREQ_NULLABLE(ifname, "fakeiface-ok")) {
return 0;
}
return real_virNetDevSendVfSetLinkRequest(ifname, vfInfoType, payload, payloadLen);
}
static int
testVirNetDevSetVfMac(const void *opaque G_GNUC_UNUSED)
{
struct testCase {
const char *ifname;
const int vf_num;
const virMacAddr macaddr;
bool allow_retry;
const int rc;
};
size_t i = 0;
int rc = 0;
struct testCase testCases[] = {
{ .ifname = "fakeiface-ok", .vf_num = 1,
.macaddr = { .addr = { 0, 0, 0, 0, 0, 0 } }, .allow_retry = false, .rc = 0 },
{ .ifname = "fakeiface-ok", .vf_num = 2,
.macaddr = { .addr = { 0, 0, 0, 7, 7, 7 } }, .allow_retry = false, .rc = 0 },
{ .ifname = "fakeiface-ok", .vf_num = 3,
.macaddr = { .addr = { 0, 0, 0, 0, 0, 0 } }, .allow_retry = true, .rc = 0 },
{ .ifname = "fakeiface-ok", .vf_num = 4,
.macaddr = { .addr = { 0, 0, 0, 7, 7, 7 } }, .allow_retry = true, .rc = 0 },
{ .ifname = "fakeiface-eperm", .vf_num = 5,
.macaddr = { .addr = { 0, 0, 0, 0, 0, 0 } }, .allow_retry = false, .rc = -EPERM },
{ .ifname = "fakeiface-einval", .vf_num = 6,
.macaddr = { .addr = { 0, 0, 0, 0, 0, 0 } }, .allow_retry = false, .rc = -EINVAL },
{ .ifname = "fakeiface-einval", .vf_num = 7,
.macaddr = { .addr = { 0, 0, 0, 0, 0, 0 } }, .allow_retry = true, .rc = -EINVAL },
{ .ifname = "fakeiface-einval", .vf_num = 8,
.macaddr = { .addr = { 0, 0, 0, 7, 7, 7 } }, .allow_retry = false, .rc = -EINVAL },
{ .ifname = "fakeiface-einval", .vf_num = 9,
.macaddr = { .addr = { 0, 0, 0, 7, 7, 7 } }, .allow_retry = true, .rc = -EINVAL },
};
for (i = 0; i < sizeof(testCases) / sizeof(struct testCase); ++i) {
rc = virNetDevSetVfMac(testCases[i].ifname, testCases[i].vf_num,
&testCases[i].macaddr, &testCases[i].allow_retry);
if (rc != testCases[i].rc) {
return -1;
}
}
return 0;
}
static int
testVirNetDevSetVfMissingMac(const void *opaque G_GNUC_UNUSED)
{
bool allowRetry = false;
/* NULL MAC pointer. */
if (virNetDevSetVfMac("fakeiface-ok", 1, NULL, &allowRetry) != -EINVAL) {
return -1;
}
allowRetry = true;
if (virNetDevSetVfMac("fakeiface-ok", 1, NULL, &allowRetry) != -EINVAL) {
return -1;
}
return 0;
}
static int
testVirNetDevSetVfVlan(const void *opaque G_GNUC_UNUSED)
{
struct testCase {
const char *ifname;
const int vf_num;
const int vlan_id;
const int rc;
};
struct nullVlanTestCase {
const char *ifname;
const int vf_num;
const int rc;
};
size_t i = 0;
int rc = 0;
const struct testCase testCases[] = {
/* VLAN ID is out of range of valid values (0-4095). */
{ .ifname = "enxdeadbeefcafe", .vf_num = 1, .vlan_id = 4096, .rc = -ERANGE },
{ .ifname = "enxdeadbeefcafe", .vf_num = 1, .vlan_id = -1, .rc = -ERANGE },
{ .ifname = "fakeiface-eperm", .vf_num = 1, .vlan_id = 0, .rc = -EPERM },
{ .ifname = "fakeiface-eagain", .vf_num = 1, .vlan_id = 0, .rc = -EAGAIN },
/* Successful requests with vlan id 0 need to have a zero return code. */
{ .ifname = "fakeiface-ok", .vf_num = 1, .vlan_id = 0, .rc = 0 },
/* Requests with a non-zero VLAN ID that result in an EPERM need to result in failures.
* failures. */
{ .ifname = "fakeiface-eperm", .vf_num = 1, .vlan_id = 42, .rc = -EPERM },
/* Requests with a non-zero VLAN ID that result in some other errors need to result in
* failures. */
{ .ifname = "fakeiface-eagain", .vf_num = 1, .vlan_id = 42, .rc = -EAGAIN },
/* Successful requests with a non-zero VLAN ID */
{ .ifname = "fakeiface-ok", .vf_num = 1, .vlan_id = 42, .rc = 0 },
};
const struct nullVlanTestCase nullVLANTestCases[] = {
{ .ifname = "fakeiface-eperm", .vf_num = 1, .rc = 0 },
{ .ifname = "fakeiface-eagain", .vf_num = 1, .rc = -EAGAIN },
/* Successful requests with vlan id 0 need to have a zero return code. */
{ .ifname = "fakeiface-ok", .vf_num = 1, .rc = 0 },
};
for (i = 0; i < sizeof(testCases) / sizeof(struct testCase); ++i) {
rc = virNetDevSetVfVlan(testCases[i].ifname, testCases[i].vf_num, &testCases[i].vlan_id);
if (rc != testCases[i].rc) {
return -1;
}
}
for (i = 0; i < sizeof(nullVLANTestCases) / sizeof(struct nullVlanTestCase); ++i) {
rc = virNetDevSetVfVlan(nullVLANTestCases[i].ifname, nullVLANTestCases[i].vf_num, NULL);
if (rc != nullVLANTestCases[i].rc) {
return -1;
}
}
return 0;
}
static int
testVirNetDevSetVfConfig(const void *opaque G_GNUC_UNUSED)
{
struct testCase {
const char *ifname;
const int rc;
};
int rc = 0;
size_t i = 0;
/* Nested functions are mocked so dummy values are used. */
const virMacAddr mac = { .addr = { 0xDE, 0xAD, 0xBE, 0xEF, 0xCA, 0xFE }};
const int vfNum = 1;
const int vlanid = 0;
bool *allowRetry = NULL;
const struct testCase testCases[] = {
{ .ifname = "fakeiface-macerror", .rc = -EBUSY },
{ .ifname = "fakeiface-altmacerror", .rc = -EINVAL },
{ .ifname = "fakeiface-macerror-novlanerror", .rc = -EAGAIN },
{ .ifname = "fakeiface-macerror-vlanerror", .rc = -ENODEV },
{ .ifname = "fakeiface-nomacerror-novlanerror", .rc = 0 },
};
for (i = 0; i < sizeof(testCases) / sizeof(struct testCase); ++i) {
rc = virNetDevSetVfConfig(testCases[i].ifname, vfNum, &mac, &vlanid, allowRetry);
if (rc != testCases[i].rc) {
return -1;
}
}
return 0;
}
# endif /* defined(WITH_LIBNL) */
static int
mymain(void)
{
int ret = 0;
# define DO_TEST_LINK(ifname, state, speed) \
do { \
struct testVirNetDevGetLinkInfoData data = {ifname, state, speed}; \
if (virTestRun("Link info: " # ifname, \
testVirNetDevGetLinkInfo, &data) < 0) \
ret = -1; \
} while (0)
DO_TEST_LINK("eth0", VIR_NETDEV_IF_STATE_UP, 1000);
DO_TEST_LINK("lo", VIR_NETDEV_IF_STATE_UNKNOWN, 0);
DO_TEST_LINK("eth0-broken", VIR_NETDEV_IF_STATE_DOWN, 0);
# if defined(WITH_LIBNL)
if (virTestRun("Set VF MAC", testVirNetDevSetVfMac, NULL) < 0)
ret = -1;
if (virTestRun("Set VF MAC: missing MAC pointer", testVirNetDevSetVfMissingMac, NULL) < 0)
ret = -1;
if (virTestRun("Set VF VLAN", testVirNetDevSetVfVlan, NULL) < 0)
ret = -1;
if (virTestRun("Set VF Config", testVirNetDevSetVfConfig, NULL) < 0)
ret = -1;
# endif /* defined(WITH_LIBNL) */
return ret == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}
VIR_TEST_MAIN_PRELOAD(mymain, VIR_TEST_MOCK("virnetdev"))
#else
int
main(void)
{
return EXIT_AM_SKIP;
}
#endif