[ovs-dev] [vlans v2 5/5] Implement "native VLAN" feature.

Ben Pfaff blp at nicira.com
Sat Sep 24 00:17:55 UTC 2011


Significant updates by Ben Pfaff, including:

* Comment, coding style, indentation updates.
* Documentation improved.
* Added tests.
* Dropped PORT_VLAN_EMPTY.
---
 NEWS                         |    3 +
 ofproto/ofproto-dpif.c       |  156 +++++++++++++++++++++++++++++++-------
 ofproto/ofproto.h            |   26 ++++++-
 tests/automake.mk            |    1 +
 tests/compare-odp-actions.pl |   66 ++++++++++++++++
 tests/ofproto-dpif.at        |  173 +++++++++++++++++++++++++++++-------------
 vswitchd/bridge.c            |   33 +++++++-
 vswitchd/vswitch.ovsschema   |    8 ++-
 vswitchd/vswitch.xml         |  110 +++++++++++++++++++--------
 9 files changed, 454 insertions(+), 122 deletions(-)
 create mode 100644 tests/compare-odp-actions.pl

diff --git a/NEWS b/NEWS
index 7f28986..1d209e9 100644
--- a/NEWS
+++ b/NEWS
@@ -18,6 +18,9 @@ Post-v1.2.0
     - Flow setups are now processed in a round-robin manner across ports
       to prevent any single client from monopolizing the CPU and conducting
       a denial of service attack.
+    - Added support for native VLAN tagging.  A new "vlan_mode"
+      parameter can be set for "port". Possible values: "access",
+      "trunk", "native-tagged" and "native-untagged".
 
 v1.2.0 - 03 Aug 2011
 ------------------------
