brcompat: Add /proc/net/vlan, /proc/net/bonding compatibility support.
authorBen Pfaff <blp@nicira.com>
Fri, 24 Apr 2009 18:12:10 +0000 (11:12 -0700)
committerBen Pfaff <blp@nicira.com>
Tue, 28 Apr 2009 17:21:52 +0000 (10:21 -0700)
This adds a kernel interface, controlled by userspace through Generic
Netlink, to create, modify, and delete files in /proc/net/vlan and
/proc/net/bonding, plus vswitch support for updating files in those
directories to fit better into legacy environments that expect specific
files to appear in those directories.

We hope that the need for this support will be temporary.

13 files changed:
datapath/brc_procfs.c [new file with mode: 0644]
datapath/brc_procfs.h [new file with mode: 0644]
datapath/brcompat.c
datapath/linux-2.6/Modules.mk
datapath/linux-2.6/compat-2.6/include/linux/netdevice.h
include/openflow/brcompat-netlink.h
lib/vlog-modules.def
vswitchd/automake.mk
vswitchd/bridge.c
vswitchd/proc-net-compat.c [new file with mode: 0644]
vswitchd/proc-net-compat.h [new file with mode: 0644]
vswitchd/vswitchd.8.in
vswitchd/vswitchd.c

diff --git a/datapath/brc_procfs.c b/datapath/brc_procfs.c
new file mode 100644 (file)
index 0000000..3455da1
--- /dev/null
@@ -0,0 +1,180 @@
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
+#include <net/genetlink.h>
+#include "openflow/brcompat-netlink.h"
+
+/* This code implements a Generic Netlink command BRC_GENL_C_SET_PROC that can
+ * be used to add, modify, and delete arbitrary files in selected
+ * subdirectories of /proc.  It's a horrible kluge prompted by the need to
+ * simulate certain /proc/net/vlan and /proc/net/bonding files for software
+ * that wants to read them, and with any luck it will go away eventually.
+ *
+ * The implementation is a kluge too.  In particular, we want to release the
+ * strings copied into the 'data' members of proc_dir_entry when the
+ * proc_dir_entry structures are freed, but there doesn't appear to be a way to
+ * hook that, so instead we have to rely on being the only entity modifying the
+ * directories in question.
+ */
+
+static int brc_seq_show(struct seq_file *seq, void *unused)
+{
+       seq_puts(seq, seq->private);
+       return 0;
+}
+
+static int brc_seq_open(struct inode *inode, struct file *file)
+{
+       return single_open(file, brc_seq_show, PDE(inode)->data);
+}
+
+static struct file_operations brc_fops = {
+       .owner = THIS_MODULE,
+       .open    = brc_seq_open,
+       .read    = seq_read,
+       .llseek  = seq_lseek,
+       .release = single_release,
+};
+
+static struct proc_dir_entry *proc_vlan_dir;
+static struct proc_dir_entry *proc_bonding_dir;
+
+struct proc_dir_entry *brc_lookup_entry(struct proc_dir_entry *de, const char *name)
+{
+       int namelen = strlen(name);
+       for (de = de->subdir; de; de = de->next) {
+               if (de->namelen != namelen)
+                       continue;
+               if (!memcmp(name, de->name, de->namelen))
+                       return de;
+       }
+       return NULL;
+}
+
+static struct proc_dir_entry *brc_open_dir(const char *dir_name,
+                                          struct proc_dir_entry *parent,
+                                          struct proc_dir_entry **dirp)
+{
+       if (!*dirp) {
+               struct proc_dir_entry *dir;
+               if (brc_lookup_entry(parent, dir_name)) {
+                       printk(KERN_WARNING "%s proc directory exists, can't "
+                              "simulate--probably its real module is "
+                              "loaded\n", dir_name);
+                       return NULL;
+               }
+               dir = *dirp = proc_mkdir(dir_name, parent);
+       }
+       return *dirp;
+}
+
+int brc_genl_set_proc(struct sk_buff *skb, struct genl_info *info)
+{
+       struct proc_dir_entry *dir, *entry;
+       const char *dir_name, *name;
+       char *data;
+
+       /* The following would be much simpler if we could depend on supporting
+        * NLA_NUL_STRING and the .len member in Generic Netlink policy, but
+        * upstream 2.6.18 does not have either.  */
+       if (!info->attrs[BRC_GENL_A_PROC_DIR] ||
+           VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_DIR]) ||
+           !info->attrs[BRC_GENL_A_PROC_NAME] ||
+           VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_NAME]) ||
+           (info->attrs[BRC_GENL_A_PROC_DATA] &&
+            VERIFY_NUL_STRING(info->attrs[BRC_GENL_A_PROC_DATA])))
+               return -EINVAL;
+
+       dir_name = nla_data(info->attrs[BRC_GENL_A_PROC_DIR]);
+       name = nla_data(info->attrs[BRC_GENL_A_PROC_NAME]);
+       if (strlen(dir_name) > 32 || strlen(name) > 32)
+               return -EINVAL;
+
+       if (!strcmp(dir_name, "net/vlan"))
+               dir = brc_open_dir("vlan", proc_net, &proc_vlan_dir);
+       else if (!strcmp(dir_name, "net/bonding"))
+               dir = brc_open_dir("bonding", proc_net, &proc_bonding_dir);
+       else
+               return -EINVAL;
+       if (!dir) {
+               /* Probably failed because the module that really implements
+                * the function in question is loaded and already owns the
+                * directory in question.*/
+               return -EBUSY;
+       }
+
+       entry = brc_lookup_entry(dir, name);
+       if (!info->attrs[BRC_GENL_A_PROC_DATA]) {
+               if (!entry)
+                       return -ENOENT;
+
+               data = entry->data;
+               remove_proc_entry(name, dir);
+               if (brc_lookup_entry(dir, name))
+                       return -EBUSY; /* Shouldn't happen */
+
+               kfree(data);
+       } else {
+               data = kstrdup(nla_data(info->attrs[BRC_GENL_A_PROC_DATA]),
+                              GFP_KERNEL);
+               if (!data)
+                       return -ENOMEM;
+
+               if (entry) {
+                       char *old_data = entry->data;
+                       entry->data = data;
+                       kfree(old_data);
+                       return 0;
+               }
+
+               entry = create_proc_entry(name, S_IFREG|S_IRUSR|S_IWUSR, dir);
+               if (!entry) {
+                       kfree(data);
+                       return -ENOBUFS;
+               }
+               entry->proc_fops = &brc_fops;
+               entry->data = data;
+       }
+       return 0;
+}
+
+static void kill_proc_dir(const char *dir_name,
+                         struct proc_dir_entry *parent,
+                         struct proc_dir_entry *dir)
+{
+       if (!dir)
+               return;
+       for (;;) {
+               struct proc_dir_entry *e;
+               char *data;
+               char name[33];
+
+               e = dir->subdir;
+               if (!e)
+                       break;
+
+               if (e->namelen >= sizeof name) {
+                       /* Can't happen: we prevent adding names this long by
+                        * limiting the BRC_GENL_A_PROC_NAME string to 32
+                        * bytes.  */
+                       WARN_ON(1);
+                       break;
+               }
+               strcpy(name, e->name);
+
+               data = e->data;
+               e->data = NULL;
+               kfree(data);
+
+               remove_proc_entry(name, dir);
+       }
+       remove_proc_entry(dir_name, parent);
+}
+
+void brc_procfs_exit(void)
+{
+       kill_proc_dir("vlan", proc_net, proc_vlan_dir);
+       kill_proc_dir("bonding", proc_net, proc_bonding_dir);
+}
diff --git a/datapath/brc_procfs.h b/datapath/brc_procfs.h
new file mode 100644 (file)
index 0000000..93e21cf
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef BRC_PROCFS_H
+#define BRC_PROCFS_H 1
+
+struct sk_buff;
+struct genl_info;
+
+void brc_procfs_exit(void);
+int brc_genl_set_proc(struct sk_buff *skb, struct genl_info *info);
+
+#endif /* brc_procfs.h */
+
index 46ecce005663d481e59238b96aaa65fc440bbfc5..3817ba84d87f02b22df7073646a92e5b1e8b77d1 100644 (file)
@@ -12,6 +12,7 @@
 #include "compat.h"
 #include "openflow/openflow-netlink.h"
 #include "openflow/brcompat-netlink.h"
