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@xxxxxxxxxxxxx> > --- > 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