vswitchd: Support limiting the number of VLANs carried by a trunk port.
authorBen Pfaff <blp@nicira.com>
Thu, 1 Jan 2009 01:23:21 +0000 (17:23 -0800)
committerBen Pfaff <blp@nicira.com>
Thu, 1 Jan 2009 19:13:57 +0000 (11:13 -0800)
lib/automake.mk
lib/bitmap.c [new file with mode: 0644]
lib/bitmap.h [new file with mode: 0644]
vswitchd/bridge.c
vswitchd/cfg.c
vswitchd/cfg.h
vswitchd/vswitchd.conf.5

index db82e1ed54cb9979857956e4314798ce8e126923..53b17289324b10938d2926dd9c7ca8d221644a7a 100644 (file)
@@ -1,6 +1,8 @@
 noinst_LIBRARIES += lib/libopenflow.a
 
 lib_libopenflow_a_SOURCES = \
+       lib/bitmap.c \
+       lib/bitmap.h \
        lib/command-line.c \
        lib/command-line.h \
        lib/compiler.h \
diff --git a/lib/bitmap.c b/lib/bitmap.c
new file mode 100644 (file)
index 0000000..b2881db
--- /dev/null
@@ -0,0 +1,72 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include <config.h>
+#include "bitmap.h"
+#include <string.h>
+
+/* Sets 'count' consecutive bits in 'bitmap', starting at bit offset 'start',
+ * to 'value'. */
+void
+bitmap_set_multiple(unsigned long *bitmap, size_t start, size_t count,
+                    bool value)
+{
+    for (; count && start % BITMAP_ULONG_BITS; count--) {
+        bitmap_set(bitmap, start++, value);
+    }
+    for (; count >= BITMAP_ULONG_BITS; count -= BITMAP_ULONG_BITS) {
+        *bitmap_unit__(bitmap, start) = -(unsigned long) value;
+        start += BITMAP_ULONG_BITS;
+    }
+    for (; count; count--) {
+        bitmap_set(bitmap, start++, value);
+    }
+}
+
+/* Compares the 'n' bits in bitmaps 'a' and 'b'.  Returns true if all bits are
+ * equal, false otherwise. */
+bool
+bitmap_equal(const unsigned long *a, const unsigned long *b, size_t n)
+{
+    size_t i;
+
+    if (memcmp(a, b, n / BITMAP_ULONG_BITS * sizeof(unsigned long))) {
+        return false;
+    }
+    for (i = ROUND_DOWN(n, BITMAP_ULONG_BITS); i < n; i++) {
+        if (bitmap_is_set(a, i) != bitmap_is_set(b, i)) {
+            return false;
+        }
+    }
+    return true;
+}
diff --git a/lib/bitmap.h b/lib/bitmap.h
new file mode 100644 (file)
index 0000000..7d37b57
--- /dev/null
@@ -0,0 +1,99 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef BITMAP_H
+#define BITMAP_H 1
+
+#include <limits.h>
+#include <stdlib.h>
+#include "util.h"
+
+#define BITMAP_ULONG_BITS (sizeof(unsigned long) * CHAR_BIT)
+
+static inline unsigned long *
+bitmap_unit__(const unsigned long *bitmap, size_t offset)
+{
+    return (unsigned long *) &bitmap[offset / BITMAP_ULONG_BITS];
+}
+
+static inline unsigned long
+bitmap_bit__(size_t offset)
+{
+    return 1UL << (offset % BITMAP_ULONG_BITS);
+}
+
+static inline unsigned long *
+bitmap_allocate(size_t n_bits)
+{
+    return xcalloc(1, ROUND_UP(n_bits, BITMAP_ULONG_BITS));
+}
+
+static inline void
+bitmap_free(unsigned long *bitmap)
+{
+    free(bitmap);
+}
+
+static inline bool
+bitmap_is_set(const unsigned long *bitmap, size_t offset)
+{
+    return (*bitmap_unit__(bitmap, offset) & bitmap_bit__(offset)) != 0;
+}
+
+static inline void
+bitmap_set1(unsigned long *bitmap, size_t offset)
+{
+    *bitmap_unit__(bitmap, offset) |= bitmap_bit__(offset);
+}
+
+static inline void
+bitmap_set0(unsigned long *bitmap, size_t offset)
+{
+    *bitmap_unit__(bitmap, offset) &= ~bitmap_bit__(offset);
+}
+
+static inline void
+bitmap_set(unsigned long *bitmap, size_t offset, bool value)
+{
+    if (value) {
+        bitmap_set1(bitmap, offset);
+    } else {
+        bitmap_set0(bitmap, offset);
+    }
+}
+
+void bitmap_set_multiple(unsigned long *, size_t start, size_t count,
+                         bool value);
+bool bitmap_equal(const unsigned long *, const unsigned long *, size_t n);
+
+#endif /* bitmap.h */
index cd92ecd12ffc695c5f718efb6bb5b41c6348a5a8..db729fff7b3103060e051bb24b4b7ff2391a5c2b 100644 (file)
@@ -45,6 +45,7 @@
 #include <sys/stat.h>
 #include <sys/socket.h>
 #include <unistd.h>