+#include "brc_procfs.h"
 #include "brc_sysfs.h"
 #include "datapath.h"
 #include "dp_dev.h"
@@ -288,7 +289,10 @@ static struct genl_ops brc_genl_ops_query_dp = {
 
 /* Attribute policy: what each attribute may contain.  */
 static struct nla_policy brc_genl_policy[BRC_GENL_A_MAX + 1] = {
-       [BRC_GENL_A_ERR_CODE] = { .type = NLA_U32 }
+       [BRC_GENL_A_ERR_CODE] = { .type = NLA_U32 },
+       [BRC_GENL_A_PROC_DIR] = { .type = NLA_NUL_STRING },
+       [BRC_GENL_A_PROC_NAME] = { .type = NLA_NUL_STRING },
+       [BRC_GENL_A_PROC_DATA] = { .type = NLA_NUL_STRING },
 };
 
 static int
@@ -311,6 +315,14 @@ static struct genl_ops brc_genl_ops_dp_result = {
        .dumpit = NULL
 };
 
+static struct genl_ops brc_genl_ops_set_proc = {
+       .cmd = BRC_GENL_C_SET_PROC,
+       .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privelege. */
+       .policy = brc_genl_policy,
+       .doit = brc_genl_set_proc,
+       .dumpit = NULL
+};
+
 int dp_exists(const char *dp_name)
 {
        struct net_device *dev;
@@ -524,6 +536,10 @@ __init brc_init(void)
        if (err != 0) 
                goto err_unregister;
 
+       err = genl_register_ops(&brc_genl_family, &brc_genl_ops_set_proc);
+       if (err != 0) 
+               goto err_unregister;
+
        strcpy(brc_mc_group.name, "brcompat");
        err = genl_register_mc_group(&brc_genl_family, &brc_mc_group);
        if (err < 0)
@@ -554,6 +570,7 @@ brc_cleanup(void)
        brioctl_set(NULL);
 
        genl_unregister_family(&brc_genl_family);
+       brc_procfs_exit();
 }
 
 module_init(brc_init);
index 18c0749e69d6c61efa8be84728b2beca50287b15..ad302a8d90d9f0c8c7a3a2013adaf2336c5c7707 100644 (file)
@@ -35,9 +35,11 @@ both_modules += brcompat
 brcompat_sources = \
        linux-2.6/compat-2.6/genetlink-brcompat.c \
        brcompat.c \
+       brc_procfs.c \
        brc_sysfs_dp.c \
        brc_sysfs_if.c 
 brcompat_headers = \
+       brc_procfs.h \
        brc_sysfs.h 
 
 dist_modules += veth
index 042e30d7543d0a88a694b19425de82d1413f4cf8..32e1735dcd84d73049762a19aa69414d22c3f6a6 100644 (file)
@@ -17,6 +17,10 @@ struct net *dev_net(const struct net_device *dev)
 }
 #endif /* linux kernel < 2.6.26 */
 
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,24)
+#define proc_net init_net.proc_net
+#endif
+
 #ifndef for_each_netdev
 /* Linux before 2.6.22 didn't have for_each_netdev at all. */
 #define for_each_netdev(net, d) for (d = dev_base; d; d = d->next)