diff --git a/ofproto/ofproto-dpif.c b/ofproto/ofproto-dpif.c
index 0459e96..ecad489 100644
--- a/ofproto/ofproto-dpif.c
+++ b/ofproto/ofproto-dpif.c
@@ -138,6 +138,7 @@ struct ofbundle {
 
     /* Configuration. */
     struct list ports;          /* Contains "struct ofport"s. */
+    enum port_vlan_mode vlan_mode; /* VLAN mode */
     int vlan;                   /* -1=trunk port, else a 12-bit VLAN ID. */
     unsigned long *trunks;      /* Bitmap of trunked VLANs, if 'vlan' == -1.
                                  * NULL if all VLANs are trunked. */
@@ -1029,9 +1030,10 @@ bundle_set(struct ofproto *ofproto_, void *aux,
 {
     struct ofproto_dpif *ofproto = ofproto_dpif_cast(ofproto_);
     bool need_flush = false;
-    const unsigned long *trunks;
     struct ofport_dpif *port;
     struct ofbundle *bundle;
+    unsigned long *trunks;
+    int vlan;
     size_t i;
     bool ok;
 
@@ -1054,6 +1056,7 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         bundle->name = NULL;
 
         list_init(&bundle->ports);
+        bundle->vlan_mode = PORT_VLAN_TRUNK;
         bundle->vlan = -1;
         bundle->trunks = NULL;
         bundle->lacp = NULL;
@@ -1113,19 +1116,65 @@ bundle_set(struct ofproto *ofproto_, void *aux,
         return EINVAL;
     }
 
+    /* Set VLAN tagging mode */
+    if (s->vlan_mode != bundle->vlan_mode) {
+        bundle->vlan_mode = s->vlan_mode;
+        need_flush = true;
+    }
+
     /* Set VLAN tag. */
-    if (s->vlan != bundle->vlan) {
-        bundle->vlan = s->vlan;
+    vlan = (s->vlan_mode == PORT_VLAN_TRUNK ? -1
+            : s->vlan >= 0 && s->vlan <= 4095 ? s->vlan
+            : 0);
+    if (vlan != bundle->vlan) {
+        bundle->vlan = vlan;
         need_flush = true;
     }
 
     /* Get trunked VLANs. */
-    trunks = s->vlan == -1 ? s->trunks : NULL;
+    switch (s->vlan_mode) {
+    case PORT_VLAN_ACCESS:
+        trunks = NULL;
+        break;
+
+    case PORT_VLAN_TRUNK:
+        trunks = (unsigned long *) s->trunks;
+        break;
+
+    case PORT_VLAN_NATIVE_UNTAGGED:
+    case PORT_VLAN_NATIVE_TAGGED:
+        if (vlan != 0 && (!s->trunks
+                          || !bitmap_is_set(s->trunks, vlan)
+                          || bitmap_is_set(s->trunks, 0))) {
+            /* Force trunking the native VLAN and prohibit trunking VLAN 0. */
+            if (s->trunks) {
+                trunks = bitmap_clone(s->trunks, 4096);
+            } else {
+                trunks = bitmap_allocate1(4096);
+            }
+            bitmap_set1(trunks, vlan);
+            bitmap_set0(trunks, 0);
+        } else {
+            trunks = (unsigned long *) s->trunks;
+        }
+        break;
+
+    default:
+        NOT_REACHED();
+    }
     if (!vlan_bitmap_equal(trunks, bundle->trunks)) {
         free(bundle->trunks);
-        bundle->trunks = vlan_bitmap_clone(trunks);
+        if (trunks == s->trunks) {
+            bundle->trunks = vlan_bitmap_clone(trunks);
+        } else {
+            bundle->trunks = trunks;
+            trunks = NULL;
+        }
         need_flush = true;
     }
+    if (trunks != s->trunks) {
+        free(trunks);
+    }
 
     /* Bonding. */
     if (!list_is_short(&bundle->ports)) {
@@ -3499,20 +3548,71 @@ static void dst_set_free(struct dst_set *);
 
 static struct ofport_dpif *ofbundle_get_a_port(const struct ofbundle *);
 
+/* Given 'vid', the VID obtained from the 802.1Q header that was received as
+ * part of a packet (specify 0 if there was no 802.1Q header), and 'in_bundle',
+ * the bundle on which the packet was received, returns the VLAN to which the
+ * packet belongs.
+ *
+ * Both 'vid' and the return value are in the range 0...4095. */
+static uint16_t
+input_vid_to_vlan(const struct ofbundle *in_bundle, uint16_t vid)
+{
+    switch (in_bundle->vlan_mode) {
+    case PORT_VLAN_ACCESS:
+        return in_bundle->vlan;
+        break;
+
+    case PORT_VLAN_TRUNK:
+        return vid;
+
+    case PORT_VLAN_NATIVE_UNTAGGED:
+    case PORT_VLAN_NATIVE_TAGGED:
+        return vid ? vid : in_bundle->vlan;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
+/* Given 'vlan', the VLAN that a packet belongs to, and
+ * 'out_bundle', a bundle on which the packet is to be output, returns the VID
+ * that should be included in the 802.1Q header.  (If the return value is 0,
+ * then the 802.1Q header should only be included in the packet if there is a
+ * nonzero PCP.)
+ *
+ * Both 'vlan' and the return value are in the range 0...4095. */
+static uint16_t
+output_vlan_to_vid(const struct ofbundle *out_bundle, uint16_t vlan)
+{
+    switch (out_bundle->vlan_mode) {
+    case PORT_VLAN_ACCESS:
+        return 0;
+
+    case PORT_VLAN_TRUNK:
+    case PORT_VLAN_NATIVE_TAGGED:
+        return vlan;
+
+    case PORT_VLAN_NATIVE_UNTAGGED:
+        return vlan == out_bundle->vlan ? 0 : vlan;
+
+    default:
+        NOT_REACHED();
+    }
+}
+
 static bool
 set_dst(struct action_xlate_ctx *ctx, struct dst *dst,
         const struct ofbundle *in_bundle, const struct ofbundle *out_bundle)
 {
-    dst->vid = (out_bundle->vlan >= 0 ? 0
-                : in_bundle->vlan >= 0 ? in_bundle->vlan
-                : ctx->flow.vlan_tci == 0 ? 0
-                : vlan_tci_to_vid(ctx->flow.vlan_tci));
+    uint16_t vlan;
+
+    vlan = input_vid_to_vlan(in_bundle, vlan_tci_to_vid(ctx->flow.vlan_tci));
+    dst->vid = output_vlan_to_vid(out_bundle, vlan);
 
     dst->port = (!out_bundle->bond
                  ? ofbundle_get_a_port(out_bundle)
                  : bond_choose_output_slave(out_bundle->bond, &ctx->flow,
                                             dst->vid, &ctx->tags));
-
     return dst->port != NULL;
 }
 
@@ -3574,7 +3674,7 @@ dst_is_duplicate(const struct dst_set *set, const struct dst *test)
 static bool
 ofbundle_trunks_vlan(const struct ofbundle *bundle, uint16_t vlan)
 {
-    return (bundle->vlan < 0
+    return (bundle->vlan_mode != PORT_VLAN_ACCESS
             && (!bundle->trunks || bitmap_is_set(bundle->trunks, vlan)));
 }
 
@@ -3702,19 +3802,14 @@ compose_mirror_dsts(struct action_xlate_ctx *ctx,
                     if (ofbundle_includes_vlan(bundle, m->out_vlan)
                         && set_dst(ctx, &dst, in_bundle, bundle))
                     {
-                        if (bundle->vlan < 0) {
-                            dst.vid = m->out_vlan;
-                        }
+                        /* set_dst() got dst->vid from the input packet's VLAN,
+                         * not from m->out_vlan, so recompute it. */
+                        dst.vid = output_vlan_to_vid(bundle, m->out_vlan);
+
                         if (dst_is_duplicate(set, &dst)) {
                             continue;
                         }
 
-                        /* Use the vlan tag on the original flow instead of
-                         * the one passed in the vlan parameter.  This ensures
-                         * that we compare the vlan from before any implicit
-                         * tagging tags place. This is necessary because
-                         * dst->vlan is the final vlan, after removing implicit
-                         * tags. */
                         if (bundle == in_bundle && dst.vid == flow_vid) {
                             /* Don't send out input port on same VLAN. */
                             continue;
@@ -3790,8 +3885,9 @@ flow_get_vlan(struct ofproto_dpif *ofproto, const struct flow *flow,
               struct ofbundle *in_bundle, bool have_packet)
 {
     int vlan = vlan_tci_to_vid(flow->vlan_tci);
-    if (in_bundle->vlan >= 0) {
-        if (vlan) {
+    if (vlan) {
+        if (in_bundle->vlan_mode == PORT_VLAN_ACCESS) {
+            /* Drop tagged packet on access port */
             if (have_packet) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged "
@@ -3801,10 +3897,10 @@ flow_get_vlan(struct ofproto_dpif *ofproto, const struct flow *flow,
                              in_bundle->name, in_bundle->vlan);
             }
             return -1;
-        }
-        vlan = in_bundle->vlan;
-    } else {
-        if (!ofbundle_includes_vlan(in_bundle, vlan)) {
+        } else if (ofbundle_includes_vlan(in_bundle, vlan)) {
+            return vlan;
+        } else {
+            /* Drop packets from a VLAN not member of the trunk */
             if (have_packet) {
                 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
                 VLOG_WARN_RL(&rl, "bridge %s: dropping VLAN %d tagged "
@@ -3814,9 +3910,13 @@ flow_get_vlan(struct ofproto_dpif *ofproto, const struct flow *flow,
             }
             return -1;
         }
+    } else {
+        if (in_bundle->vlan_mode != PORT_VLAN_TRUNK) {
+            return in_bundle->vlan;
+        } else {
+            return ofbundle_includes_vlan(in_bundle, 0) ? 0 : -1;
+        }
     }
-
-    return vlan;
 }
 
 /* A VM broadcasts a gratuitous ARP to indicate that it has resumed after
diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h
index 3552aac..1f39a25 100644
--- a/ofproto/ofproto.h
+++ b/ofproto/ofproto.h
@@ -180,6 +180,27 @@ void ofproto_port_set_cfm(struct ofproto *, uint16_t ofp_port,
                           const struct cfm_settings *);
 int ofproto_port_is_lacp_current(struct ofproto *, uint16_t ofp_port);
 
+/* The behaviour of the port regarding VLAN handling */
+enum port_vlan_mode {
+    /* This port is an access port.  'vlan' is the VLAN ID.  'trunks' is
+     * ignored. */
+    PORT_VLAN_ACCESS,
+
+    /* This port is a trunk.  'trunks' is the set of trunks. 'vlan' is
+     * ignored. */
+    PORT_VLAN_TRUNK,
+
+    /* Untagged incoming packets are part of 'vlan', as are incoming packets
+     * tagged with 'vlan'.  Outgoing packets tagged with 'vlan' stay tagged.
+     * Other VLANs in 'trunks' are trunked. */
+    PORT_VLAN_NATIVE_TAGGED,
+
+    /* Untagged incoming packets are part of 'vlan', as are incoming packets
+     * tagged with 'vlan'.  Outgoing packets tagged with 'vlan' are untagged.
+     * Other VLANs in 'trunks' are trunked. */
+    PORT_VLAN_NATIVE_UNTAGGED
+};
+
 /* Configuration of bundles. */
 struct ofproto_bundle_settings {
     char *name;                 /* For use in log messages. */
@@ -187,8 +208,9 @@ struct ofproto_bundle_settings {
     uint16_t *slaves;           /* OpenFlow port numbers for slaves. */
     size_t n_slaves;
 
-    int vlan;                   /* VLAN if access port, -1 if trunk port. */
-    unsigned long *trunks;      /* vlan_bitmap, NULL to trunk all VLANs. */
+    enum port_vlan_mode vlan_mode; /* Selects mode for vlan and trunks */
+    int vlan;                   /* VLAN VID, except for PORT_VLAN_TRUNK. */
+    unsigned long *trunks;      /* vlan_bitmap, except for PORT_VLAN_ACCESS. */
 
     struct bond_settings *bond; /* Must be nonnull iff if n_slaves > 1. */
     uint32_t *bond_stable_ids;  /* Array of n_slaves elements. */
diff --git a/tests/automake.mk b/tests/automake.mk
index 9b094de..7655a29 100644
--- a/tests/automake.mk
+++ b/tests/automake.mk
@@ -54,6 +54,7 @@ TESTSUITE_AT = \
 	tests/interface-reconfigure.at
 TESTSUITE = $(srcdir)/tests/testsuite
 DISTCLEANFILES += tests/atconfig tests/atlocal
+EXTRA_DIST += tests/compare-odp-actions.pl
 
 AUTOTEST_PATH = utilities:vswitchd:ovsdb:tests
 
diff --git a/tests/compare-odp-actions.pl b/tests/compare-odp-actions.pl
new file mode 100644
index 0000000..b18a593
--- /dev/null
+++ b/tests/compare-odp-actions.pl
@@ -0,0 +1,66 @@
+# -*- perl -*-
+
+use strict;
+use warnings;
+
+if (@ARGV < 2) {
+    print <<EOF;
+$0: to check ODP sets of actions for equivalence
+usage: $0 ACTIONS1 ACTIONS2 [NAME=NUMBER]...
+where ACTIONS1 and ACTIONS2 are sets of ODP actions as output by, e.g.
+      "ovs-dpctl dump-flows" and each NAME=NUMBER pair identifies an ODP
+      port's name-to-number mapping.
+
+Exits with status 0 if ACTIONS1 and ACTIONS2 are equivalent, with
+status 1 if they differ.
+EOF
+    exit 1;
+}
+
+# Construct mappings between port numbers and names.
+our (%name_to_number);
+our (%number_to_name);
+for (@ARGV[2..$#ARGV]) {
+    my ($name, $number) = /([^=]+)=([0-9]+)/
+      or die "$_: bad syntax (use --help for help)\n";
+    $number_to_name{$number} = $name;
+    $name_to_number{$name} = $number;
+}
+
+my $n1 = normalize_odp_actions($ARGV[0]);
+my $n2 = normalize_odp_actions($ARGV[1]);
+print "Normalized action set 1: $n1\n";
+print "Normalized action set 2: $n2\n";
+exit($n1 ne $n2);
+
+sub normalize_odp_actions {
+    my ($actions) = @_;
+
+    # Transliterate all commas inside parentheses into semicolons.
+    undef while $actions =~ s/(\([^),]*),([^)]*\))/$1;$2/g;
+
+    # Split on commas.
+    my (@actions) = split(',', $actions);
+
+    # Map port numbers into port names.
+    foreach my $s (@actions) {
+        $s = $number_to_name{$s} if exists($number_to_name{$s});
+    }
+
+    # Sort sequential groups of port names into alphabetical order.
+    for (my $i = 0; $i <= $#actions; ) {
+        my $j = $i + 1;
+        if (exists($name_to_number{$actions[$i]})) {
+            for (; $j <= $#actions; $j++) {
+                last if !exists($name_to_number{$actions[$j]});
+            }
+        }
+        @actions[$i..($j - 1)] = sort(@actions[$i..($j - 1)]);
+        $i = $j;
+    }
+
+    # Now compose a string again and transliterate semicolons back to commas.
+    $actions = join(',', @actions);
+    $actions =~ tr/;/,/;
+    return $actions;
+}
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 957d7c7..b5ca08c 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -80,80 +80,147 @@ AT_CHECK([tail -1 stdout], [0],
 OFPROTO_STOP
 AT_CLEANUP
 
-AT_SETUP([ofproto-dpif - trunks])
+AT_SETUP([ofproto-dpif - VLAN handling])
 OVS_VSWITCHD_START(
-  [add-port br0 p1 trunks=10,12 -- set Interface p1 type=dummy -- \
-   add-port br0 p2 tag=10       -- set Interface p2 type=dummy -- \
-   add-port br0 p3 tag=12       -- set Interface p3 type=dummy -- \
-   add-port br0 p4 tag=12       -- set Interface p4 type=dummy])
+  [add-port br0 p1                                  trunks=10,12 -- \
+   add-port br0 p2                           tag=10              -- \
+   add-port br0 p3                           tag=12              -- \
+   add-port br0 p4                           tag=12              -- \
+   add-port br0 p5 vlan_mode=native-tagged   tag=10              -- \
+   add-port br0 p6 vlan_mode=native-tagged   tag=10 trunks=10,12 -- \
+   add-port br0 p7 vlan_mode=native-untagged tag=12              -- \
+   add-port br0 p8 vlan_mode=native-untagged tag=12 trunks=10,12 -- \
+   set Interface p1 type=dummy -- \
+   set Interface p2 type=dummy -- \
+   set Interface p3 type=dummy -- \
+   set Interface p4 type=dummy -- \
+   set Interface p5 type=dummy -- \
+   set Interface p6 type=dummy -- \
+   set Interface p7 type=dummy -- \
+   set Interface p8 type=dummy --])
 
 AT_CHECK(
   [ovs-vsctl \
         -- get Interface p1 ofport \
         -- get Interface p2 ofport \
         -- get Interface p3 ofport \
-        -- get Interface p4 ofport],
+        -- get Interface p4 ofport \
+        -- get Interface p5 ofport \
+        -- get Interface p6 ofport \
+        -- get Interface p7 ofport \
+        -- get Interface p8 ofport],
   [0], [stdout])
 set `cat stdout`
-br0=0 p1=$1 p2=$2 p3=$3 p4=$4
+br0=0 p1=$1 p2=$2 p3=$3 p4=$4 p5=$5 p6=$6 p7=$7 p8=$8
 
-dnl Each of these specifies an in_port, a VLAN VID (or "none"), and one
-dnl or more sets of valid datapath actions.  (The order of datapath
-dnl actions is somewhat unpredictable, hence the ability to specify more
-dnl than one set.)
+dnl Each of these specifies an in_port, a VLAN VID (or "none"), a VLAN
+dnl PCP (used if the VID isn't "none") and the expected set of datapath
+dnl actions.
+dnl
+dnl XXX Some of these actually output an 802.1Q header to an access port
+dnl (see for example the actions for in_port=p3, vlan=0) to qualify the
+dnl packet with a priority.  That should be configurable.
 for tuple in \
-        "$br0 none drop" \
-        "$br0 0 drop" \
-        "$br0 10 $p1,strip_vlan,$p2" \
-        "$br0 11 drop" \
-        "$br0 12 $p1,strip_vlan,$p3,$p4 $p1,strip_vlan,$p4,$p3" \
-        "$p1 none drop" \
-        "$p1 0 drop" \
-        "$p1 10 $br0,strip_vlan,$p2" \
-        "$p1 11 drop" \
-        "$p1 12 $br0,strip_vlan,$p4,$p3 $br0,strip_vlan,$p3,$p4" \
-        "$p2 none set_tci(vid=10,pcp=0),$br0,$p1 set_tci(vid=10,pcp=0),$p1,$br0" \
-        "$p2 0 set_tci(vid=10,pcp=1),$br0,$p1 set_tci(vid=10,pcp=1),$p1,$br0" \
-        "$p2 10 drop" \
-        "$p2 11 drop" \
-        "$p2 12 drop" \
-        "$p3 none $p4,set_tci(vid=12,pcp=0),$br0,$p1 $p4,set_tci(vid=12,pcp=0),$p1,$br0" \
-        "$p3 0 $p4,set_tci(vid=12,pcp=1),$br0,$p1 $p4,set_tci(vid=12,pcp=1),$p1,$br0" \
-        "$p3 10 drop" \
-        "$p3 11 drop" \
-        "$p3 12 drop" \
-        "$p4 none $p3,set_tci(vid=12,pcp=0),$br0,$p1 $p3,set_tci(vid=12,pcp=0),$p1,$br0" \
-        "$p4 0 $p3,set_tci(vid=12,pcp=1),$br0,$p1 $p3,set_tci(vid=12,pcp=1),$p1,$br0" \
-        "$p4 10 drop" \
-        "$p4 11 drop" \
-        "$p4 12 drop"
+        "br0 none 0 drop" \
+        "br0 0    0 drop" \
+        "br0 0    1 drop" \
+        "br0 10   0 p1,p5,p6,p7,p8,pop_vlan,p2" \
+        "br0 10   1 p1,p5,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "br0 11   0 p5,p7" \
+        "br0 11   1 p5,p7" \
+        "br0 12   0 p1,p5,p6,pop_vlan,p3,p4,p7,p8" \
+        "br0 12   1 p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
+        "p1  none 0 drop" \
+        "p1  0    0 drop" \
+        "p1  0    1 drop" \
+        "p1  10   0 br0,p5,p6,p7,p8,pop_vlan,p2" \
+        "p1  10   1 br0,p5,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p1  11   0 drop" \
+        "p1  11   1 drop" \
+        "p1  12   0 br0,p5,p6,pop_vlan,p3,p4,p7,p8" \
+        "p1  12   1 br0,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
+        "p2  none 0 push_vlan(vid=10,pcp=0),br0,p1,p5,p6,p7,p8" \
+        "p2  0    0 pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p5,p6,p7,p8" \
+        "p2  0    1 pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p5,p6,p7,p8" \
+        "p2  10   0 drop" \
+        "p2  10   1 drop" \
+        "p2  11   0 drop" \
+        "p2  11   1 drop" \
+        "p2  12   0 drop" \
+        "p2  12   1 drop" \
+        "p3  none 0 p4,p7,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p3  0    0 p4,p7,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p3  0    1 p4,p7,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p3  10   0 drop" \
+        "p3  10   1 drop" \
+        "p3  11   0 drop" \
+        "p3  11   1 drop" \
+        "p3  12   0 drop" \
+        "p3  12   1 drop" \
+        "p4  none 0 p3,p7,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p4  0    0 p3,p7,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p4  0    1 p3,p7,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p4  10   0 drop" \
+        "p4  10   1 drop" \
+        "p4  11   0 drop" \
+        "p4  11   1 drop" \
+        "p4  12   0 drop" \
+        "p4  12   1 drop" \
+        "p5  none 0 p2,push_vlan(vid=10,pcp=0),br0,p1,p6,p7,p8" \
+        "p5  0    0 p2,pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p6,p7,p8" \
+        "p5  0    1 p2,pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p6,p7,p8" \
+        "p5  10   0 br0,p1,p6,p7,p8,pop_vlan,p2" \
+        "p5  10   1 br0,p1,p6,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p5  11   0 br0,p7" \
+        "p5  11   1 br0,p7" \
+        "p5  12   0 br0,p1,p6,pop_vlan,p3,p4,p7,p8" \
+        "p5  12   1 br0,p1,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
+        "p6  none 0 p2,push_vlan(vid=10,pcp=0),br0,p1,p5,p7,p8" \
+        "p6  0    0 p2,pop_vlan,push_vlan(vid=10,pcp=0),br0,p1,p5,p7,p8" \
+        "p6  0    1 p2,pop_vlan,push_vlan(vid=10,pcp=1),br0,p1,p5,p7,p8" \
+        "p6  10   0 br0,p1,p5,p7,p8,pop_vlan,p2" \
+        "p6  10   1 br0,p1,p5,p7,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p6  11   0 drop" \
+        "p6  11   1 drop" \
+        "p6  12   0 br0,p1,p5,pop_vlan,p3,p4,p7,p8" \
+        "p6  12   1 br0,p1,p5,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7,p8" \
+        "p7  none 0 p3,p4,p8,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p7  0    0 p3,p4,p8,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p7  0    1 p3,p4,p8,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p7  10   0 br0,p1,p5,p6,p8,pop_vlan,p2" \
+        "p7  10   1 br0,p1,p5,p6,p8,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p7  11   0 br0,p5" \
+        "p7  11   1 br0,p5" \
+        "p7  12   0 br0,p1,p5,p6,pop_vlan,p3,p4,p8" \
+        "p7  12   1 br0,p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p8" \
+        "p8  none 0 p3,p4,p7,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p8  0    0 p3,p4,p7,pop_vlan,push_vlan(vid=12,pcp=0),br0,p1,p5,p6" \
+        "p8  0    1 p3,p4,p7,pop_vlan,push_vlan(vid=12,pcp=1),br0,p1,p5,p6" \
+        "p8  10   0 br0,p1,p5,p6,p7,pop_vlan,p2" \
+        "p8  10   1 br0,p1,p5,p6,p7,pop_vlan,push_vlan(vid=0,pcp=1),p2" \
+        "p8  11   0 drop" \
+        "p8  11   1 drop" \
+        "p8  12   0 br0,p1,p5,p6,pop_vlan,p3,p4,p7" \
+        "p8  12   1 br0,p1,p5,p6,pop_vlan,push_vlan(vid=0,pcp=1),p3,p4,p7"
 do
   set $tuple
   in_port=$1
   vlan=$2
-  shift; shift
+  pcp=$3
+  expected=$4
 
+  eval n_in_port=\$$in_port
   if test $vlan = none; then
-    flow="in_port($in_port),eth(src=50:54:00:00:00:01,dst=ff:ff:ff:ff:ff:ff),eth_type(0xabcd)"
+    flow="in_port($n_in_port),eth(src=50:54:00:00:00:01,dst=ff:ff:ff:ff:ff:ff),eth_type(0xabcd)"
   else
-    flow="in_port($in_port),eth(src=50:54:00:00:00:01,dst=ff:ff:ff:ff:ff:ff),vlan(vid=$vlan,pcp=1),eth_type(0xabcd)"
+    flow="in_port($n_in_port),eth(src=50:54:00:00:00:01,dst=ff:ff:ff:ff:ff:ff),vlan(vid=$vlan,pcp=$pcp),eth_type(0xabcd)"
   fi
 
-  AT_CHECK(
-    [echo "-- $tuple"
-     echo "-- br0=$br0 p1=$p1 p2=$p2 p3=$p3 p4=$p4"
-     ovs-appctl ofproto/trace br0 "$flow"],
-    [0], [stdout])
+  AT_CHECK([ovs-appctl ofproto/trace br0 "$flow"], [0], [stdout])
+  actual=`tail -1 stdout | sed 's/Datapath actions: //'`
 
-  actions=`tail -1 stdout | sed 's/Datapath actions: //'`
-  no_match=:
-  for pattern
-  do
-    if test X"$actions" = X"$pattern"; then
-      no_match=false
-    fi
-  done
-  AT_FAIL_IF([$no_match])
+  AT_CHECK([echo "in_port=$in_port vlan=$vlan"
+            $PERL $srcdir/compare-odp-actions.pl "$expected" "$actual" br0=$br0 p1=$p1 p2=$p2 p3=$p3 p4=$p4 p5=$p5 p6=$p6 p7=$p7 p8=$p8], [0], [ignore])
 done
 
 OVS_VSWITCHD_STOP
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index d93d5c5..a576844 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -493,7 +493,6 @@ port_configure(struct port *port)
         if (list_is_short(&port->ifaces)) {
             if (*cfg->tag >= 0 && *cfg->tag <= 4095) {
                 s.vlan = *cfg->tag;
-                VLOG_DBG("port %s: assigning VLAN tag %d", port->name, s.vlan);
             }
         } else {
             /* It's possible that bonded, VLAN-tagged ports make sense.  Maybe
@@ -505,11 +504,35 @@ port_configure(struct port *port)
 
     /* Get VLAN trunks. */
     s.trunks = NULL;
-    if (s.vlan < 0 && cfg->n_trunks) {
+    if (cfg->n_trunks) {
         s.trunks = vlan_bitmap_from_array(cfg->trunks, cfg->n_trunks);
-    } else if (s.vlan >= 0 && cfg->n_trunks) {
-        VLOG_ERR("port %s: ignoring trunks in favor of implicit vlan",
-                 port->name);
+    }
+
+    /* Get VLAN mode. */
+    if (cfg->vlan_mode) {
+        if (!strcmp(cfg->vlan_mode, "access")) {
+            s.vlan_mode = PORT_VLAN_ACCESS;
+        } else if (!strcmp(cfg->vlan_mode, "trunk")) {
+            s.vlan_mode = PORT_VLAN_TRUNK;
+        } else if (!strcmp(cfg->vlan_mode, "native-tagged")) {
+            s.vlan_mode = PORT_VLAN_NATIVE_TAGGED;
+        } else if (!strcmp(cfg->vlan_mode, "native-untagged")) {
+            s.vlan_mode = PORT_VLAN_NATIVE_UNTAGGED;
+        } else {
+            /* This "can't happen" because ovsdb-server should prevent it. */
+            VLOG_ERR("unknown VLAN mode %s", cfg->vlan_mode);
+            s.vlan_mode = PORT_VLAN_TRUNK;
+        }
+    } else {
+        if (s.vlan >= 0) {
+            s.vlan_mode = PORT_VLAN_ACCESS;
+            if (cfg->n_trunks) {
+                VLOG_ERR("port %s: ignoring trunks in favor of implicit vlan",
+                         port->name);
+            }
+        } else {
+            s.vlan_mode = PORT_VLAN_TRUNK;
+        }
     }
 
     /* Get LACP settings. */
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 25047b5..b25ebad 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -1,6 +1,6 @@
 {"name": "Open_vSwitch",
- "version": "6.0.0",
- "cksum": "3439303729 14480",
+ "version": "6.1.0",
+ "cksum": "3987556157 14663",
  "tables": {
    "Open_vSwitch": {
      "columns": {
@@ -115,6 +115,10 @@
                           "minInteger": 0,
                           "maxInteger": 4095},
                   "min": 0, "max": 1}},
+       "vlan_mode": {
+         "type": {"key": {"type": "string",
+           "enum": ["set", ["trunk", "access", "native-tagged", "native-untagged"]]},
+         "min": 0, "max": 1}},
        "qos": {
          "type": {"key": {"type": "uuid",
                           "refTable": "QoS"},
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index fb41196..a9850c6 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -499,50 +499,96 @@
     </column>
 
     <group title="VLAN Configuration">
-      <p>A bridge port must be configured for VLANs in one of two
-        mutually exclusive ways:
-        <ul>
-          <li>A ``trunk port'' has an empty value for <ref
-            column="tag"/>.  Its <ref column="trunks"/> value may be
-            empty or non-empty.</li>
-          <li>An ``implicitly tagged VLAN port'' or ``access port''
-            has an nonempty value for <ref column="tag"/>.  Its
-            <ref column="trunks"/> value must be empty.</li>
-        </ul>
-        If <ref column="trunks"/> and <ref column="tag"/> are both
-        nonempty, the configuration is ill-formed.
+      <p>Bridge ports support the following types of VLAN configuration:</p>
+      <dl>
+        <dt>trunk</dt>
+        <dd>
+          <p>
+            A trunk port carries packets on one or more specified VLANs
+            specified in the <ref column="trunks"/> column (often, on every
+            VLAN).  A packet that ingresses on a trunk port is in the VLAN
+            specified in its 802.1Q header, or VLAN 0 if the packet has no
+            802.1Q header.  A packet that egresses through a trunk port will
+            have a 802.1Q header if it has a nonzero VLAN ID (or a nonzero
+            802.1Q priority).
+          </p>
+
+          <p>
+            Any packet that ingresses on a trunk port tagged with a VLAN that
+            the port does not trunk is dropped.
+          </p>
+        </dd>
+
+        <dt>access</dt>
+        <dd>
+          <p>
+            An access port carries packets on exactly one VLAN specified in the
+            <ref column="tag"/> column.  Packets ingressing and egressing on an
+            access port have no 802.1Q header.
+          </p>
+
+          <p>
+            Any packet with an 802.1Q header that ingresses on an access port
+            is dropped, regardless of whether the VLAN ID in the header is the
+            access port's VLAN ID.
+          </p>
+        </dd>
+
+        <dt>native-tagged</dt>
+        <dd>
+          A native-tagged port resembles a trunk port, with the exception that
+          a packet without an 802.1Q header that ingresses on a native-tagged
+          port is in the ``native VLAN'' (specified in the <ref column="tag"/>
+          column).
+        </dd>
+
+        <dt>native-untagged</dt>
+        <dd>
+          A native-untagged port resembles a native-tagged port, with the
+          exception that a packet that egresses on a native-untagged port in
+          the native VLAN not have an 802.1Q header.
+        </dd>
+      </dl>
+      <p>
+        A packet will only egress through bridge ports that carry the VLAN of
+        the packet, as described by the rules above.
       </p>
 
-      <column name="tag">
-        <p>
-          If this is an access port (see above), the port's implicitly
-          tagged VLAN.  Must be empty if this is a trunk port.
-        </p>
+      <column name="vlan_mode">
         <p>
-          Frames arriving on trunk ports will be forwarded to this
-          port only if they are tagged with the given VLAN (or, if
-          <ref column="tag"/> is 0, then if they lack a VLAN header).
-          Frames arriving on other access ports will be forwarded to
-          this port only if they have the same <ref column="tag"/>
-          value.  Frames forwarded to this port will not have an
-          802.1Q header.
+          The VLAN mode of the port, as described above.  When this column is
+          empty, a default mode is selected as follows:
         </p>
+        <ul>
+          <li>
+            If <ref column="tag"/> contains a value, the port is an access
+            port.  The <ref column="trunks"/> column should be empty.
+          </li>
+          <li>
+            Otherwise, the port is a trunk port.  The <ref column="trunks"/>
+            column value is honored if it is present.
+          </li>
+        </ul>
+      </column>
+
+      <column name="tag">
         <p>
-          When a frame with a 802.1Q header that indicates a nonzero
-          VLAN is received on an access port, it is discarded.
+          For an access port, the port's implicitly tagged VLAN.  For a
+          native-tagged or native-untagged port, the port's native VLAN.  Must
+          be empty if this is a trunk port.
         </p>
       </column>
 
       <column name="trunks">
         <p>
-          If this is a trunk port (see above), the 802.1Q VLAN(s) that
-          this port trunks; if it is empty, then the port trunks all
-          VLANs.  Must be empty if this is an access port.
+          For a trunk, native-tagged, or native-untagged port, the 802.1Q VLAN
+          or VLANs that this port trunks; if it is empty, then the port trunks
+          all VLANs.  Must be empty if this is an access port.
         </p>
         <p>
-          Frames arriving on trunk ports are dropped if they are not
-          in one of the specified VLANs.  For this purpose, packets
-          that have no VLAN header are treated as part of VLAN 0.
+          A native-tagged or native-untagged port always trunks its native
+          VLAN, regardless of whether <ref column="trunks"/> includes that
+          VLAN.
         </p>
       </column>
     </group>
-- 
1.7.4.4




More information about the dev mailing list