Implement new "VLAN splinters" feature.
authorBen Pfaff <blp@nicira.com>
Thu, 24 Nov 2011 01:03:31 +0000 (17:03 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 24 Nov 2011 01:03:31 +0000 (17:03 -0800)
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.

I tested earlier versions of this commit, but I have not tested this
version.

See ovs-vswitchd.conf.db(5) for more information.

NEWS
ofproto/ofproto-dpif.c
ofproto/ofproto-provider.h
ofproto/ofproto.c
ofproto/ofproto.h
vswitchd/bridge.c
vswitchd/vswitch.xml

diff --git a/NEWS b/NEWS
index 3b0e9ac66020707c784d7ae864d53655669d5ec5..3a6eec7d0cc8818050cb485e12dd6747ec94c0af 100644 (file)
--- 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.
index 241dbd3a861ad990c63d1dd09700d9d8714d28ca..96bd764d2c6960891a0f8d5cab3997c1bc3005a9 100644 (file)
@@ -392,11 +392,21 @@ 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;
 
     struct hmap priorities;     /* Map of attached 'priority_to_dscp's. */
+
+    /* 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;
 };
 
 /* Node in 'ofport_dpif''s 'priorities' map.  Used to maintain a map from
@@ -409,6 +419,27 @@ struct priority_to_dscp {
     uint8_t dscp;               /* DSCP bits to mark outgoing traffic with. */
 };
 
+/* 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)
 {
@@ -473,6 +504,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
@@ -511,8 +546,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);
@@ -623,6 +657,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;
 }
@@ -670,6 +707,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);
 }
 
@@ -881,6 +921,8 @@ port_construct(struct ofport *port_)
     port->stp_port = NULL;
     port->stp_state = STP_DISABLED;
     hmap_init(&port->priorities);
+    port->realdev_ofp_port = 0;
+    port->vlandev_vid = 0;
 
     if (ofproto->sflow) {
         dpif_sflow_add_port(ofproto->sflow, port->odp_port,
@@ -2435,11 +2477,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) {
@@ -2447,6 +2491,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;
 }
 
@@ -3762,18 +3819,26 @@ rule_modify_actions(struct rule *rule_)
 }
 \f
 /* 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);
 
@@ -4066,7 +4131,9 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
 {
     const struct ofport_dpif *ofport = get_ofp_port(ctx->ofproto, ofp_port);
     uint16_t odp_port = ofp_port_to_odp_port(ofp_port);
+    ovs_be16 flow_vlan_tci = ctx->flow.vlan_tci;
     uint8_t flow_nw_tos = ctx->flow.nw_tos;
+    uint16_t out_port;
 
     if (ofport) {
         struct priority_to_dscp *pdscp;
@@ -4087,11 +4154,18 @@ compose_output_action__(struct action_xlate_ctx *ctx, uint16_t ofp_port,
          * later and we're pre-populating the flow table.  */
     }
 
+    out_port = vsp_realdev_to_vlandev(ctx->ofproto, odp_port,
+                                      ctx->flow.vlan_tci);
+    if (out_port != odp_port) {
+        ctx->flow.vlan_tci = htons(0);
+    }
     commit_odp_actions(ctx);
-    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, odp_port);
+    nl_msg_put_u32(ctx->odp_actions, OVS_ACTION_ATTR_OUTPUT, out_port);
+
     ctx->sflow_odp_port = odp_port;
     ctx->sflow_n_outputs++;
     ctx->nf_output_iface = ofp_port;
+    ctx->flow.vlan_tci = flow_vlan_tci;
     ctx->flow.nw_tos = flow_nw_tos;
 }
 
@@ -5696,6 +5770,148 @@ ofproto_dpif_unixctl_init(void)
     unixctl_command_register("ofproto/unclog", "", ofproto_dpif_unclog, NULL);
 }
 \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 int
+set_realdev(struct ofport *ofport_, uint16_t realdev_ofp_port, int vid)
+{
+    struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofport_->ofproto);
+    struct ofport_dpif *ofport = ofport_dpif_cast(ofport_);
+
+    if (realdev_ofp_port == ofport->realdev_ofp_port
+        && vid == ofport->vlandev_vid) {
+        return 0;
+    }
+
+    ofproto->need_revalidate = true;
+
+    if (ofport->realdev_ofp_port) {
+        vsp_remove(ofport);
+    }
+    if (realdev_ofp_port && ofport->bundle) {
+        /* vlandevs are enslaved to their realdevs, so they are not allowed to
+         * themselves be part of a 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)
+{
+    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 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");
+    }
+}
+\f
 const struct ofproto_class ofproto_dpif_class = {
     enumerate_types,
     enumerate_names,
@@ -5751,4 +5967,5 @@ const struct ofproto_class ofproto_dpif_class = {
     set_flood_vlans,
     is_mirror_output_bundle,
     forward_bpdu_changed,
+    set_realdev,
 };
index 2a7324acb29aaadcbda553e760c5e29f5862f4f4..558b8719cb88864fcb6f6e27cf0c11e83b6637dc 100644 (file)
@@ -69,6 +69,15 @@ struct ofproto {
     struct list pending;        /* List of "struct ofopgroup"s. */
     unsigned int n_pending;     /* list_size(&pending). */
     struct hmap deletions;      /* All OFOPERATION_DELETE "ofoperation"s. */
+
+    /* 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. */
+    unsigned long int *vlan_bitmap; /* 4096-bit bitmap of in-use VLANs. */
+    bool vlans_changed;             /* True if new VLANs are in use. */
 };
 
 struct ofproto *ofproto_lookup(const char *name);