@@ -26,4 +30,6 @@ struct net *dev_net(const struct net_device *dev)
 #define for_each_netdev(net,d) list_for_each_entry(d, &dev_base_head, dev_list)
 #endif
 
+
+
 #endif
index 8447960be5619ba080587eb69f20d0b93e9d6d7f..1f10f2dc6bf17f8c20bf698f0db5fd3c6dfa98da 100644 (file)
@@ -43,6 +43,9 @@ enum {
        BRC_GENL_A_PORT_NAME,   /* Interface name. */
        BRC_GENL_A_ERR_CODE,    /* Positive error code. */
        BRC_GENL_A_MC_GROUP,    /* Generic netlink multicast group. */
+       BRC_GENL_A_PROC_DIR,    /* Name of subdirectory in /proc. */
+       BRC_GENL_A_PROC_NAME,   /* Name of file in /proc. */
+       BRC_GENL_A_PROC_DATA,   /* Contents of file in /proc. */
 
        __BRC_GENL_A_MAX,
        BRC_GENL_A_MAX = __BRC_GENL_A_MAX - 1
@@ -62,6 +65,7 @@ enum brc_genl_command {
        BRC_GENL_C_PORT_ADD,    /* K: Port added to datapath. */
        BRC_GENL_C_PORT_DEL,    /* K: Port removed from datapath. */
        BRC_GENL_C_QUERY_MC,    /* U: Get multicast group for brcompat. */
+       BRC_GENL_C_SET_PROC,    /* U: Set contents of file in /proc. */
 
        __BRC_GENL_C_MAX,
        BRC_GENL_C_MAX = __BRC_GENL_C_MAX - 1
index e6b738111994761936dcb26e9609101f84773926..94f28d792d5100933ffc488ebab9beb56e8505aa 100644 (file)
@@ -32,6 +32,7 @@ VLOG_MODULE(pktbuf)
 VLOG_MODULE(pcap)
 VLOG_MODULE(poll_loop)
 VLOG_MODULE(port_watcher)
+VLOG_MODULE(proc_net_compat)
 VLOG_MODULE(process)
 VLOG_MODULE(secchan)
 VLOG_MODULE(rconn)
index ecc44276e94db0f2a839dc6d2d86c8fb83fd5b81..86573a974411329cfb25365f990f0bb05722e2b0 100644 (file)
@@ -13,6 +13,7 @@ vswitchd_vswitchd_SOURCES = \
        vswitchd/mgmt.h \
        vswitchd/port.c \
        vswitchd/port.h \
+       vswitchd/proc-net-compat.c \
        vswitchd/vswitchd.c \
        vswitchd/vswitchd.h \
        vswitchd/xenserver.c \
index 684eaaafd23713509b17d4d396763987f4ce5a2e..69e1a5816429977dd8da190d5f6acdbac5147d32 100644 (file)
@@ -55,6 +55,7 @@
 #include "ofpbuf.h"
 #include "poll-loop.h"
 #include "port-array.h"
+#include "proc-net-compat.h"
 #include "process.h"
 #include "secchan/ofproto.h"
 #include "socket-util.h"
@@ -218,6 +219,8 @@ static void port_destroy(struct port *);
 static struct port *port_lookup(const struct bridge *, const char *name);
 static struct port *port_from_dp_ifidx(const struct bridge *,
                                        uint16_t dp_ifidx);
+static void port_update_bond_compat(struct port *);
+static void port_update_vlan_compat(struct port *);
 
 static void mirror_create(struct bridge *, const char *name);
 static void mirror_destroy(struct mirror *);
@@ -524,6 +527,12 @@ bridge_reconfigure(void)
             i++;
         }
     }
