[ovs-dev] [VLAN splinters 16/16] Implement new "VLAN splinters" feature.

Ben Pfaff blp at nicira.com
Wed Nov 16 01:17:14 UTC 2011


The "VLAN splinters" feature works around buggy device drivers in old Linux
versions.

This feature is deprecated.  When broken device drivers are no longer in
widespread use, we will delete this feature.

See ovs-vswitchd.conf.db(5) for more information.
---
 NEWS                       |    5 +
 ofproto/ofproto-dpif.c     |  223 +++++++++++++++++++++++++++++++-
 ofproto/ofproto-provider.h |   19 +++
 ofproto/ofproto.c          |   64 +++++++++
 ofproto/ofproto.h          |   20 +++
 vswitchd/bridge.c          |  303 +++++++++++++++++++++++++++++++++++++++++++-
 vswitchd/vswitch.xml       |   67 ++++++++++
 7 files changed, 692 insertions(+), 9 deletions(-)

diff --git a/NEWS b/NEWS
index 30714c5..852662a 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,10 @@
 post-v1.3.0
 ------------------------
+    - New "VLAN splinters" feature to work around buggy device drivers
+      in old Linux versions.  (This feature is deprecated.  When
+      broken device drivers are no longer in widespread use, we will
+      delete this feature.)  See ovs-vswitchd.conf.db(5) for more
+      information.
     - OpenFlow:
        - Added ability to match on IPv6 flow label through NXM.
        - Added ability to match on ECN bits in IPv4 and IPv6 through NXM.
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 88bfc1e..41613c6 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -385,11 +385,42 @@ struct ofport_dpif {
     uint32_t bond_stable_id;    /* stable_id to use as bond slave, or 0. */
     bool may_enable;            /* May be enabled in bonds. */
 
+    /* Spanning tree. */
     struct stp_port *stp_port;  /* Spanning Tree Protocol, if any. */
     enum stp_state stp_state;   /* Always STP_DISABLED if STP not in use. */
     long long int stp_state_entered;
+
+    /* 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. */
+    uint16_t realdev_ofp_port;
+    int vlandev_vid;
+};
+
+/* 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. */
+struct vlan_splinter {
+    struct hmap_node realdev_vid_node;
+    struct hmap_node vlandev_node;
+    uint16_t realdev_ofp_port;
+    uint16_t vlandev_ofp_port;
+    int vid;
 };
 
+static uint32_t vsp_realdev_to_vlandev(const struct ofproto_dpif *,
+                                       uint32_t realdev, ovs_be16 vlan_tci);
+static uint16_t vsp_vlandev_to_realdev(const struct ofproto_dpif *,
+                                       uint16_t vlandev, int *vid);
+static void vsp_remove(struct ofport_dpif *);
+static void vsp_add(struct ofport_dpif *, uint16_t realdev_ofp_port, int vid);
+
 static struct ofport_dpif *
 ofport_dpif_cast(const struct ofport *ofport)
 {
@@ -453,6 +484,10 @@ struct ofproto_dpif {
     /* Spanning tree. */
     struct stp *stp;
     long long int stp_last_tick;
+
+    /* VLAN splinters. */
+    struct hmap realdev_vid_map; /* (realdev,vid) -> vlandev. */
+    struct hmap vlandev_map;     /* vlandev -> (realdev,vid). */
 };
 
 /* Defer flow mod completion until "ovs-appctl ofproto/unclog"?  (Useful only
@@ -494,8 +529,7 @@ static int expire(struct ofproto_dpif *);
 static void send_netflow_active_timeouts(struct ofproto_dpif *);
 
 /* Utilities. */
-static int send_packet(const struct ofport_dpif *,
-                       const struct ofpbuf *packet);
+static int send_packet(const struct ofport_dpif *, struct ofpbuf *packet);
 static size_t
 compose_sflow_action(const struct ofproto_dpif *, struct ofpbuf *odp_actions,
                      const struct flow *, uint32_t odp_port);
@@ -606,6 +640,9 @@ construct(struct ofproto *ofproto_, int *n_tablesp)
 
     ofproto->has_bundle_action = false;
 
+    hmap_init(&ofproto->vlandev_map);
+    hmap_init(&ofproto->realdev_vid_map);
+
     *n_tablesp = N_TABLES;
     return 0;
 }
@@ -653,6 +690,9 @@ destruct(struct ofproto *ofproto_)
     hmap_destroy(&ofproto->facets);
     hmap_destroy(&ofproto->subfacets);
 
