vlandev: New library for working with Linux VLAN devices.
authorBen Pfaff <blp@nicira.com>
Thu, 10 Nov 2011 00:16:10 +0000 (16:16 -0800)
committerBen Pfaff <blp@nicira.com>
Wed, 23 Nov 2011 23:32:36 +0000 (15:32 -0800)
lib/automake.mk
lib/netdev-linux.c
lib/netdev-linux.h
lib/vlandev.c [new file with mode: 0644]
lib/vlandev.h [new file with mode: 0644]

index da95acb9cca1e703a260c13c8ef80dd4d8101a63..c0cc9065aaea764e3ad72fb0cb6993a6e1db6441 100644 (file)
@@ -181,6 +181,8 @@ lib_libopenvswitch_a_SOURCES = \
        lib/vconn.h \
        lib/vlan-bitmap.c \
        lib/vlan-bitmap.h \
+       lib/vlandev.c \
+       lib/vlandev.h \
        lib/vlog.c \
        lib/vlog.h
 nodist_lib_libopenvswitch_a_SOURCES = \
index 134d99b415f535f157513d17c34f00562877c7f4..44167da4a4a4c4af6d07b65b921273d6ee0876ea 100644 (file)
@@ -3932,6 +3932,14 @@ tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes)
 \f
 /* Linux-only functions declared in netdev-linux.h  */
 
+/* Returns a fd for an AF_INET socket or a negative errno value. */
+int
+netdev_linux_get_af_inet_sock(void)
+{
+    int error = netdev_linux_init();
+    return error ? -error : af_inet_sock;
+}
+
 /* Modifies the 'flag' bit in ethtool's flags field for 'netdev'.  If
  * 'enable' is true, the bit is set.  Otherwise, it is cleared. */
 int
index c8caa4e3cd4c220ebe147a2cd44a247e12300a76..21a2db2587735e1117d10b32160fe0e96ac1488d 100644 (file)
@@ -29,5 +29,6 @@ struct rtnl_link_stats;
 
 int netdev_linux_ethtool_set_flag(struct netdev *netdev, uint32_t flag,
                                   const char *flag_name, bool enable);
+int netdev_linux_get_af_inet_sock(void);
 
 #endif /* netdev-linux.h */