+#include "bitmap.h"
 #include "cfg.h"
 #include "dirs.h"
 #include "dpif.h"
@@ -117,6 +118,7 @@ struct port {
     struct bridge *bridge;
     size_t port_idx;
     int vlan;                   /* 0=trunk port, otherwise a 12-bit VLAN ID. */
+    unsigned long *trunks;      /* Trunked VLANs, if 'vlan' == 0. */
     const char *name;
 
     /* An ordinary bridge port has 1 interface.
@@ -1362,6 +1364,18 @@ dst_is_duplicate(const struct ft_dst *dsts, size_t n_dsts,
     return false;
 }
 
+static bool
+port_trunks_vlan(const struct port *port, uint16_t vlan)
+{
+    return !port->vlan && bitmap_is_set(port->trunks, vlan);
+}
+
+static bool
+port_includes_vlan(const struct port *port, uint16_t vlan)
+{
+    return vlan == port->vlan || port_trunks_vlan(port, vlan);
+}
+
 static size_t
 compose_dsts(const struct bridge *br, const struct flow *flow, uint16_t vlan,
              const struct port *in_port, const struct port *out_port,
@@ -1378,7 +1392,7 @@ compose_dsts(const struct bridge *br, const struct flow *flow, uint16_t vlan,
 
         for (i = 0; i < br->n_ports; i++) {
             struct port *port = br->ports[i];
-            if (port != in_port && (!port->vlan || vlan == port->vlan)
+            if (port != in_port && port_includes_vlan(port, vlan)
                 && set_dst(dst, flow, in_port, port, tags)) {
                 mirrors |= port->dst_mirrors;
                 dst++;
@@ -1572,6 +1586,15 @@ process_flow(struct bridge *br, const struct flow *flow,
             goto done;
         }
         vlan = in_port->vlan;
+    } else {
+        if (!port_includes_vlan(in_port, vlan)) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
+            VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged "
+                         "packet received on port %s not configured for "
+                         "trunking VLAN %d",
+                         br->name, vlan, in_port->name, vlan);
+            goto done;
+        }
     }
 
     /* Drop multicast and broadcast packets on inactive bonded interfaces, to
@@ -2164,6 +2187,7 @@ port_create(struct bridge *br, const char *name)
     port->bridge = br;
     port->port_idx = br->n_ports;
     port->vlan = 0;
+    port->trunks = NULL;
     port->name = xstrdup(name);
     port->active_iface = -1;
 
@@ -2182,6 +2206,7 @@ port_reconfigure(struct port *port)
 {
     bool bonded = cfg_has_section("bonding.%s", port->name);
     struct svec old_ifaces, new_ifaces;
+    unsigned long *trunks;
     int vlan;
     size_t i;
 
@@ -2234,12 +2259,10 @@ port_reconfigure(struct port *port)
     vlan = 0;
     if (cfg_has("vlan.%s.tag", port->name)) {
         if (!bonded) {
-            vlan = cfg_get_int(0, "vlan.%s.tag", port->name);
+            vlan = cfg_get_vlan(0, "vlan.%s.tag", port->name);
             if (vlan >= 0 && vlan <= 4095) {
                 VLOG_DBG("port %s: assigning VLAN tag %d", port->name, vlan);
             } else {
-                VLOG_WARN("port %s: ignoring VLAN tag %d because it is not in "
-                          "the valid range from 0 to 4095", port->name, vlan);
                 vlan = 0;
             }
         } else {
@@ -2253,6 +2276,49 @@ port_reconfigure(struct port *port)
         port->vlan = vlan;
         bridge_flush(port->bridge);
     }
+
+    /* Get trunked VLANs. */
+    trunks = NULL;
+    if (!vlan) {
+        size_t n_trunks, n_errors;
+        size_t i;
+
+        trunks = bitmap_allocate(4096);
+        n_trunks = cfg_count("vlan.%s.trunks", port->name);
+        n_errors = 0;
+        for (i = 0; i < n_trunks; i++) {
+            int trunk = cfg_get_vlan(i, "vlan.%s.trunks", port->name);
+            if (trunk >= 0) {
+                bitmap_set1(trunks, trunk);
+            } else {
+                n_errors++;
+            }
+        }
+        if (n_errors) {
+            VLOG_ERR("port %s: invalid values for %zu trunk VLANs",
+                     port->name, n_trunks);
+        }
+        if (n_errors == n_trunks) {
+            if (n_errors) {
+                VLOG_ERR("port %s: no valid trunks, trunking all VLANs",
+                         port->name);
+            }
+            bitmap_set_multiple(trunks, 0, 4096, 1);
+        }
+    } else {
+        if (cfg_has("vlan.%s.trunks", port->name)) {
+            VLOG_ERR("ignoring vlan.%s.trunks in favor of vlan.%s.vlan",
+                     port->name, port->name);
+        }
+    }
+    if (trunks == NULL
+        ? port->trunks != NULL
+        : port->trunks == NULL || !bitmap_equal(trunks, port->trunks, 4096)) {
+        bridge_flush(port->bridge);
+    }
+    bitmap_free(port->trunks);
+    port->trunks = trunks;
+
     svec_destroy(&old_ifaces);
     svec_destroy(&new_ifaces);
 }
