net/vdev_netvsc: remove specified devices IP check
[dpdk.git] / drivers / net / vdev_netvsc / vdev_netvsc.c
index 0055d0b..ed040be 100644 (file)
@@ -1,12 +1,14 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright 2017 6WIND S.A.
- * Copyright 2017 Mellanox Technologies, Ltd.
+ * Copyright 2017 Mellanox Technologies, Ltd
  */
 
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
 #include <linux/sockios.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
 #include <net/if.h>
 #include <net/if_arp.h>
 #include <netinet/ip.h>
 #include <rte_errno.h>
 #include <rte_ethdev.h>
 #include <rte_ether.h>
+#include <rte_hypervisor.h>
 #include <rte_kvargs.h>
 #include <rte_log.h>
 
 #define VDEV_NETVSC_DRIVER net_vdev_netvsc
+#define VDEV_NETVSC_DRIVER_NAME RTE_STR(VDEV_NETVSC_DRIVER)
 #define VDEV_NETVSC_ARG_IFACE "iface"
 #define VDEV_NETVSC_ARG_MAC "mac"
+#define VDEV_NETVSC_ARG_FORCE "force"
+#define VDEV_NETVSC_ARG_IGNORE "ignore"
 #define VDEV_NETVSC_PROBE_MS 1000
 
 #define NETVSC_CLASS_ID "{f8615163-df3e-46c5-913f-f2d2f965ed0e}"