+    hmap_destroy(&ofproto->vlandev_map);
+    hmap_destroy(&ofproto->realdev_vid_map);
+
     dpif_close(ofproto->dpif);
 }
 
@@ -863,6 +903,8 @@ port_construct(struct ofport *port_)
     port->may_enable = true;
     port->stp_port = NULL;
     port->stp_state = STP_DISABLED;
+    port->realdev_ofp_port = 0;
+    port->vlandev_vid = 0;
 
     if (ofproto->sflow) {
         dpif_sflow_add_port(ofproto->sflow, port->odp_port,
@@ -2301,11 +2343,13 @@ handle_flow_miss(struct ofproto_dpif *ofproto, struct flow_miss *miss,
 }
 
 static enum odp_key_fitness
-ofproto_dpif_extract_flow_key(const struct ofproto_dpif *ofproto OVS_UNUSED,
+ofproto_dpif_extract_flow_key(const struct ofproto_dpif *ofproto,
                               const struct nlattr *key, size_t key_len,
                               struct flow *flow, ovs_be16 *initial_tci)
 {
     enum odp_key_fitness fitness;
+    uint16_t realdev;
+    int vid;
 
     fitness = odp_flow_key_to_flow(key, key_len, flow);
     if (fitness == ODP_FIT_ERROR) {
@@ -2313,6 +2357,19 @@ ofproto_dpif_extract_flow_key(const struct ofproto_dpif *ofproto OVS_UNUSED,
     }
     *initial_tci = flow->vlan_tci;
 
+    realdev = vsp_vlandev_to_realdev(ofproto, flow->in_port, &vid);
+    if (realdev) {
+        /* Cause the flow to be processed as if it came in on the real device
+         * with the VLAN device's VLAN ID. */
+        flow->in_port = realdev;
+        flow->vlan_tci = htons((vid & VLAN_VID_MASK) | VLAN_CFI);
+
+        /* Let the caller know that we can't reproduce 'key' from 'flow'. */
+        if (fitness == ODP_FIT_PERFECT) {
+            fitness = ODP_FIT_TOO_MUCH;
+        }
+    }
+
     return fitness;
 }
 
@@ -3627,18 +3684,26 @@ rule_modify_actions(struct rule *rule_)
 }
 
 /* Sends 'packet' out 'ofport'.
+ * May modify 'packet'.
  * Returns 0 if successful, otherwise a positive errno value. */
 static int
-send_packet(const struct ofport_dpif *ofport, const struct ofpbuf *packet)
+send_packet(const struct ofport_dpif *ofport, struct ofpbuf *packet)
 {
     const struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport->up.ofproto);
-    uint16_t odp_port = ofport->odp_port;
     struct ofpbuf key, odp_actions;
     struct odputil_keybuf keybuf;
+    uint16_t odp_port;
     struct flow flow;
     int error;
 
     flow_extract((struct ofpbuf *) packet, 0, 0, 0, &flow);
+    odp_port = vsp_realdev_to_vlandev(ofproto, ofport->odp_port,
+                                      flow.vlan_tci);
+    if (odp_port != ofport->odp_port) {
+        eth_pop_vlan(packet);
+        flow.vlan_tci = htons(0);
+    }
+
     ofpbuf_use_stack(&key, &keybuf, sizeof keybuf);
     odp_flow_key_from_flow(&key, &flow);
 
@@ -3936,7 +4001,15 @@ commit_odp_actions(struct action_xlate_ctx *ctx)
 static void
 compose_output_action(struct action_xlate_ctx *ctx, uint16_t odp_port)
 {
-    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, odp_port);
+    uint16_t out_port;
+
+    out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port,
+                                      ctx->flow.vlan_tci);
+    if (out_port != odp_port) {
+        commit_vlan_action(ctx, htons(0));
+    }
+    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, out_port);
+
     ctx->sflow_odp_port = odp_port;
     ctx->sflow_n_outputs++;
 }
@@ -5651,6 +5724,143 @@ ofproto_dpif_unixctl_init(void)
     unixctl_command_register("ofproto/unclog", "", ofproto_dpif_unclog, NULL);
 }
 
