summaryrefslogtreecommitdiff
path: root/vswitchd/proc-net-compat.c
diff options
context:
space:
mode:
Diffstat (limited to 'vswitchd/proc-net-compat.c')
-rw-r--r--vswitchd/proc-net-compat.c344
1 files changed, 344 insertions, 0 deletions
diff --git a/vswitchd/proc-net-compat.c b/vswitchd/proc-net-compat.c
new file mode 100644
index 000000000..3f5cf44ad
--- /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 <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 "openvswitch/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: ovs-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_int(vid, hash_string(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\t 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);
+}