@@ -44,7 +50,7 @@
 #define DRV_LOG(level, ...) \
        rte_log(RTE_LOG_ ## level, \
                vdev_netvsc_logtype, \
-               RTE_FMT(RTE_STR(VDEV_NETVSC_DRIVER) ": " \
+               RTE_FMT(VDEV_NETVSC_DRIVER_NAME ": " \
                        RTE_FMT_HEAD(__VA_ARGS__,) "\n", \
                RTE_FMT_TAIL(__VA_ARGS__,)))
 
@@ -203,36 +209,96 @@ vdev_netvsc_iface_is_netvsc(const struct if_nameindex *iface)
  *
  * @param[in] name
  *   Network device name.
+ * @param[in] family
+ *   Address family: AF_INET for IPv4 or AF_INET6 for IPv6.
  *
  * @return
- *   A nonzero value when interface has an route. In case of error,
- *   rte_errno is updated and 0 returned.
+ *   1 when interface has a route, negative errno value in case of error and
+ *   0 otherwise.
  */
 static int
-vdev_netvsc_has_route(const char *name)
+vdev_netvsc_has_route(const struct if_nameindex *iface,
+                     const unsigned char family)
 {
-       FILE *fp;
+       /*
+        * The implementation can be simpler by getifaddrs() function usage but
+        * it works for IPv6 only starting from glibc 2.3.3.
+        */
+       char buf[4096];
+       int len;
        int ret = 0;
-       char route[NETVSC_MAX_ROUTE_LINE_SIZE];
-       char *netdev;
-
-       fp = fopen("/proc/net/route", "r");
-       if (!fp) {
-               rte_errno = errno;
-               return 0;
+       int res;
+       int sock;
+       struct nlmsghdr *retmsg = (struct nlmsghdr *)buf;
+       struct sockaddr_nl sa;
+       struct {
+               struct nlmsghdr nlhdr;
+               struct ifaddrmsg addrmsg;
+       } msg;
+
+       if (!iface || (family != AF_INET && family != AF_INET6)) {
+               DRV_LOG(ERR, "%s", rte_strerror(EINVAL));
+               return -EINVAL;
        }
-       while (fgets(route, NETVSC_MAX_ROUTE_LINE_SIZE, fp) != NULL) {
-               netdev = strtok(route, "\t");
-               if (strcmp(netdev, name) == 0) {
-                       ret = 1;
-                       break;
+       sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+       if (sock == -1) {
+               DRV_LOG(ERR, "cannot open socket: %s", rte_strerror(errno));
+               return -errno;
+       }
+       memset(&sa, 0, sizeof(sa));
+       sa.nl_family = AF_NETLINK;
+       sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR;
+       res = bind(sock, (struct sockaddr *)&sa, sizeof(sa));
+       if (res == -1) {
+               ret = -errno;
+               DRV_LOG(ERR, "cannot bind socket: %s", rte_strerror(errno));
+               goto close;
+       }
+       memset(&msg, 0, sizeof(msg));
+       msg.nlhdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+       msg.nlhdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
+       msg.nlhdr.nlmsg_type = RTM_GETADDR;
+       msg.nlhdr.nlmsg_pid = getpid();
+       msg.addrmsg.ifa_family = family;
+       msg.addrmsg.ifa_index = iface->if_index;
+       res = send(sock, &msg, msg.nlhdr.nlmsg_len, 0);
+       if (res == -1) {
+               ret = -errno;
+               DRV_LOG(ERR, "cannot send socket message: %s",
+                       rte_strerror(errno));
+               goto close;
+       }
+       memset(buf, 0, sizeof(buf));
+       len = recv(sock, buf, sizeof(buf), 0);
+       if (len == -1) {
+               ret = -errno;
+               DRV_LOG(ERR, "cannot receive socket message: %s",
+                       rte_strerror(errno));
+               goto close;
+       }
+       while (NLMSG_OK(retmsg, (unsigned int)len)) {
+               struct ifaddrmsg *retaddr =
+                               (struct ifaddrmsg *)NLMSG_DATA(retmsg);
+
+               if (retaddr->ifa_family == family &&
+                   retaddr->ifa_index == iface->if_index) {
+                       struct rtattr *retrta = IFA_RTA(retaddr);
+                       int attlen = IFA_PAYLOAD(retmsg);
+
+                       while (RTA_OK(retrta, attlen)) {
+                               if (retrta->rta_type == IFA_ADDRESS) {
+                                       ret = 1;
+                                       DRV_LOG(DEBUG, "interface %s has IP",
+                                               iface->if_name);
+                                       goto close;
+                               }
+                               retrta = RTA_NEXT(retrta, attlen);
+                       }
                }
-               /* Move file pointer to the next line. */
-               while (strchr(route, '\n') == NULL &&
-                      fgets(route, NETVSC_MAX_ROUTE_LINE_SIZE, fp) != NULL)
-                       ;
+               retmsg = NLMSG_NEXT(retmsg, len);
        }
-       fclose(fp);
+close:
+       close(sock);
        return ret;
 }
 
@@ -384,7 +450,7 @@ vdev_netvsc_alarm(__rte_unused void *arg)
 
        LIST_FOREACH(ctx, &vdev_netvsc_ctx_list, entry) {
                ret = vdev_netvsc_foreach_iface(vdev_netvsc_device_probe, ctx);
-               if (ret)
+               if (ret < 0)
                        break;
        }
        if (!vdev_netvsc_ctx_count)
@@ -419,6 +485,9 @@ vdev_netvsc_alarm(__rte_unused void *arg)
  *   - struct rte_kvargs *kvargs:
  *     Device arguments provided to current driver instance.
  *
+ *   - int force:
+ *     Accept specified interface even if not detected as NetVSC.
+ *
  *   - unsigned int specified:
  *     Number of specific netdevices provided as device arguments.
  *
@@ -436,6 +505,7 @@ vdev_netvsc_netvsc_probe(const struct if_nameindex *iface,
 {
        const char *name = va_arg(ap, const char *);
        struct rte_kvargs *kvargs = va_arg(ap, struct rte_kvargs *);
+       int force = va_arg(ap, int);
        unsigned int specified = va_arg(ap, unsigned int);
        unsigned int *matched = va_arg(ap, unsigned int *);
        unsigned int i;
@@ -490,20 +560,19 @@ vdev_netvsc_netvsc_probe(const struct if_nameindex *iface,
                return 0;
        }
        if (!vdev_netvsc_iface_is_netvsc(iface)) {
-               if (!specified)
+               if (!specified || !force)
                        return 0;
                DRV_LOG(WARNING,
-                       "interface \"%s\" (index %u) is not NetVSC,"
-                       " skipping",
+                       "using non-NetVSC interface \"%s\" (index %u)",
                        iface->if_name, iface->if_index);
-               return 0;
        }
        /* Routed NetVSC should not be probed. */
-       if (vdev_netvsc_has_route(iface->if_name)) {
-               DRV_LOG(WARNING, "NetVSC interface \"%s\" (index %u) is routed",
-                       iface->if_name, iface->if_index);
+       if (vdev_netvsc_has_route(iface, AF_INET) ||
+           vdev_netvsc_has_route(iface, AF_INET6)) {
                if (!specified)
                        return 0;
+               DRV_LOG(WARNING, "probably using routed NetVSC interface \"%s\""
+                       " (index %u)", iface->if_name, iface->if_index);
        }
        /* Create interface context. */
        ctx = calloc(1, sizeof(*ctx));
@@ -545,13 +614,13 @@ vdev_netvsc_netvsc_probe(const struct if_nameindex *iface,
                       name, ctx->id);
        if (ret == -1 || (size_t)ret >= sizeof(ctx->name))
                ++i;
-       ret = snprintf(ctx->devname, sizeof(ctx->devname), "net_failsafe_%s",
-                      ctx->name);
+       ret = snprintf(ctx->devname, sizeof(ctx->devname), "net_failsafe_vsc%u",
+                      ctx->id);
        if (ret == -1 || (size_t)ret >= sizeof(ctx->devname))
                ++i;
        ret = snprintf(ctx->devargs, sizeof(ctx->devargs),
-                      "fd(%d),dev(net_tap_%s,remote=%s)",
-                      ctx->pipe[0], ctx->name, ctx->if_name);
+                      "fd(%d),dev(net_tap_vsc%u,remote=%s)",
+                      ctx->pipe[0], ctx->id, ctx->if_name);
        if (ret == -1 || (size_t)ret >= sizeof(ctx->devargs))
                ++i;
        if (i) {
@@ -597,6 +666,8 @@ vdev_netvsc_vdev_probe(struct rte_vdev_device *dev)
        static const char *const vdev_netvsc_arg[] = {
                VDEV_NETVSC_ARG_IFACE,
                VDEV_NETVSC_ARG_MAC,
+               VDEV_NETVSC_ARG_FORCE,
+               VDEV_NETVSC_ARG_IGNORE,
                NULL,
        };
        const char *name = rte_vdev_device_name(dev);
@@ -605,6 +676,8 @@ vdev_netvsc_vdev_probe(struct rte_vdev_device *dev)
                                                     vdev_netvsc_arg);
        unsigned int specified = 0;
        unsigned int matched = 0;
+       int force = 0;
+       int ignore = 0;
        unsigned int i;
        int ret;
 
@@ -616,14 +689,23 @@ vdev_netvsc_vdev_probe(struct rte_vdev_device *dev)
        for (i = 0; i != kvargs->count; ++i) {
                const struct rte_kvargs_pair *pair = &kvargs->pairs[i];
 
-               if (!strcmp(pair->key, VDEV_NETVSC_ARG_IFACE) ||
-                   !strcmp(pair->key, VDEV_NETVSC_ARG_MAC))
+               if (!strcmp(pair->key, VDEV_NETVSC_ARG_FORCE))
+                       force = !!atoi(pair->value);
+               else if (!strcmp(pair->key, VDEV_NETVSC_ARG_IGNORE))
+                       ignore = !!atoi(pair->value);
+               else if (!strcmp(pair->key, VDEV_NETVSC_ARG_IFACE) ||
+                        !strcmp(pair->key, VDEV_NETVSC_ARG_MAC))
                        ++specified;
        }
+       if (ignore) {
+               if (kvargs)
+                       rte_kvargs_free(kvargs);
+               return 0;
+       }
        rte_eal_alarm_cancel(vdev_netvsc_alarm, NULL);
        /* Gather interfaces. */
        ret = vdev_netvsc_foreach_iface(vdev_netvsc_netvsc_probe, name, kvargs,
-                                       specified, &matched);
+                                       force, specified, &matched);
        if (ret < 0)
                goto error;
        if (matched < specified)
@@ -682,7 +764,9 @@ RTE_PMD_REGISTER_VDEV(VDEV_NETVSC_DRIVER, vdev_netvsc_vdev);
 RTE_PMD_REGISTER_ALIAS(VDEV_NETVSC_DRIVER, eth_vdev_netvsc);
 RTE_PMD_REGISTER_PARAM_STRING(net_vdev_netvsc,
                              VDEV_NETVSC_ARG_IFACE "=<string> "
-                             VDEV_NETVSC_ARG_MAC "=<string>");
+                             VDEV_NETVSC_ARG_MAC "=<string> "
+                             VDEV_NETVSC_ARG_FORCE "=<int> "
+                             VDEV_NETVSC_ARG_IGNORE "=<int>");
 
 /** Initialize driver log type. */
 RTE_INIT(vdev_netvsc_init_log)
@@ -691,3 +775,41 @@ RTE_INIT(vdev_netvsc_init_log)
        if (vdev_netvsc_logtype >= 0)
                rte_log_set_level(vdev_netvsc_logtype, RTE_LOG_NOTICE);
 }
+
+/** Compare function for vdev find device operation. */
+static int
+vdev_netvsc_cmp_rte_device(const struct rte_device *dev1,
+                          __rte_unused const void *_dev2)
+{
+       return strcmp(dev1->devargs->name, VDEV_NETVSC_DRIVER_NAME);
+}
+
+/**
+ * A callback called by vdev bus scan function to ensure this driver probing
+ * automatically in Hyper-V VM system unless it already exists in the
+ * devargs list.
+ */
+static void
+vdev_netvsc_scan_callback(__rte_unused void *arg)
+{
+       struct rte_vdev_device *dev;
+       struct rte_devargs *devargs;
+       struct rte_bus *vbus = rte_bus_find_by_name("vdev");
+
+       RTE_EAL_DEVARGS_FOREACH("vdev", devargs)
+               if (!strcmp(devargs->name, VDEV_NETVSC_DRIVER_NAME))
+                       return;
+       dev = (struct rte_vdev_device *)vbus->find_device(NULL,
+               vdev_netvsc_cmp_rte_device, VDEV_NETVSC_DRIVER_NAME);
+       if (dev)
+               return;
+       if (rte_devargs_add(RTE_DEVTYPE_VIRTUAL, VDEV_NETVSC_DRIVER_NAME))
+               DRV_LOG(ERR, "unable to add netvsc devargs.");
+}
+
+/** Initialize the custom scan. */
+RTE_INIT(vdev_netvsc_custom_scan_add)
+{
+       if (rte_hypervisor_get() == RTE_HYPERVISOR_HYPERV)
+               rte_vdev_add_custom_scan(vdev_netvsc_scan_callback, NULL);
+}