diff --git a/lib/vlandev.c b/lib/vlandev.c
new file mode 100644 (file)
index 0000000..736b779
--- /dev/null
@@ -0,0 +1,247 @@
+/*
+ * Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+
+#include "vlandev.h"
+
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+
+#include "hash.h"
+#include "rtnetlink-link.h"
+#include "shash.h"
+#include "vlog.h"
+
+VLOG_DEFINE_THIS_MODULE(vlandev);
+
+#ifdef __linux__
+#include <linux/if_vlan.h>
+#include <linux/sockios.h>
+#include "netdev-linux.h"
+
+static struct nln_notifier *vlan_cache_notifier;
+static struct shash vlan_devs = SHASH_INITIALIZER(&vlan_devs);
+static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs);
+static bool cache_valid;
+
+static void
+vlan_cache_cb(const struct rtnetlink_link_change *change OVS_UNUSED,
+              void *aux OVS_UNUSED)
+{
+    cache_valid = false;
+}
+
+int
+vlandev_refresh(void)
+{
+    const char *fn = "/proc/net/vlan/config";
+    struct shash_node *node;
+    char line[128];
+    FILE *stream;
+
+    if (!vlan_cache_notifier) {
+        vlan_cache_notifier = rtnetlink_link_notifier_create(vlan_cache_cb,
+                                                             NULL);
+        if (!vlan_cache_notifier) {
+            return EINVAL;
+        }
+    }
+
+    if (cache_valid) {
+        return 0;
+    }
+
+    /* Free old cache.
+     *
+     * The 'name' members point to strings owned by the "shash"es so we do not
+     * free them ourselves. */
+    shash_clear_free_data(&vlan_devs);
+    SHASH_FOR_EACH (node, &vlan_real_devs) {
+        struct vlan_real_dev *vrd = node->data;
+
+        hmap_destroy(&vrd->vlan_devs);
+    }
+    shash_clear_free_data(&vlan_real_devs);
+
+    /* Repopulate cache. */
+    stream = fopen(fn, "r");
+    if (!stream) {
+        static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+        int error = errno;
+        struct stat s;
+
+        if (error == ENOENT && !stat("/proc", &s)) {
+            /* Probably the vlan module just isn't loaded, and probably that's
+             * because no VLAN devices have been created.
+             *
+             * Not really an error. */
+            return 0;
+        }
+
+        VLOG_WARN_RL(&rl, "%s: open failed (%s)", fn, strerror(error));
+        return error;
+    }
+
+    while (fgets(line, sizeof line, stream)) {
+        char vlan_dev[16], real_dev[16];
+        int vid;
+
+        if (sscanf(line, "%15[^ |] | %d | %15s", vlan_dev, &vid, real_dev) == 3
+            && vid >= 0 && vid <= 4095
+            && !shash_find(&vlan_devs, vlan_dev)) {
+            struct vlan_real_dev *vrd;
+            struct vlan_dev *vd;
+
+            vrd = shash_find_data(&vlan_real_devs, real_dev);
+            if (!vrd) {
+                vrd = xmalloc(sizeof *vrd);
+                vrd->name = xstrdup(real_dev);
+                hmap_init(&vrd->vlan_devs);
+                shash_add_nocopy(&vlan_real_devs, vrd->name, vrd);
+            }
+
+            vd = xmalloc(sizeof *vd);
+            hmap_insert(&vrd->vlan_devs, &vd->hmap_node, hash_int(vid, 0));
+            vd->name = xstrdup(vlan_dev);
+            vd->vid = vid;
+            vd->real_dev = vrd;
+            shash_add_nocopy(&vlan_devs, vd->name, vd);
+        }
+    }
+    fclose(stream);
+
+    cache_valid = true;
+    return 0;
+}
+
+struct shash *
+vlandev_get_real_devs(void)
+{
+    return &vlan_real_devs;
+}
+
+const char *
+vlandev_get_name(const char *real_dev_name, int vid)
+{
+    const struct vlan_real_dev *real_dev;
+
+    real_dev = shash_find_data(&vlan_real_devs, real_dev_name);
+    if (real_dev) {
+        const struct vlan_dev *vlan_dev;
+
+        HMAP_FOR_EACH_WITH_HASH (vlan_dev, hmap_node, hash_int(vid, 0),
+                                 &real_dev->vlan_devs) {
+            if (vlan_dev->vid == vid) {
+                return vlan_dev->name;
+            }
+        }
+    }
+
+    return NULL;
+}
+
+static int
+do_vlan_ioctl(const char *netdev_name, struct vlan_ioctl_args *via,
+              int cmd, const char *cmd_name)
+{
+    static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 1);
+    int error;
+    int sock;
+
+    via->cmd = cmd;
+    ovs_strlcpy(via->device1, netdev_name, sizeof via->device1);
+
+    sock = netdev_linux_get_af_inet_sock();
+    if (sock < 0) {
+        return -sock;
+    }
+
+    error = ioctl(sock, SIOCSIFVLAN, via) < 0 ? errno : 0;
+    if (error) {
+        VLOG_WARN_RL(&rl, "%s: VLAN ioctl %s failed (%s)",
+                     netdev_name, cmd_name, strerror(error));
+    }
+    return error;
+}
+
+int
+vlandev_add(const char *real_dev, int vid)
+{
+    struct vlan_ioctl_args via;
+    int error;
+
+    memset(&via, 0, sizeof via);
+    via.u.VID = vid;
+
+    error = do_vlan_ioctl(real_dev, &via, ADD_VLAN_CMD, "ADD_VLAN_CMD");
+    if (!error) {
+        cache_valid = false;
+    }
+    return error;
+}
+
+int
+vlandev_del(const char *vlan_dev)
+{
+    struct vlan_ioctl_args via;
+    int error;
+
+    memset(&via, 0, sizeof via);
+    error = do_vlan_ioctl(vlan_dev, &via, DEL_VLAN_CMD, "DEL_VLAN_CMD");
+    if (!error) {
+        cache_valid = false;
+    }
+    return error;
+}
+#else  /* !__linux__ */
+/* Stubs. */
+
+int
+vlandev_refresh(void)
+{
+    return 0;
+}
+
+struct shash *
+vlandev_get_real_devs(void)
+{
+    static struct shash vlan_real_devs = SHASH_INITIALIZER(&vlan_real_devs);
+
+    return &vlan_real_devs;
+}
+
+const char *
+vlandev_get_name(const char *real_dev_name OVS_UNUSED, int vid OVS_UNUSED)
+{
+    return NULL;
+}
+
+int
+vlandev_add(const char *real_dev OVS_UNUSED, int vid OVS_UNUSED)
+{
+    VLOG_ERR("not supported on non-Linux platform");
+    return EOPNOTSUPP;
+}
+
+int
+vlandev_del(const char *vlan_dev OVS_UNUSED)
+{
+    VLOG_ERR("not supported on non-Linux platform");
+    return EOPNOTSUPP;
+}
+#endif
diff --git a/lib/vlandev.h b/lib/vlandev.h
new file mode 100644 (file)
index 0000000..cba0fa4
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2011 Nicira Networks.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VLANDEV_H
+#define VLANDEV_H 1
+
+#include "hmap.h"
+
+/* Linux VLAN device support (e.g. "eth0.10" for VLAN 10.)
+ *
+ * This is deprecated.  It is only for compatibility with broken device
+ * drivers in old versions of Linux that do not properly support VLANs when
+ * VLAN devices are not used.  When broken device drivers are no longer in
+ * widespread use, we will delete these interfaces. */
+
+/* A VLAN device (e.g. "eth0.10" for VLAN 10 on eth0). */
+struct vlan_dev {
+    struct vlan_real_dev *real_dev; /* Parent, e.g. "eth0". */
+    struct hmap_node hmap_node;     /* In vlan_real_dev's "vlan_devs" map. */
+    char *name;                     /* VLAN device name, e.g. "eth0.10". */
+    int vid;                        /* VLAN ID, e.g. 10. */
+};
+
+/* A device that has VLAN devices broken out of it. */
+struct vlan_real_dev {
+    char *name;                 /* Name, e.g. "eth0". */
+    struct hmap vlan_devs;      /* All child VLAN devices, hashed by VID. */
+};
+
+int vlandev_refresh(void);
+
+struct shash *vlandev_get_real_devs(void);
+
+const char *vlandev_get_name(const char *real_dev_name, int vid);
+
+int vlandev_add(const char *real_dev, int vid);
+int vlandev_del(const char *vlan_dev);
+
+#endif /* vlandev.h */