- 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
------------------------
/* 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. */
{
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;
bundle->name = NULL;
list_init(&bundle->ports);
+ bundle->vlan_mode = PORT_VLAN_TRUNK;
bundle->vlan = -1;
bundle->trunks = NULL;
bundle->lacp = NULL;
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)) {
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;
}
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)));
}
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;
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 "
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 "
}
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
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. */
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. */
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
--- /dev/null
+# -*- 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;
+}
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
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
/* 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. */
{"name": "Open_vSwitch",
- "version": "6.0.0",
- "cksum": "3439303729 14480",
+ "version": "6.1.0",
+ "cksum": "3987556157 14663",
"tables": {
"Open_vSwitch": {
"columns": {
"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"},
</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>