Implement new "VLAN splinters" feature.
[sliver-openvswitch.git] / vswitchd / bridge.c
index ca106c5..32885eb 100644 (file)
@@ -45,6 +45,7 @@
 #include "timeval.h"
 #include "util.h"
 #include "unixctl.h"
+#include "vlandev.h"
 #include "vswitchd/vswitch-idl.h"
 #include "xenserver.h"
 #include "vlog.h"
@@ -143,7 +144,8 @@ static unixctl_cb_func bridge_unixctl_dump_flows;
 static unixctl_cb_func bridge_unixctl_reconnect;
 static size_t bridge_get_controllers(const struct bridge *br,
                                      struct ovsrec_controller ***controllersp);
-static void bridge_add_del_ports(struct bridge *);
+static void bridge_add_del_ports(struct bridge *,
+                                 const unsigned long int *splinter_vlans);
 static void bridge_add_ofproto_ports(struct bridge *);
 static void bridge_del_ofproto_ports(struct bridge *);
 static void bridge_refresh_ofp_port(struct bridge *);
@@ -209,11 +211,32 @@ static void iface_refresh_cfm_stats(struct iface *);
 static void iface_refresh_stats(struct iface *);
 static void iface_refresh_status(struct iface *);
 static bool iface_is_synthetic(const struct iface *);
+static const char *get_interface_other_config(const struct ovsrec_interface *,
+                                              const char *key,
+                                              const char *default_value);
 
 static void shash_from_ovs_idl_map(char **keys, char **values, size_t n,
                                    struct shash *);
 static void shash_to_ovs_idl_map(struct shash *,
                                  char ***keys, char ***values, size_t *n);
+
+/* 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. */
+
+/* True if VLAN splinters are enabled on any interface, false otherwise.*/
+static bool vlan_splinters_enabled_anywhere;
+
+static bool vlan_splinters_is_enabled(const struct ovsrec_interface *);
+static unsigned long int *collect_splinter_vlans(
+    const struct ovsrec_open_vswitch *);
+static void configure_splinter_port(struct port *);
+static void add_vlan_splinter_ports(struct bridge *,
+                                    const unsigned long int *splinter_vlans,
+                                    struct shash *ports);
 \f
 /* Public functions. */
 
@@ -362,6 +385,7 @@ collect_in_band_managers(const struct ovsrec_open_vswitch *ovs_cfg,
 static void
 bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
 {
+    unsigned long int *splinter_vlans;
     struct sockaddr_in *managers;
     struct bridge *br, *next;
     int sflow_bridge_number;
@@ -376,9 +400,11 @@ bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
      * This is mostly an update to bridge data structures.  Very little is
      * pushed down to ofproto or lower layers. */
     add_del_bridges(ovs_cfg);
+    splinter_vlans = collect_splinter_vlans(ovs_cfg);
     HMAP_FOR_EACH (br, node, &all_bridges) {
-        bridge_add_del_ports(br);
+        bridge_add_del_ports(br, splinter_vlans);
     }
+    free(splinter_vlans);
 
     /* Delete all datapaths and datapath ports that are no longer configured.
      *
@@ -489,6 +515,11 @@ port_configure(struct port *port)
     struct ofproto_bundle_settings s;
     struct iface *iface;
 
+    if (cfg->vlan_mode && !strcmp(cfg->vlan_mode, "splinter")) {
+        configure_splinter_port(port);
+        return;
+    }
+
     /* Get name. */
     s.name = port->name;
 
@@ -1094,6 +1125,12 @@ bridge_add_ofproto_ports(struct bridge *br)
                     VLOG_WARN("could not open network device %s (%s)",
                               iface->name, strerror(error));
                 }
+
+                if (iface->netdev
+                    && port->cfg->vlan_mode
+                    && !strcmp(port->cfg->vlan_mode, "splinter")) {
+                    netdev_turn_flags_on(iface->netdev, NETDEV_UP, true);
+                }
             } else {
                 error = 0;
             }