+    LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
+        for (i = 0; i < br->n_ports; i++) {
+            struct port *port = br->ports[i];
+            port_update_vlan_compat(port);
+        }
+    }
     LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
         brstp_reconfigure(br);
     }
@@ -1757,11 +1766,12 @@ bridge_port_changed_ofhook_cb(enum ofp_port_reason reason,
 
         bridge_flush(br);
     } else {
+        memcpy(iface->mac, opp->hw_addr, ETH_ADDR_LEN);
         if (port->n_ifaces > 1) {
             bool up = !(opp->state & OFPPS_LINK_DOWN);
             bond_link_status_update(iface, up);
+            port_update_bond_compat(port);
         }
-        memcpy(iface->mac, opp->hw_addr, ETH_ADDR_LEN);
     }
 }
 
@@ -1943,6 +1953,8 @@ port_destroy(struct port *port)
         struct port *del;
         size_t i;
 
+        proc_net_compat_update_vlan(port->name, NULL, 0);
+
         for (i = 0; i < MAX_MIRRORS; i++) {
             struct mirror *m = br->mirrors[i];
             if (m && m->out_port == port) {
@@ -1991,8 +2003,11 @@ port_update_bonding(struct port *port)
 {
     if (port->n_ifaces < 2) {
         /* Not a bonded port. */
-        free(port->bond_hash);
-        port->bond_hash = NULL;
+        if (port->bond_hash) {
+            free(port->bond_hash);
+            port->bond_hash = NULL;
+            proc_net_compat_update_bond(port->name, NULL);
+        }
     } else {
         if (!port->bond_hash) {
             size_t i;
@@ -2006,7 +2021,74 @@ port_update_bonding(struct port *port)
             port->no_ifaces_tag = tag_create_random();
             bond_choose_active_iface(port);
         }
+        port_update_bond_compat(port);
+    }
+}
+
+static void
+port_update_bond_compat(struct port *port)
+{
+    struct compat_bond bond;
+    size_t i;
+
+    if (port->n_ifaces < 2) {
+        return;
+    }
+
+    bond.up = false;
+    bond.updelay = port->updelay;
+    bond.downdelay = port->downdelay;
+    bond.n_slaves = port->n_ifaces;
+    bond.slaves = xmalloc(port->n_ifaces * sizeof *bond.slaves);
+    for (i = 0; i < port->n_ifaces; i++) {
+        struct iface *iface = port->ifaces[i];
+        struct compat_bond_slave *slave = &bond.slaves[i];
+        slave->name = iface->name;
+        slave->up = ((iface->enabled && iface->delay_expires == LLONG_MAX) ||
+                     (!iface->enabled && iface->delay_expires != LLONG_MAX));
+        if (slave->up) {
+            bond.up = true;
+        }
+        memcpy(slave->mac, iface->mac, ETH_ADDR_LEN);
+    }
+    proc_net_compat_update_bond(port->name, &bond);
+    free(bond.slaves);
+}
+
+static void
+port_update_vlan_compat(struct port *port)
+{
+    struct bridge *br = port->bridge;
+    char *vlandev_name = NULL;
+
+    if (port->vlan > 0) {
+        /* Figure out the name that the VLAN device should actually have, if it
+         * existed.  This takes some work because the VLAN device would not
+         * have port->name in its name; rather, it would have the trunk port's
+         * name, and 'port' would be attached to a bridge that also had the
+         * VLAN device one of its ports.  So we need to find a trunk port that
+         * includes port->vlan.
+         *
+         * There might be more than one candidate.  This doesn't happen on
+         * XenServer, so if it happens we just pick the first choice in
+         * alphabetical order instead of creating multiple VLAN devices. */
+        size_t i;
+        for (i = 0; i < br->n_ports; i++) {
+            struct port *p = br->ports[i];
+            if (port_trunks_vlan(p, port->vlan)
+                && p->n_ifaces
+                && (!vlandev_name || strcmp(p->name, vlandev_name) <= 0))
+            {
+                const uint8_t *ea = p->ifaces[0]->mac;
+                if (!eth_addr_is_multicast(ea) &&
+                    !eth_addr_is_reserved(ea) &&
+                    !eth_addr_is_zero(ea)) {
+                    vlandev_name = p->name;
+                }
+            }
+        }
     }
+    proc_net_compat_update_vlan(port->name, vlandev_name, port->vlan);
 }
 \f
 /* Interface functions. */
diff --git a/vswitchd/proc-net-compat.c b/vswitchd/proc-net-compat.c
new file mode 100644 (file)
index 0000000..a4f0f22
--- /dev/null
@@ -0,0 +1,344 @@
+/* Copyright (c) 2009  Nicira Networks
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include <config.h>
+#include "proc-net-compat.h"
+#include <assert.h>
+#include <dirent.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <string.h>
+#include "dynamic-string.h"
+#include "hash.h"
+#include "netlink-protocol.h"
+#include "netlink.h"
+#include "ofpbuf.h"
+#include "openflow/brcompat-netlink.h"
+#include "hmap.h"
+#include "shash.h"
+#include "svec.h"
+
+#define THIS_MODULE VLM_proc_net_compat
+#include "vlog.h"
+
+/* Netlink socket to bridge compatibility kernel module. */
+static struct nl_sock *brc_sock;
+
+/* The Generic Netlink family number used for bridge compatibility. */
+static int brc_family = 0;
+
+/* Rate limiting for log messages. */
+static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
+
+static void flush_dir(const char *dir);
+static int set_proc_file(const char *dir, const char *file, const char *data);
+
+/* Initializes the /proc/net compatibility layer.  Returns 0 if successful,
+ * otherwise a positive errno value. */
+int
+proc_net_compat_init(void)
+{
+    if (!brc_sock) {
+        int retval = nl_lookup_genl_family(BRC_GENL_FAMILY_NAME, &brc_family);
+        if (retval) {
+            return retval;
+        }
+
+        retval = nl_sock_create(NETLINK_GENERIC, 0, 0, 0, &brc_sock);
+        if (retval) {
+            return retval;
+        }
+
+        flush_dir("/proc/net/vlan");
+        flush_dir("/proc/net/bonding");
+    }
+    return 0;
+}
+
+static int
+set_proc_file(const char *dir, const char *file, const char *data)
+{
+    struct ofpbuf request, *reply;
+    int retval;
+
+    ofpbuf_init(&request, 0);
+    nl_msg_put_genlmsghdr(&request, brc_sock, 1024, brc_family, NLM_F_REQUEST,
+                          BRC_GENL_C_SET_PROC, 1);
+    nl_msg_put_string(&request, BRC_GENL_A_PROC_DIR, dir);
+    nl_msg_put_string(&request, BRC_GENL_A_PROC_NAME, file);
+    if (data) {
+        nl_msg_put_string(&request, BRC_GENL_A_PROC_DATA, data);
+    }
+
+    retval = nl_sock_transact(brc_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    ofpbuf_delete(reply);
+    if (retval) {
+        VLOG_WARN_RL(&rl, "failed to %s /proc/%s/%s (%s)",
+                     data ? "update" : "remove", dir, file, strerror(retval));
+    }
+    return retval;
+}
+
+static void
+flush_dir(const char *dir)
+{
+    const char *subdir;
+    struct dirent *de;
+    DIR *stream;
+
+    assert(!memcmp(dir, "/proc/", 6));
+    subdir = dir + 6;
+
+    stream = opendir(dir);
+    if (!stream) {
+        if (errno != ENOENT) {
+            VLOG_WARN_RL(&rl, "%s: open failed (%s)", dir, strerror(errno));
+        }
+        return;
+    }
+
+    while ((de = readdir(stream)) != NULL) {
+        if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
+            set_proc_file(subdir, de->d_name, NULL);
+        }
+    }
+    closedir(stream);
+}
+\f
+/* If 'bond' is nonnull, creates a file in /proc/net/bonding for a bond with
+ * the given 'name' and the details in 'bond'.  If 'bond' is null, deletes
+ * the /proc/net/bonding file with the given 'name'.
+ *
+ * This function has no effect unless proc_net_compat_init() has been
+ * called. */
+void
+proc_net_compat_update_bond(const char *name, const struct compat_bond *bond)
+{
+    struct ds ds;
+    int i;
+
+    if (!brc_sock) {
+        return;
+    }
+
+    if (!bond) {
+        set_proc_file("net/bonding", name, NULL);
+        return;
+    }
+
+    ds_init(&ds);
+    ds_put_format(
+        &ds,
+        "Ethernet Channel Bonding Driver: vswitchd "
+        VERSION BUILDNR" ("__DATE__" "__TIME__")\n"
+        "Bonding Mode: source load balancing\n"
+        "Primary Slave: None\n"
+        "Currently Active Slave: None\n"
+        "MII Status: %s\n"
+        "MII Polling Interval (ms): 100\n"
+        "Up Delay (ms): %d\n"
+        "Down Delay (ms): %d\n"
+        "\n"
+        "Source load balancing info:\n",
+        bond->up ? "up" : "down", bond->updelay, bond->downdelay);
+    for (i = 0; i < bond->n_slaves; i++) {
+        const struct compat_bond_slave *slave = &bond->slaves[i];
+        ds_put_format(
+            &ds,
+            "\n"
+            "Slave Interface: %s\n"
+            "MII Status: %s\n"
+            "Link Failure Count: 0\n"
+            "Permanent HW addr: "ETH_ADDR_FMT"\n",
+            slave->name, slave->up ? "up" : "down",
+            ETH_ADDR_ARGS(slave->mac));
+    }
+    set_proc_file("net/bonding", name, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+\f
+/* /proc/net/vlan compatibility.
+ *
+ * This is much more complex than I expected it to be. */
+
+struct compat_vlan {
+    /* Hash key. */
+    struct hmap_node trunk_node; /* Hash map node. */
+    char *trunk_dev;             /* Name of trunk network device. */
+    int vid;                     /* VLAN number. */
+
+    /* Auxiliary data. */
+    char *vlan_dev;             /* sprintf("%s.%d", trunk_dev, vid); */
+    struct svec tagged_devs;    /* Name of tagged network device(s). */
+};
+
+/* Current set of VLAN devices, indexed two different ways. */
+static struct hmap vlans_by_trunk = HMAP_INITIALIZER(&vlans_by_trunk);
+static struct shash vlans_by_tagged = SHASH_INITIALIZER(&vlans_by_tagged);
+
+static bool remove_tagged_dev(struct shash_node *, const char *tagged_dev);
+static void update_vlan_config(void);
+static void set_vlan_proc_file(const struct compat_vlan *);
+static uint32_t hash_vlan(const char *trunk_dev, uint32_t vid);
+
+/* Updates the /proc/net/vlan compatibility layer's idea of what trunk device
+ * and VLAN the given 'tagged_dev' is associated with.  If 'tagged_dev' has an
+ * implicit VLAN tag, then 'trunk_dev' should be the name of a network device
+ * on the same bridge that trunks that VLAN, and 'vid' should be the VLAN tag
+ * number.  If 'tagged_dev' does not have an implicit VLAN tag, then
+ * 'trunk_dev' should be NULL and 'vid' should be -1.
+ *
+ * This function has no effect unless proc_net_compat_init() has been
+ * called. */
+void
+proc_net_compat_update_vlan(const char *tagged_dev, const char *trunk_dev,
+                            int vid)
+{
+    struct compat_vlan *vlan;
+    struct shash_node *node;
+
+    if (!brc_sock) {
+        return;
+    }
+
+    /* Find the compat_vlan that we currently have for 'tagged_dev' (if
+     * any). */
+    node = shash_find(&vlans_by_tagged, tagged_dev);
+    vlan = node ? node->data : NULL;
+    if (vid <= 0 || !trunk_dev) {
+        if (vlan) {
+            if (remove_tagged_dev(node, tagged_dev)) {
+                update_vlan_config();
+            }
+        }
+    } else {
+        if (vlan) {
+            if (!strcmp(trunk_dev, vlan->trunk_dev) && vid == vlan->vid) {
+                /* No change. */
+                return;
+            } else {
+                /* 'tagged_dev' is attached to the wrong compat_vlan.  Start
+                 * by removing it from that one. */
+                remove_tagged_dev(node, tagged_dev);
+                node = NULL;
+                vlan = NULL;
+            }
+        }
+
+        /* 'tagged_dev' is not attached to any compat_vlan.  Find the
+         * compat_vlan corresponding to (trunk_dev,vid) to attach it to, or
+         * create a new compat_vlan if none exists for (trunk_dev,vid). */
+        HMAP_FOR_EACH_WITH_HASH (vlan, struct compat_vlan, trunk_node,
+                                 hash_vlan(trunk_dev, vid),
+                                 &vlans_by_trunk) {
+            if (!strcmp(trunk_dev, vlan->trunk_dev) && vid == vlan->vid) {
+                break;
+            }
+        }
+        if (!vlan) {
+            /* Create a new compat_vlan for (trunk_dev,vid). */
+            vlan = xcalloc(1, sizeof *vlan);
+            vlan->trunk_dev = xstrdup(trunk_dev);
+            vlan->vid = vid;
+            vlan->vlan_dev = xasprintf("%s.%d", trunk_dev, vid);
+            svec_init(&vlan->tagged_devs);
+            hmap_insert(&vlans_by_trunk, &vlan->trunk_node,
+                        hash_vlan(trunk_dev, vid));
+            set_vlan_proc_file(vlan);
+        }
+
+        /* Attach 'tagged_dev' to 'vlan'. */
+        svec_add(&vlan->tagged_devs, tagged_dev);
+        shash_add(&vlans_by_tagged, tagged_dev, vlan);
+        svec_sort(&vlan->tagged_devs);
+        update_vlan_config();
+    }
+}
+
+/* Remove 'tagged_dev' from the compat_vlan in 'node'.  If that causes the
+ * compat_vlan to have no tagged_devs left, destroy the compat_vlan too. */
+static bool
+remove_tagged_dev(struct shash_node *node, const char *tagged_dev)
+{
+    struct compat_vlan *vlan = node->data;
+
+    svec_del(&vlan->tagged_devs, tagged_dev);
+    shash_delete(&vlans_by_tagged, node);
+    if (!vlan->tagged_devs.n) {
+        set_proc_file("net/vlan", vlan->vlan_dev, NULL);
+
+        hmap_remove(&vlans_by_trunk, &vlan->trunk_node);
+        svec_destroy(&vlan->tagged_devs);
+        free(vlan->trunk_dev);
+        free(vlan->vlan_dev);
+        free(vlan);
+        return true;
+    }
+    return false;
+}
+
+/* Returns a hash value for (trunk_dev,vid). */
+static uint32_t
+hash_vlan(const char *trunk_dev, uint32_t vid)
+{
+    return hash_words(&vid, 1, hash_bytes(trunk_dev, strlen(trunk_dev), 0));
+}
+
+/* Update /proc/net/vlan/<vlan_dev> for 'vlan'. */
+static void
+set_vlan_proc_file(const struct compat_vlan *vlan)
+{
+    struct ds ds;
+
+    ds_init(&ds);
+    ds_put_format(
+        &ds,
+        "%s  VID: %d     REORDER_HDR: 1  dev->priv_flags: 81\n"
+        "         total frames received            0\n"
+        "          total bytes received            0\n"
+        "      Broadcast/Multicast Rcvd            0\n"
+        "\n"
+        "      total frames transmitted            0\n"
+        "       total bytes transmitted            0\n"
+        "            total headroom inc            0\n"
+        "           total encap on xmit            0\n"
+        "Device: %s\n"
+        "INGRESS priority mappings: 0:0  1:0  2:0  3:0  4:0  5:0  6:0 7:0\n"
+        "EGRESSS priority Mappings: \n",
+        vlan->vlan_dev, vlan->vid, vlan->trunk_dev);
+    set_proc_file("net/vlan", vlan->vlan_dev, ds_cstr(&ds));
+    ds_destroy(&ds);
+}
+
+/* Update /proc/net/vlan/config. */
+static void
+update_vlan_config(void)
+{
+    struct compat_vlan *vlan;
+    struct ds ds;
+
+    ds_init(&ds);
+    ds_put_cstr(&ds, "VLAN Dev name     | VLAN ID\n"
+                "Name-Type: VLAN_NAME_TYPE_RAW_PLUS_VID_NO_PAD\n");
+    HMAP_FOR_EACH (vlan, struct compat_vlan, trunk_node, &vlans_by_trunk) {
+        ds_put_format(&ds, "%-15s| %d  | %s\n",
+                      vlan->vlan_dev, vlan->vid, vlan->trunk_dev);
+    }
+    set_proc_file("net/vlan", "config", ds_cstr(&ds));
+    ds_destroy(&ds);
+}
diff --git a/vswitchd/proc-net-compat.h b/vswitchd/proc-net-compat.h
new file mode 100644 (file)
index 0000000..a35dc6f
--- /dev/null
@@ -0,0 +1,51 @@
+/* Copyright (c) 2009  Nicira Networks
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, Nicira Networks gives permission
+ * to link the code of its release of vswitchd with the OpenSSL project's
+ * "OpenSSL" library (or with modified versions of it that use the same
+ * license as the "OpenSSL" library), and distribute the linked
+ * executables.  You must obey the GNU General Public License in all
+ * respects for all of the code used other than "OpenSSL".  If you modify
+ * this file, you may extend this exception to your version of the file,
+ * but you are not obligated to do so.  If you do not wish to do so,
+ * delete this exception statement from your version.
+ */
+
+#ifndef VSWITCHD_PROC_NET_COMPAT_H
+#define VSWITCHD_PROC_NET_COMPAT_H 1
+
+#include "packets.h"
+
+struct compat_bond {
+    bool up;
+    int updelay;
+    int downdelay;
+    int n_slaves;
+    struct compat_bond_slave *slaves;
+};
+
+struct compat_bond_slave {
+    const char *name;
+    bool up;
+    uint8_t mac[ETH_ADDR_LEN];
+};
+
+int proc_net_compat_init(void);
+void proc_net_compat_update_bond(const char *name, const struct compat_bond *);
+void proc_net_compat_update_vlan(const char *dev, const char *vlandev,
+                                 int vlan);
+
+#endif /* vswitchd/proc-net-compat.h */
index 38b3158e03b6cbc0fed210b7fac5ca3bba298022..4a2b49531cc4dc013320315712d91b400b222404 100644 (file)
@@ -63,6 +63,14 @@ An OpenFlow datapath kernel module must be loaded for \fBvswitchd\fR
 to be useful.  Please refer to the \fBINSTALL\fR file included in the
 OpenFlow source distribution for instructions on how to build and load
 the OpenFlow kernel module.
+.PP
+.SH OPTIONS
+.IP "\fB--fake-proc-net\fR"
+Causes \fBvswitchd\fR to simulate some files in \fB/proc/net/vlan\fR
+and \fB/proc/net/bonding\fR that some legacy software expects to
+exist.  This option should only be used if such legacy software is
+actually in use.  It requires the \fBbrcompat_mod.ko\fR kernel module
+to be loaded.
 .
 .so lib/daemon.man
 .so lib/vlog.man
index ba0acf07685ba4d99c7e28eb98a225feecc754c5..e10ee3a2f52b6b822fd42edfb0d2f3aa32c862ff 100644 (file)
@@ -44,6 +44,7 @@
 #include "mgmt.h"
 #include "poll-loop.h"
 #include "port.h"
+#include "proc-net-compat.h"
 #include "process.h"
 #include "signals.h"
 #include "svec.h"
@@ -153,12 +154,14 @@ parse_options(int argc, char *argv[])
 {
     enum {
         OPT_PEER_CA_CERT = UCHAR_MAX + 1,
+        OPT_FAKE_PROC_NET,
         VLOG_OPTION_ENUMS,
         LEAK_CHECKER_OPTION_ENUMS
     };
     static struct option long_options[] = {
         {"help",        no_argument, 0, 'h'},
         {"version",     no_argument, 0, 'V'},
+        {"fake-proc-net", no_argument, 0, OPT_FAKE_PROC_NET},
         DAEMON_LONG_OPTIONS,
         VLOG_LONG_OPTIONS,
         LEAK_CHECKER_LONG_OPTIONS,
@@ -190,6 +193,14 @@ parse_options(int argc, char *argv[])
                    argv[0]);
             exit(EXIT_SUCCESS);
 
+        case OPT_FAKE_PROC_NET:
+            error = proc_net_compat_init();
+            if (error) {
+                ofp_fatal(error, "failed to initialize /proc/net "
+                          "compatibility");
+            }
+            break;
+
         VLOG_OPTION_HANDLERS
         DAEMON_OPTION_HANDLERS
         VCONN_SSL_OPTION_HANDLERS
@@ -235,7 +246,9 @@ usage(void)
            program_name, program_name);
     daemon_usage();
     vlog_usage();
-    printf("\nOther options:\n"
+    printf("\nLegacy compatibility options:\n"
+           " --fake-proc-net          simulate some files in /proc/net\n"
+           "\nOther options:\n"
            "  -h, --help              display this help message\n"
            "  -V, --version           display version information\n");
     leak_checker_usage();