+/* 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 int
+set_realdev(struct ofport *ofport_, uint16_t realdev_ofp_port, int vid)
+{
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+
+    if (realdev_ofp_port == ofport->realdev_ofp_port
+        && vid == ofport->vlandev_vid) {
+        return 0;
+    }
+
+    if (ofport->realdev_ofp_port) {
+        vsp_remove(ofport);
+    }
+    if (ofport->bundle) {
+        bundle_set(ofport->up.ofproto, ofport->bundle, NULL);
+    }
+
+    ofport->realdev_ofp_port = realdev_ofp_port;
+    ofport->vlandev_vid = vid;
+
+    if (realdev_ofp_port) {
+        vsp_add(ofport, realdev_ofp_port, vid);
+    }
+
+    return 0;
+}
+
+static uint32_t
+hash_realdev_vid(uint16_t realdev_ofp_port, int vid)
+{
+    return hash_2words(realdev_ofp_port, vid);
+}
+
+static uint32_t
+vsp_realdev_to_vlandev(const struct ofproto_dpif *ofproto,
+                   uint32_t realdev_odp_port, ovs_be16 vlan_tci)
+{
+    if (!hmap_is_empty(&ofproto->realdev_vid_map)) {
+        uint16_t realdev_ofp_port = odp_port_to_ofp_port(realdev_odp_port);
+        int vid = vlan_tci_to_vid(vlan_tci);
+        const struct vlan_splinter *vsp;
+
+        HMAP_FOR_EACH_WITH_HASH (vsp, realdev_vid_node,
+                                 hash_realdev_vid(realdev_ofp_port, vid),
+                                 &ofproto->realdev_vid_map) {
+            if (vsp->realdev_ofp_port == realdev_ofp_port
+                && vsp->vid == vid) {
+                return ofp_port_to_odp_port(vsp->vlandev_ofp_port);
+            }
+        }
+    }
+    return realdev_odp_port;
+}
+
+static struct vlan_splinter *
+vlandev_find(const struct ofproto_dpif *ofproto, uint16_t vlandev_ofp_port)
+{
+    const struct vlan_splinter *vsp;
+
+    HMAP_FOR_EACH_WITH_HASH (vsp, vlandev_node, hash_int(vlandev_ofp_port, 0),
+                             &ofproto->vlandev_map) {
+        if (vsp->vlandev_ofp_port == vlandev_ofp_port) {
+            return (struct vlan_splinter *) vsp;
+        }
+    }
+
+    return NULL;
+}
+
+static uint16_t
+vsp_vlandev_to_realdev(const struct ofproto_dpif *ofproto,
+                   uint16_t vlandev_ofp_port, int *vid)
+{
+    if (!hmap_is_empty(&ofproto->vlandev_map)) {
+        const struct vlan_splinter *vsp;
+
+        vsp = vlandev_find(ofproto, vlandev_ofp_port);
+        if (vsp) {
+            if (vid) {
+                *vid = vsp->vid;
+            }
+            return vsp->realdev_ofp_port;
+        }
+    }
+    return 0;
+}
+
+static void
+vsp_remove(struct ofport_dpif *port)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto);
+    struct vlan_splinter *vsp;
+
+    vsp = vlandev_find(ofproto, port->up.ofp_port);
+    if (vsp) {
+        hmap_remove(&ofproto->vlandev_map, &vsp->vlandev_node);
+        hmap_remove(&ofproto->realdev_vid_map, &vsp->realdev_vid_node);
+        free(vsp);
+
+        port->realdev_ofp_port = 0;
+    } else {
+        VLOG_ERR("missing vlan device record");
+    }
+}
+
+static void
+vsp_add(struct ofport_dpif *port, uint16_t realdev_ofp_port, int vid)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(port->up.ofproto);
+
+    if (!vsp_vlandev_to_realdev(ofproto, port->up.ofp_port, NULL)
+        && (vsp_realdev_to_vlandev(ofproto, realdev_ofp_port, htons(vid))
+            == realdev_ofp_port)) {
+        struct vlan_splinter *vsp;
+
+        vsp = xmalloc(sizeof *vsp);
+        hmap_insert(&ofproto->vlandev_map, &vsp->vlandev_node,
+                    hash_int(port->up.ofp_port, 0));
+        hmap_insert(&ofproto->realdev_vid_map, &vsp->realdev_vid_node,
+                    hash_realdev_vid(realdev_ofp_port, vid));
+        vsp->realdev_ofp_port = realdev_ofp_port;
+        vsp->vlandev_ofp_port = port->up.ofp_port;
+        vsp->vid = vid;
+
+        port->realdev_ofp_port = realdev_ofp_port;
+    } else {
+        VLOG_ERR("duplicate vlan device record");
+    }
+}
+
 const struct ofproto_class ofproto_dpif_class = {
     enumerate_types,
     enumerate_names,
@@ -5705,4 +5915,5 @@ const struct ofproto_class ofproto_dpif_class = {
     set_flood_vlans,
     is_mirror_output_bundle,
     forward_bpdu_changed,
+    set_realdev,
 };
diff --git a/ofproto/ofproto-provider.h b/ofproto/ofproto-provider.h
index bb3a7fb..f4b7820 100644
--- a/ofproto/ofproto-provider.h
+++ b/ofproto/ofproto-provider.h
@@ -1024,6 +1024,25 @@ struct ofproto_class {
     /* When the configuration option of forward_bpdu changes, this function
      * will be invoked. */
     void (*forward_bpdu_changed)(struct ofproto *ofproto);