@@ -1747,6 +1784,7 @@ bridge_run(void)
 {
     const struct ovsrec_open_vswitch *cfg;
 
+    bool vlan_splinters_changed;
     bool datapath_destroyed;
     bool database_changed;
     struct bridge *br;
@@ -1794,7 +1832,19 @@ bridge_run(void)
         stream_ssl_set_ca_cert_file(ssl->ca_cert, ssl->bootstrap_ca_cert);
     }
 
-    if (database_changed || datapath_destroyed) {
+    /* If VLAN splinters are in use, then we need to reconfigure if VLAN usage
+     * has changed. */
+    vlan_splinters_changed = false;
+    if (vlan_splinters_enabled_anywhere) {
+        HMAP_FOR_EACH (br, node, &all_bridges) {
+            if (ofproto_has_vlan_usage_changed(br->ofproto)) {
+                vlan_splinters_changed = true;
+                break;
+            }
+        }
+    }
+
+    if (database_changed || datapath_destroyed || vlan_splinters_changed) {
         if (cfg) {
             struct ovsdb_idl_txn *txn = ovsdb_idl_txn_create(idl);
 
@@ -2132,7 +2182,8 @@ bridge_get_controllers(const struct bridge *br,
 /* Adds and deletes "struct port"s and "struct iface"s under 'br' to match
  * those configured in 'br->cfg'. */
 static void
-bridge_add_del_ports(struct bridge *br)
+bridge_add_del_ports(struct bridge *br,
+                     const unsigned long int *splinter_vlans)
 {
     struct port *port, *next;
     struct shash_node *node;
@@ -2165,6 +2216,10 @@ bridge_add_del_ports(struct bridge *br)
         shash_add(&new_ports, br->name, &br->synth_local_port);
     }
 
+    if (splinter_vlans) {
+        add_vlan_splinter_ports(br, splinter_vlans, &new_ports);
+    }
+
     /* Get rid of deleted ports.
      * Get rid of deleted interfaces on ports that still exist. */
     HMAP_FOR_EACH_SAFE (port, next, hmap_node, &br->ports) {
@@ -3233,3 +3288,270 @@ mirror_configure(struct mirror *m, const struct ovsrec_mirror *cfg)
 
     return true;
 }
+\f
+/* 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. */
+
+static void **blocks;
+static size_t n_blocks, allocated_blocks;
+
+/* Adds 'block' to a list of blocks that have to be freed with free() when the
+ * VLAN splinters are reconfigured. */
+static void
+register_block(void *block)
+{
+    if (n_blocks >= allocated_blocks) {
+        blocks = x2nrealloc(blocks, &allocated_blocks, sizeof *blocks);
+    }
+    blocks[n_blocks++] = block;
+}
+
+/* Frees all of the blocks registered with register_block(). */
+static void
+free_registered_blocks(void)
+{
+    size_t i;
+
+    for (i = 0; i < n_blocks; i++) {
+        free(blocks[i]);
+    }
+    n_blocks = 0;
+}
+
+/* Returns true if VLAN splinters are enabled on 'iface_cfg', false
+ * otherwise. */
+static bool
+vlan_splinters_is_enabled(const struct ovsrec_interface *iface_cfg)
+{
+    const char *value;
+
+    value = get_interface_other_config(iface_cfg, "enable-vlan-splinters", "");
+    return !strcmp(value, "true");
+}
+
+/* Figures out the set of VLANs that are in use for the purpose of VLAN
+ * splinters.
+ *
+ * If VLAN splinters are enabled on at least one interface and any VLANs are in
+ * use, returns a 4096-bit bitmap with a 1-bit for each in-use VLAN (bits 0 and
+ * 4095 will not be set).  The caller is responsible for freeing the bitmap,
+ * with free().
+ *
+ * If VLANs splinters are not enabled on any interface or if no VLANs are in
+ * use, returns NULL.
+ *
+ * Updates 'vlan_splinters_enabled_anywhere'. */
+static unsigned long int *
+collect_splinter_vlans(const struct ovsrec_open_vswitch *ovs_cfg)
+{
+    unsigned long int *splinter_vlans;
+    struct sset splinter_ifaces;
+    const char *real_dev_name;
+    struct shash *real_devs;
+    struct shash_node *node;
+    struct bridge *br;
+    size_t i;
+
+    splinter_vlans = NULL;
+    sset_init(&splinter_ifaces);
+    for (i = 0; i < ovs_cfg->n_bridges; i++) {
+        struct ovsrec_bridge *br_cfg = ovs_cfg->bridges[i];
+        size_t j;
+
+        for (j = 0; j < br_cfg->n_ports; j++) {
+            struct ovsrec_port *port_cfg = br_cfg->ports[j];
+            int k;
+
+            for (k = 0; k < port_cfg->n_interfaces; k++) {
+                struct ovsrec_interface *iface_cfg = port_cfg->interfaces[k];
+
+                if (vlan_splinters_is_enabled(iface_cfg)) {
+                    sset_add(&splinter_ifaces, iface_cfg->name);
+
+                    if (!splinter_vlans) {
+                        splinter_vlans = bitmap_allocate(4096);
+                    }
+                    vlan_bitmap_from_array__(port_cfg->trunks,
+                                             port_cfg->n_trunks,
+                                             splinter_vlans);
+                }
+            }
+        }
+    }
+
+    vlan_splinters_enabled_anywhere = splinter_vlans != NULL;
+    if (!splinter_vlans) {
+        sset_destroy(&splinter_ifaces);
+        return NULL;
+    }
+
+    HMAP_FOR_EACH (br, node, &all_bridges) {
+        if (br->ofproto) {
+            ofproto_get_vlan_usage(br->ofproto, splinter_vlans);
+        }
+    }
+
+    /* Don't allow VLANs 0 or 4095 to be splintered.  VLAN 0 should appear on
+     * the real device.  VLAN 4095 is reserved and Linux doesn't allow a VLAN
+     * device to be created for it. */
+    bitmap_set0(splinter_vlans, 0);
+    bitmap_set0(splinter_vlans, 4095);
+
+    /* Delete all VLAN devices that we don't need. */
+    vlandev_refresh();
+    real_devs = vlandev_get_real_devs();
+    SHASH_FOR_EACH (node, real_devs) {
+        const struct vlan_real_dev *real_dev = node->data;
+        const struct vlan_dev *vlan_dev;
+        bool real_dev_has_splinters;
+
+        real_dev_has_splinters = sset_contains(&splinter_ifaces,
+                                               real_dev->name);
+        HMAP_FOR_EACH (vlan_dev, hmap_node, &real_dev->vlan_devs) {
+            if (!real_dev_has_splinters
+                || !bitmap_is_set(splinter_vlans, vlan_dev->vid)) {
+                struct netdev *netdev;
+
+                if (!netdev_open(vlan_dev->name, "system", &netdev)) {
+                    if (!netdev_get_in4(netdev, NULL, NULL) ||
+                        !netdev_get_in6(netdev, NULL)) {
+                        vlandev_del(vlan_dev->name);
+                    } else {
+                        /* It has an IP address configured, so we don't own
+                         * it.  Don't delete it. */
+                    }
+                    netdev_close(netdev);
+                }
+            }
+
+        }
+    }
+
+    /* Add all VLAN devices that we need. */
+    SSET_FOR_EACH (real_dev_name, &splinter_ifaces) {
+        int vid;
+
+        BITMAP_FOR_EACH_1 (vid, 4096, splinter_vlans) {
+            if (!vlandev_get_name(real_dev_name, vid)) {
+                vlandev_add(real_dev_name, vid);
+            }
+        }
+    }
+
+    vlandev_refresh();
+
+    sset_destroy(&splinter_ifaces);
+
+    if (bitmap_scan(splinter_vlans, 0, 4096) >= 4096) {
+        free(splinter_vlans);
+        return NULL;
+    }
+    return splinter_vlans;
+}
+
+/* Pushes the configure of VLAN splinter port 'port' (e.g. eth0.9) down to
+ * ofproto.  */
+static void
+configure_splinter_port(struct port *port)
+{
+    struct ofproto *ofproto = port->bridge->ofproto;
+    uint16_t realdev_ofp_port;
+    const char *realdev_name;
+    struct iface *vlandev, *realdev;
+
+    ofproto_bundle_unregister(port->bridge->ofproto, port);
+
+    vlandev = CONTAINER_OF(list_front(&port->ifaces), struct iface,
+                           port_elem);
+
+    realdev_name = get_port_other_config(port->cfg, "realdev", NULL);
+    realdev = iface_lookup(port->bridge, realdev_name);
+    realdev_ofp_port = realdev ? realdev->ofp_port : 0;
+
+    ofproto_port_set_realdev(ofproto, vlandev->ofp_port, realdev_ofp_port,
+                             *port->cfg->tag);
+}
+
+static struct ovsrec_port *
+synthesize_splinter_port(const char *real_dev_name,
+                         const char *vlan_dev_name, int vid)
+{
+    struct ovsrec_interface *iface;
+    struct ovsrec_port *port;
+
+    iface = xzalloc(sizeof *iface);
+    iface->name = xstrdup(vlan_dev_name);
+    iface->type = "system";
+
+    port = xzalloc(sizeof *port);
+    port->interfaces = xmemdup(&iface, sizeof iface);
+    port->n_interfaces = 1;
+    port->name = xstrdup(vlan_dev_name);
+    port->vlan_mode = "splinter";
+    port->tag = xmalloc(sizeof *port->tag);
+    *port->tag = vid;
+    port->key_other_config = xmalloc(sizeof *port->key_other_config);
+    port->key_other_config[0] = "realdev";
+    port->value_other_config = xmalloc(sizeof *port->value_other_config);
+    port->value_other_config[0] = xstrdup(real_dev_name);
+    port->n_other_config = 1;
+
+    register_block(iface);
+    register_block(iface->name);
+    register_block(port);
+    register_block(port->interfaces);
+    register_block(port->name);
+    register_block(port->tag);
+    register_block(port->key_other_config);
+    register_block(port->value_other_config);
+    register_block(port->value_other_config[0]);
+
+    return port;
+}
+
+/* For each interface with 'br' that has VLAN splinters enabled, adds a
+ * corresponding ovsrec_port to 'ports' for each splinter VLAN marked with a
+ * 1-bit in the 'splinter_vlans' bitmap. */
+static void
+add_vlan_splinter_ports(struct bridge *br,
+                        const unsigned long int *splinter_vlans,
+                        struct shash *ports)
+{
+    size_t i;
+
+    free_registered_blocks();
+
+    /* We iterate through 'br->cfg->ports' instead of 'ports' here because
+     * we're modifying 'ports'. */
+    for (i = 0; i < br->cfg->n_ports; i++) {
+        const char *name = br->cfg->ports[i]->name;
+        struct ovsrec_port *port_cfg = shash_find_data(ports, name);
+        size_t j;
+
+        for (j = 0; j < port_cfg->n_interfaces; j++) {
+            struct ovsrec_interface *iface_cfg = port_cfg->interfaces[j];
+
+            if (vlan_splinters_is_enabled(iface_cfg)) {
+                const char *real_dev_name;
+                uint16_t vid;
+
+                real_dev_name = iface_cfg->name;
+                BITMAP_FOR_EACH_1 (vid, 4096, splinter_vlans) {
+                    const char *vlan_dev_name;
+
+                    vlan_dev_name = vlandev_get_name(real_dev_name, vid);
+                    if (vlan_dev_name
+                        && !shash_find(ports, vlan_dev_name)) {
+                        shash_add(ports, vlan_dev_name,
+                                  synthesize_splinter_port(
+                                      real_dev_name, vlan_dev_name, vid));
+                    }
+                }
+            }
+        }
+    }
+}