@@ -1035,6 +1044,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;
index af02a0e35664e5dbc6254b93e115dd0fb58b214a..1a2009794ed73a49c25dc360c2bf75ad174d5ad6 100644 (file)
@@ -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"
@@ -337,6 +338,8 @@ ofproto_create(const char *datapath_name, const char *datapath_type,
     list_init(&ofproto->pending);
     ofproto->n_pending = 0;
     hmap_init(&ofproto->deletions);
+    ofproto->vlan_bitmap = NULL;
+    ofproto->vlans_changed = false;
 
     error = ofproto->ofproto_class->construct(ofproto, &n_tables);
     if (error) {
@@ -834,6 +837,8 @@ ofproto_destroy__(struct ofproto *ofproto)
 
     hmap_destroy(&ofproto->deletions);
 
+    free(ofproto->vlan_bitmap);
+
     ofproto->ofproto_class->dealloc(ofproto);
 }
 
@@ -1394,6 +1399,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);
         }
@@ -3098,7 +3106,8 @@ ofoperation_complete(struct ofoperation *op, int error)
 {
     struct ofopgroup *group = op->group;
     struct rule *rule = op->rule;
-    struct classifier *table = &rule->ofproto->tables[rule->table_id];
+    struct ofproto *ofproto = rule->ofproto;
+    struct classifier *table = &ofproto->tables[rule->table_id];
 
     assert(rule->pending == op);
     assert(op->status < 0);
@@ -3130,6 +3139,15 @@ ofoperation_complete(struct ofoperation *op, int error)
             if (op->victim) {
                 ofproto_rule_destroy__(op->victim);
             }
+            if (!(rule->cr.wc.vlan_tci_mask & htons(VLAN_VID_MASK))
+                && ofproto->vlan_bitmap) {
+                uint16_t vid = vlan_tci_to_vid(rule->cr.flow.vlan_tci);
+
+                if (!bitmap_is_set(ofproto->vlan_bitmap, vid)) {
+                    bitmap_set1(ofproto->vlan_bitmap, vid);
+                    ofproto->vlans_changed = true;
+                }
+            }
         } else {
             if (op->victim) {
                 classifier_replace(table, &op->victim->cr);
@@ -3242,3 +3260,86 @@ ofproto_unixctl_init(void)
 
     unixctl_command_register("ofproto/list", "", ofproto_unixctl_list, NULL);
 }
+\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. */
+
+/* Sets a 1-bit in the 4096-bit 'vlan_bitmap' for each VLAN ID that is matched
+ * (exactly) by an OpenFlow rule in 'ofproto'. */
+void
+ofproto_get_vlan_usage(struct ofproto *ofproto, unsigned long int *vlan_bitmap)
+{
+    const struct classifier *cls;
+
+    free(ofproto->vlan_bitmap);
+    ofproto->vlan_bitmap = bitmap_allocate(4096);
+    ofproto->vlans_changed = false;
+
+    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);
+                    bitmap_set1(ofproto->vlan_bitmap, vid);
+                }
+            }
+        }
+    }
+}
+
+/* Returns true if new VLANs have come into use by the flow table since the
+ * last call to ofproto_get_vlan_usage().
+ *
+ * We don't track when old VLANs stop being used. */
+bool
+ofproto_has_vlan_usage_changed(const struct ofproto *ofproto)
+{
+    return ofproto->vlans_changed;
+}
+
+/* Configures a VLAN splinter binding between the ports identified by OpenFlow
+ * port numbers 'vlandev_ofp_port' and 'realdev_ofp_port'.  If
+ * 'realdev_ofp_port' is nonzero, then the VLAN device is enslaved to the real
+ * device as a VLAN splinter for VLAN ID 'vid'.  If 'realdev_ofp_port' is zero,
+ * then the VLAN device is un-enslaved. */
+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;
+
+    assert(vlandev_ofp_port != realdev_ofp_port);
+
+    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;
+}
index 74b3decec4a0053514a57d45e663c5f10e226db8..4999c8280257af4684c0ac8e85badc34c6ff127b 100644 (file)
@@ -266,6 +266,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,
@@ -313,6 +321,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 *);
+\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. */
+
+void ofproto_get_vlan_usage(struct ofproto *, unsigned long int *vlan_bitmap);
+bool ofproto_has_vlan_usage_changed(const struct ofproto *);
+int ofproto_port_set_realdev(struct ofproto *, uint16_t vlandev_ofp_port,
+                             uint16_t realdev_ofp_port, int vid);
 
 #ifdef  __cplusplus
 }
index ca106c5c18fe97da8d5b363b5277ec01b4742b91..32885ebcfec538c73a399a6e14d5ed323028c3db 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));
+                    }
+                }
+            }
+        }
+    }
+}
index 8eba5cbf6be7a6c9e3b3b370252373e53963ab49..a467be4e327ccfceedba37c21b037ee38f9f67ce 100644 (file)
       </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.