From a2afb05c7badc3624dcbaf6444522ab4bb08c5c9 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 24 Apr 2009 11:12:10 -0700 Subject: [PATCH] brcompat: Add /proc/net/vlan, /proc/net/bonding compatibility support. 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. --- datapath/brc_procfs.c | 180 +++++++++ datapath/brc_procfs.h | 11 + datapath/brcompat.c | 19 +- datapath/linux-2.6/Modules.mk | 2 + .../compat-2.6/include/linux/netdevice.h | 6 + include/openflow/brcompat-netlink.h | 4 + lib/vlog-modules.def | 1 + vswitchd/automake.mk | 1 + vswitchd/bridge.c | 88 ++++- vswitchd/proc-net-compat.c | 344 ++++++++++++++++++ vswitchd/proc-net-compat.h | 51 +++ vswitchd/vswitchd.8.in | 8 + vswitchd/vswitchd.c | 15 +- 13 files changed, 725 insertions(+), 5 deletions(-) create mode 100644 datapath/brc_procfs.c create mode 100644 datapath/brc_procfs.h create mode 100644 vswitchd/proc-net-compat.c create mode 100644 vswitchd/proc-net-compat.h diff --git a/datapath/brc_procfs.c b/datapath/brc_procfs.c new file mode 100644 index 00000000..3455da13 --- /dev/null +++ b/datapath/brc_procfs.c @@ -0,0 +1,180 @@ +#include +#include +#include +#include +#include +#include +#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 index 00000000..93e21cfb --- /dev/null +++ b/datapath/brc_procfs.h @@ -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 */ + diff --git a/datapath/brcompat.c b/datapath/brcompat.c index 46ecce00..3817ba84 100644 --- a/datapath/brcompat.c +++ b/datapath/brcompat.c @@ -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); diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk index 18c0749e..ad302a8d 100644 --- a/datapath/linux-2.6/Modules.mk +++ b/datapath/linux-2.6/Modules.mk @@ -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 diff --git a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h index 042e30d7..32e1735d 100644 --- a/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h +++ b/datapath/linux-2.6/compat-2.6/include/linux/netdevice.h @@ -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 diff --git a/include/openflow/brcompat-netlink.h b/include/openflow/brcompat-netlink.h index 8447960b..1f10f2dc 100644 --- a/include/openflow/brcompat-netlink.h +++ b/include/openflow/brcompat-netlink.h @@ -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 diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index e6b73811..94f28d79 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -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) diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk index ecc44276..86573a97 100644 --- a/vswitchd/automake.mk +++ b/vswitchd/automake.mk @@ -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 \ diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index 684eaaaf..69e1a581 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -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); } /* Interface functions. */ diff --git a/vswitchd/proc-net-compat.c b/vswitchd/proc-net-compat.c new file mode 100644 index 00000000..a4f0f226 --- /dev/null +++ b/vswitchd/proc-net-compat.c @@ -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 . + * + */ + +#include +#include "proc-net-compat.h" +#include +#include +#include +#include +#include +#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); +} + +/* 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); +} + +/* /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/ 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 index 00000000..a35dc6fd --- /dev/null +++ b/vswitchd/proc-net-compat.h @@ -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 . + * + * 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 */ diff --git a/vswitchd/vswitchd.8.in b/vswitchd/vswitchd.8.in index 38b3158e..4a2b4953 100644 --- a/vswitchd/vswitchd.8.in +++ b/vswitchd/vswitchd.8.in @@ -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 diff --git a/vswitchd/vswitchd.c b/vswitchd/vswitchd.c index ba0acf07..e10ee3a2 100644 --- a/vswitchd/vswitchd.c +++ b/vswitchd/vswitchd.c @@ -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(); -- 2.30.2