@@ -2279,6 +2345,7 @@ port_destroy(struct port *port)
             iface_destroy(port->ifaces[i]);
         }
         free(port->ifaces);
+        free(port->trunks);
         free(port);
         bridge_flush(br);
     }
@@ -2558,6 +2625,19 @@ vlan_is_mirrored(const struct mirror *m, int vlan)
     return false;
 }
 
+static bool
+port_trunks_any_mirrored_vlan(const struct mirror *m, const struct port *p)
+{
+    size_t i;
+
+    for (i = 0; i < m->n_vlans; i++) {
+        if (port_trunks_vlan(p, m->vlans[i])) {
+            return true;
+        }
+    }
+    return false;
+}
+
 static void
 mirror_reconfigure_one(struct mirror *m)
 {
@@ -2611,13 +2691,9 @@ mirror_reconfigure_one(struct mirror *m)
 
     /* Determine the VLAN transformation. */
     if (cfg_has("%s.transform.set-vlan", pfx)) {
-        set_vlan = cfg_get_int(0, "%s.transform.set-vlan", pfx);
+        set_vlan = cfg_get_vlan(0, "%s.transform.set-vlan", pfx);
         if (set_vlan == 0) {
             set_vlan = OFP_VLAN_NONE;
-        } else if (set_vlan < 0 || set_vlan > 4095) {
-            VLOG_WARN("%s.transform.set-vlan: %s is not a valid VLAN",
-                      pfx, cfg_get_string(0, "%s.transform.set-vlan", pfx));
-            set_vlan = -1;
         }
     } else {
         set_vlan = -1;
@@ -2647,7 +2723,9 @@ mirror_reconfigure_one(struct mirror *m)
 
         if (svec_contains(&m->src_ports, port->name)
             || (m->n_vlans
-                && (!port->vlan || vlan_is_mirrored(m, port->vlan)))) {
+                && (!port->vlan
+                    ? port_trunks_any_mirrored_vlan(m, port)
+                    : vlan_is_mirrored(m, port->vlan)))) {
             port->src_mirrors |= mirror_bit;
         } else {
             port->src_mirrors &= ~mirror_bit;
index b0915494d2ad0db847f5f209243bbf11de936950..757f214d2634bf178f3f6d8aa1c63c4e1accd08e 100644 (file)
@@ -345,6 +345,30 @@ cfg_get_mac(int idx, const char *key_, ...)
     return eth_addr_to_uint64(mac);
 }
 
+/* Returns the value numbered 'idx' of 'key', converted to an integer.  Returns
+ * -1 if 'idx' is greater than or equal to cfg_count(key) or if the value 'idx'
+ * of 'key' is not a valid integer between 0 and 4095.  */
+int
+cfg_get_vlan(int idx, const char *key_, ...)
+{
+    const char *value;
+    int retval;
+    char *key;
+
+    FORMAT_KEY(key_, key);
+    value = get_nth_value(idx, key);
+    if (value && is_int(value)) {
+        retval = atoi(value);
+        if (retval < 0 || retval > 4095) {
+            retval = -1;
+        }
+    } else {
+        retval = -1;
+    }
+    free(key);
+    return retval;
+}
+
 /* Fills 'svec' with all of the string values of 'key'.  The caller must
  * first initialize 'svec'. */
 void
index cb0953e552d446f1b77fd35b75f7fae613190cb5..1cee0560fdea356bf001c8dbd7e823a4d74122d8 100644 (file)
@@ -53,6 +53,7 @@ enum cfg_flags {
     CFG_BOOL = 1 << 3,          /* Boolean. */
     CFG_IP = 1 << 4,            /* IPv4 address. */
     CFG_MAC = 1 << 5,           /* MAC address. */
+    CFG_VLAN = 1 << 6,          /* Integer in range 0...4095. */
 
     /* Number allowed. */
     CFG_REQUIRED = 1 << 6,      /* At least one value allowed. */
@@ -70,6 +71,7 @@ int cfg_get_int(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
 bool cfg_get_bool(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
 uint32_t cfg_get_ip(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
 uint64_t cfg_get_mac(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
+int cfg_get_vlan(int idx, const char *key, ...) PRINTF_FORMAT(2, 3);
 
 void cfg_get_all_strings(struct svec *, const char *key, ...)
     PRINTF_FORMAT(2, 3);
index e9be931c084b2888814f1db5a64fa9bffa845ad0..8e0b5d4df807033e873b90f9467294765b75c69d 100644 (file)
@@ -75,16 +75,50 @@ with network devices \fBeth0\fR, \fBeth1\fR, and \fBeth2\fR:
         port = eth2
 .fi
 .RE
-.SS "802.1Q VLAN tagging"
-By default, bridge ports are trunk ports.  Define key
+.SS "802.1Q VLAN support"
+A bridge port may be configured either as a trunk port or as belonging
+to a single, untagged VLAN.  These two options are mutually exclusive
+and a port must be configured in one way or the other.
+.PP
+.I "Trunk Ports"
+.PP
+By default, bridge ports are trunk ports that carry all VLANs.  To
+limit the VLANs that a trunk port carries, define
+\fBvlan.\fIport\fB.trunks\fR to one or more integers between 0 and
+4095 designating VLANs.  Only frames that have an 802.1Q header with
+one of the listed VLANs are accepted on a trunk port.  If 0 is
+included in the list, then frames without an 802.1Q header are also
+accepted.  Other frames are discarded.
+.PP
+The following syntax makes network device \fBeth0\fR a trunk port that
+carries VLANs 1, 2, and 3:
+.PP
+.RS
+.nf
+
+[vlan "eth0"]
+        trunks = 1
+        trunks = 2
+        trunks = 3
+        
+.fi
+.RE
+.PP
+.I "Untagged VLAN Ports"
+.PP
+A bridge port may be configured with an implicit, untagged VLAN.  
+Define key
 \fBvlan.\fIport\fB.tag\fR to an integer value \fIvid\fR between 1 and
-4095, inclusive, to instead designate the named \fIport\fR as a member
+4095, inclusive, to designate the named \fIport\fR as a member
 of 802.1Q VLAN \fIvid\fR.  When \fIport\fR is assigned a VLAN tag this
 way, frames arriving on trunk ports will be forwarded to \fIport\fR
 only if they are tagged with VLAN \fIvid\fR, and frames arriving on
 other VLAN ports will be forwarded to \fIport\fR only if their
-\fIvid\fR values are equal.  (Frames received on \fIport\fR will not
-have an 802.1Q header.)
+\fIvid\fR values are equal.  Frames forwarded to \fIport\fR will not
+have an 802.1Q header.
+.PP
+When a frame with a 802.1Q header that indicates a nonzero VLAN is
+received on an implicit VLAN port, it is discarded.
 .PP
 The following syntax makes network device \fBeth0\fR a member of VLAN
 101: