On 2/1/22 09:28, Dmitrii Shcherbakov wrote:
This has a benefit of being able to handle error codes for those
operations separately which is useful when drivers allow setting a MAC
address but do not allow setting a VLAN (which is the case with some
SmartNIC DPUs).
Signed-off-by: Dmitrii Shcherbakov <dmitrii.shcherbakov(a)canonical.com>
---
src/libvirt_private.syms | 7 ++
src/util/virnetdev.c | 226 ++++++++++++++++++++++++++------------
src/util/virnetdevpriv.h | 44 ++++++++
tests/virnetdevtest.c | 230 ++++++++++++++++++++++++++++++++++++++-
4 files changed, 434 insertions(+), 73 deletions(-)
create mode 100644 src/util/virnetdevpriv.h
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index bc6fa191bf..e6493c2176 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2847,6 +2847,13 @@ virNetDevOpenvswitchSetTimeout;
virNetDevOpenvswitchUpdateVlan;
+# util/virnetdevpriv.h
+virNetDevSendVfSetLinkRequest;
+virNetDevSetVfConfig;
+virNetDevSetVfMac;
+virNetDevSetVfVlan;
+
+
# util/virnetdevtap.h
virNetDevTapAttachBridge;
virNetDevTapCreate;
diff --git a/src/util/virnetdev.c b/src/util/virnetdev.c
index d93b2c6a83..043225d31f 100644
--- a/src/util/virnetdev.c
+++ b/src/util/virnetdev.c
@@ -19,7 +19,9 @@
#include <config.h>
#include <math.h>
-#include "virnetdev.h"
+#define LIBVIRT_VIRNETDEVPRIV_H_ALLOW
+
+#include "virnetdevpriv.h"
#include "viralloc.h"
#include "virnetlink.h"
#include "virmacaddr.h"
@@ -1509,16 +1511,12 @@ static struct nla_policy ifla_vfstats_policy[IFLA_VF_STATS_MAX+1]
= {
[IFLA_VF_STATS_MULTICAST] = { .type = NLA_U64 },
};
-
-static int
-virNetDevSetVfConfig(const char *ifname, int vf,
- const virMacAddr *macaddr, int vlanid,
- bool *allowRetry)
+int
+virNetDevSendVfSetLinkRequest(const char *ifname, int vfInfoType,
+ const void *payload, const size_t payloadLen)
Here and everywhere else, please put one argument per line.
{
- int rc = -1;
- char macstr[VIR_MAC_STRING_BUFLEN];
g_autofree struct nlmsghdr *resp = NULL;
- struct nlmsgerr *err;
+ struct nlmsgerr *err = NULL;
unsigned int recvbuflen = 0;
struct nl_msg *nl_msg;
struct nlattr *vfinfolist, *vfinfo;
@@ -1526,9 +1524,11 @@ virNetDevSetVfConfig(const char *ifname, int vf,
.ifi_family = AF_UNSPEC,
.ifi_index = -1,
};
+ int rc = 0;
We usually initialize return values to -1 so that they don't have to be
overwritten in each error path.
- if (!macaddr && vlanid < 0)
+ if (payload == NULL || payloadLen == 0) {
return -1;
+ }
This seems rather excessive. I know you just copied whatever was in the
original code, but neither of callers passes NULL/0.
nl_msg = virNetlinkMsgNew(RTM_SETLINK, NLM_F_REQUEST);
@@ -1546,30 +1546,8 @@ virNetDevSetVfConfig(const char *ifname, int vf,
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;
- }
+ if (nla_put(nl_msg, vfInfoType, payloadLen, payload) < 0)
+ goto buffer_too_small;
nla_nest_end(nl_msg, vfinfo);
nla_nest_end(nl_msg, vfinfolist);
@@ -1582,48 +1560,20 @@ virNetDevSetVfConfig(const char *ifname, int vf,
goto malformed_resp;
switch (resp->nlmsg_type) {
- case NLMSG_ERROR:
- err = (struct nlmsgerr *)NLMSG_DATA(resp);
- if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
+ case NLMSG_ERROR:
+ err = (struct nlmsgerr *)NLMSG_DATA(resp);
+ if (resp->nlmsg_len < NLMSG_LENGTH(sizeof(*err)))
+ goto malformed_resp;
+ rc = err->error;
+ break;
+ case NLMSG_DONE:
+ rc = 0;
+ break;
+ default:
goto malformed_resp;
No need for formatting change.
-
- /* 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;
@@ -1638,6 +1588,100 @@ virNetDevSetVfConfig(const char *ifname, int vf,
goto cleanup;
}
+int
+virNetDevSetVfVlan(const char *ifname, int vf, int vlanid)
+{
+ int rc = 0;
+ int requestError = 0;
+
+ struct ifla_vf_vlan ifla_vf_vlan = {
+ .vf = vf,
+ .vlan = vlanid,
+ .qos = 0,
+ };
Alignment.
+
+ /* VLAN ids 0 and 4095 are reserved per 802.1Q but are valid values. */
+ if ((vlanid < 0 || vlanid > 4095)) {
+ return -ERANGE;
Here, we need to report an error because we do so in the other error
path. The rule of thumb is that either all or none error paths report an
error.
+ }
+
+ requestError = virNetDevSendVfSetLinkRequest(ifname, IFLA_VF_VLAN,
+ &ifla_vf_vlan,
sizeof(ifla_vf_vlan));
+
+ if (requestError) {
+ virReportSystemError(-requestError,
+ _("Cannot set interface vlanid to %d "
+ "for ifname %s vf %d"),
Pre-existing, but error message are exempt from 80-chars long line rule
so that they can be easily 'git-grep'-ed. But since you're touching this
code it's worth fixing.
+ vlanid, ifname ? ifname :
"(unspecified)", vf);
+ rc = requestError;
So there's no difference between @rc and @requestError. They both have
the same value in the end.
+ }
+ VIR_DEBUG("RTM_SETLINK %s vf %d vlanid=%d - %s",
+ ifname, vf,
+ vlanid, rc < 0 ? "Fail" : "Success");
+ return rc;
+}
+
+int
+virNetDevSetVfMac(const char *ifname, int vf,
+ const virMacAddr *macaddr,
+ bool *allowRetry)
+{
Basically all comments from above apply here too.
+ int rc = 0;
+ int requestError = 0;
+ char macstr[VIR_MAC_STRING_BUFLEN];
+
+ struct ifla_vf_mac ifla_vf_mac = {
+ .vf = vf,
+ .mac = { 0, },
+ };
+
+ if (macaddr == NULL || allowRetry == NULL)
+ return -EINVAL;
+
+ virMacAddrGetRaw(macaddr, ifla_vf_mac.mac);
+
+ requestError = virNetDevSendVfSetLinkRequest(ifname, IFLA_VF_MAC,
+ &ifla_vf_mac,
sizeof(ifla_vf_mac));
+ /* 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 (requestError == -EINVAL && *allowRetry &&
!virMacAddrCmp(macaddr, &zeroMAC)) {
+ rc = requestError;
+ } else if (requestError) {
+ /* other errors are permanent */
+ virReportSystemError(-requestError,
+ _("Cannot set interface MAC to %s "
+ "for ifname %s vf %d"),
+ (macaddr
+ ? virMacAddrFormat(macaddr, macstr)
+ : "(unchanged)"),
+ ifname ? ifname : "(unspecified)",
+ vf);
+ *allowRetry = false; /* no use retrying */
+ rc = requestError;
+ } else {
+ rc = 0;
+ }
+ VIR_DEBUG("RTM_SETLINK %s vf %d MAC=%s - %s",
+ ifname, vf,
+ macaddr ? virMacAddrFormat(macaddr, macstr) : "(unchanged)",
+ rc < 0 ? "Fail" : "Success");
+ return rc;
+}
+
+int
+virNetDevSetVfConfig(const char *ifname, int vf,
+ const virMacAddr *macaddr, int vlanid,
+ bool *allowRetry)
+{
+ int rc = 0;
+ if ((rc = virNetDevSetVfMac(ifname, vf, macaddr, allowRetry)) < 0)
+ return rc;
+ if ((rc = virNetDevSetVfVlan(ifname, vf, vlanid)) < 0)
+ return rc;
+ return rc;
+}
+
/**
* virNetDevParseVfInfo:
* Get the VF interface information from kernel by netlink, To make netlink
@@ -2391,6 +2435,44 @@ virNetDevVFInterfaceStats(virPCIDeviceAddress *vfAddr
G_GNUC_UNUSED,
return -1;
}
+int
+virNetDevSendVfSetLinkRequest(const char *ifname G_GNUC_UNUSED, int vfInfoType
G_GNUC_UNUSED,
+ const void *payload G_GNUC_UNUSED,
+ const size_t payloadLen G_GNUC_UNUSED)
+{
+ virReportSystemError(ENOSYS, "%s",
+ _("Unable to send a VF SETLINK request on this
platform"));
+ return -1;
I'm inclined to return -ENOSYS rather than -1 because the real
implementation would return a real -errno. I know it doesn't matter
really because neither of callers looks at errno, they merely check for
<0 but consistency is paramount. Thus if one variant of function returns
-errno then the other should too.
+}
+
+int
+virNetDevSetVfVlan(const char *ifname G_GNUC_UNUSED, int vf G_GNUC_UNUSED, int vlanid
G_GNUC_UNUSED)
+{
+ virReportSystemError(ENOSYS, "%s",
+ _("Unable to set a VF VLAN on this platform"));
+ return -1;
+}
+
+int
+virNetDevSetVfMac(const char *ifname G_GNUC_UNUSED, int vf G_GNUC_UNUSED,
+ const virMacAddr *macaddr G_GNUC_UNUSED,
+ bool *allowRetry G_GNUC_UNUSED)
+{
+ virReportSystemError(ENOSYS, "%s",
+ _("Unable to set a VF MAC on this platform"));
+ return -1;
+}
+
+int
+virNetDevSetVfConfig(const char *ifname G_GNUC_UNUSED, int vf G_GNUC_UNUSED,
+ const virMacAddr *macaddr G_GNUC_UNUSED, int vlanid G_GNUC_UNUSED,
+ bool *allowRetry G_GNUC_UNUSED)
+{
+ virReportSystemError(ENOSYS, "%s",
+ _("Unable to set a VF config on this platform"));
+ return -1;
+}
+
#endif /* defined(WITH_LIBNL) */
diff --git a/src/util/virnetdevpriv.h b/src/util/virnetdevpriv.h
new file mode 100644
index 0000000000..7990bf5269
--- /dev/null
+++ b/src/util/virnetdevpriv.h
@@ -0,0 +1,44 @@
+/*
+ * virnetdevpriv.h: private virnetdev header for unit testing
+ *
+ * Copyright (C) 2021 Canonical Ltd.
+ *
+ * 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/>.
+ */
+
+#ifndef LIBVIRT_VIRNETDEVPRIV_H_ALLOW
+# error "virnetdevpriv.h may only be included by virnetdev.c or test suites"
+#endif /* LIBVIRT_VIRNETDEVPRIV_H_ALLOW */
+
+#pragma once
+
+#include "virnetdev.h"
+
+int
+virNetDevSendVfSetLinkRequest(const char *ifname, int vfInfoType,
+ const void *payload, const size_t payloadLen);
+
+int
+virNetDevSetVfVlan(const char *ifname, int vf, int vlanid);
+
+int
+virNetDevSetVfMac(const char *ifname, int vf,
+ const virMacAddr *macaddr,
+ bool *allowRetry);
+
+int
+virNetDevSetVfConfig(const char *ifname, int vf,
+ const virMacAddr *macaddr, int vlanid,
+ bool *allowRetry);
diff --git a/tests/virnetdevtest.c b/tests/virnetdevtest.c
index aadbeb1ef4..c8dba05c31 100644
--- a/tests/virnetdevtest.c
+++ b/tests/virnetdevtest.c
Bonus points for extensive tests.
Michal