+
+/* 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. */
+
+    /* If 'realdev_ofp_port' is nonzero, then this function configures 'ofport'
+     * as a VLAN splinter port for VLAN 'vid', associated with the real device
+     * that has OpenFlow port number 'realdev_ofp_port'.
+     *
+     * If 'realdev_ofp_port' is zero, then this function deconfigures 'ofport'
+     * as a VLAN splinter port.
+     *
+     * This function should be NULL if a an implementation does not support
+     * it. */
+    int (*set_realdev)(struct ofport *ofport,
+                       uint16_t realdev_ofp_port, int vid);
 };
 
 extern const struct ofproto_class ofproto_dpif_class;
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index 60cf524..285ad25 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -21,6 +21,7 @@
 #include <inttypes.h>
 #include <stdbool.h>
 #include <stdlib.h>
+#include "bitmap.h"
 #include "byte-order.h"
 #include "classifier.h"
 #include "connmgr.h"
@@ -1367,6 +1368,9 @@ ofproto_port_unregister(struct ofproto *ofproto, uint16_t ofp_port)
 {
     struct ofport *port = ofproto_get_port(ofproto, ofp_port);
     if (port) {
+        if (port->ofproto->ofproto_class->set_realdev) {
+            port->ofproto->ofproto_class->set_realdev(port, 0, 0);
+        }
         if (port->ofproto->ofproto_class->set_stp_port) {
             port->ofproto->ofproto_class->set_stp_port(port, NULL);
         }
@@ -3215,3 +3219,63 @@ ofproto_unixctl_init(void)
 
     unixctl_command_register("ofproto/list", "", ofproto_unixctl_list, NULL);
 }
+
+/* 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. */
+
+void
+ofproto_get_vlan_usage(const struct ofproto *ofproto,
+                       unsigned long int *vlan_bitmap)
+{
+    const struct classifier *cls;
+
+    OFPROTO_FOR_EACH_TABLE (cls, ofproto) {
+        const struct cls_table *table;
+
+        HMAP_FOR_EACH (table, hmap_node, &cls->tables) {
+            if (!(table->wc.vlan_tci_mask & htons(VLAN_VID_MASK))) {
+                const struct cls_rule *rule;
+
+                HMAP_FOR_EACH (rule, hmap_node, &table->rules) {
+                    uint16_t vid = vlan_tci_to_vid(rule->flow.vlan_tci);
+                    bitmap_set1(vlan_bitmap, vid);
+                }
+            }
+        }
+    }
+}
+
+int
+ofproto_port_set_realdev(struct ofproto *ofproto, uint16_t vlandev_ofp_port,
+                         uint16_t realdev_ofp_port, int vid)
+{
+    struct ofport *ofport;
+    int error;
+
+    ofport = ofproto_get_port(ofproto, vlandev_ofp_port);
+    if (!ofport) {
+        VLOG_WARN("%s: cannot set realdev on nonexistent port %"PRIu16,
+                  ofproto->name, vlandev_ofp_port);
+        return EINVAL;
+    }
+
+    if (!ofproto->ofproto_class->set_realdev) {
+        if (!vlandev_ofp_port) {
+            return 0;
+        }
+        VLOG_WARN("%s: vlan splinters not supported", ofproto->name);
+        return EOPNOTSUPP;
+    }
+
+    error = ofproto->ofproto_class->set_realdev(ofport, realdev_ofp_port, vid);
+    if (error) {
+        VLOG_WARN("%s: setting realdev on port %"PRIu16" (%s) failed (%s)",
+                  ofproto->name, vlandev_ofp_port,
+                  netdev_get_name(ofport->netdev), strerror(error));
+    }
+    return error;
+}
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index eed4e50..9755d28 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -257,6 +257,14 @@ struct ofproto_bundle_settings {
 
     struct lacp_settings *lacp;              /* Nonnull to enable LACP. */
     struct lacp_slave_settings *lacp_slaves; /* Array of n_slaves elements. */
+
+    /* 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. */
+    uint16_t realdev_ofp_port;  /* OpenFlow port number of real device. */
 };
 
 int ofproto_bundle_register(struct ofproto *, void *aux,
@@ -304,6 +312,18 @@ int ofproto_port_get_cfm_remote_mpids(const struct ofproto *,
 
 void ofproto_get_ofproto_controller_info(const struct ofproto *, struct shash *);
 void ofproto_free_ofproto_controller_info(struct shash *);
+
+/* 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. */
+
+void ofproto_get_vlan_usage(const struct ofproto *,
+                            unsigned long int *vlan_bitmap);
+int ofproto_port_set_realdev(struct ofproto *, uint16_t vlandev_ofp_port,
+                             uint16_t realdev_ofp_port, int vid);
 
 #ifdef  __cplusplus
 }
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index c7f2233..38e8947 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -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,28 @@ 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. */
+static bool enable_vlan_splinters(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);
 
 /* Public functions. */
 
@@ -362,6 +381,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 +396,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 +511,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;
 
@@ -1092,6 +1119,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;
             }
@@ -2130,7 +2163,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;
@@ -2163,6 +2197,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) {
@@ -3208,3 +3246,262 @@ mirror_configure(struct mirror *m, const struct ovsrec_mirror *cfg)
 
     return true;
 }
+
+/* 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
+enable_vlan_splinters(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. */
+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 (enable_vlan_splinters(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);
+                }
+            }
+        }
+    }
+    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);
+        }
+    }
+
+    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)) {
+                        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 (enable_vlan_splinters(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));
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index 7d2a72a..0ef0865 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -1741,6 +1741,73 @@
       </column>
     </group>
 
+    <group title="VLAN Splinters">
+      <p>
+	The ``VLAN splinters'' feature increases Open vSwitch compatibility
+	with buggy network drivers in old versions of Linux that do not
+	properly support VLANs when VLAN devices are not used, at some cost
+	in memory and performance.
+      </p>
+
+      <p>
+	When VLAN splinters are enabled on a particular interface, Open vSwitch
+	creates a VLAN device for each in-use VLAN.  For sending traffic tagged
+	with a VLAN on the interface, it substitutes the VLAN device.  Traffic
+	received on the VLAN device is treated as if it had been received on
+        the interface on the particular VLAN.
+      </p>
+
+      <p>
+        VLAN splinters consider a VLAN to be in use if:
+      </p>
+
+      <ul>
+        <li>
+          The VLAN is listed within the <ref table="Port" column="trunks"/>
+          column of the <ref table="Port"/> record of an interface on which
+          VLAN splinters are enabled.
+
+          An empty <ref table="Port" column="trunks"/> does not influence the
+          in-use VLANs: creating 4,096 VLAN devices is impractical because it
+          will exceed the current 1,024 port per datapath limit.
+        </li>
+
+        <li>
+          An OpenFlow flow within any bridge matches the VLAN.
+        </li>
+      </ul>
+
+      <p>
+        The same set of in-use VLANs applies to every interface on which VLAN
+        splinters are enabled.  That is, the set is not chosen separately for
+        each interface but selected once as the union of all in-use VLANs based
+        on the rules above.
+      </p>
+
+      <p>
+        It does not make sense to enable VLAN splinters on an interface for an
+        access port, or on an interface that is not a physical port.
+      </p>
+
+      <p>
+	VLAN splinters are deprecated.  When broken device drivers are no
+	longer in widespread use, we will delete this feature.
+      </p>
+
+      <column name="other_config" key="enable-vlan-splinters"
+              type='{"type": "boolean"}'>
+        <p>
+          Set to <code>true</code> to enable VLAN splinters on this interface.
+          Defaults to <code>false</code>.
+        </p>
+
+        <p>
+          VLAN splinters increase kernel and userspace memory overhead, so do
+          not use them unless they are needed.
+        </p>
+      </column>
+    </group>
+
     <group title="Common Columns">
       The overall purpose of these columns is described under <code>Common
       Columns</code> at the beginning of this document.
-- 
1.7.4.4




More information about the dev mailing list