From ecac4ebf93feed5e95a517b48915e40d92052386 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Fri, 23 Sep 2011 17:03:03 -0700 Subject: [PATCH] Implement "native VLAN" feature. 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 7f289867..1d209e9f 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 0459e961..ecad4890 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 3552aac4..1f39a254 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 8d019e7a..581bdc81 100644 --- a/tests/automake.mk +++ b/tests/automake.mk @@ -56,6 +56,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 00000000..b18a593c --- /dev/null +++ b/tests/compare-odp-actions.pl @@ -0,0 +1,66 @@ +# -*- perl -*- + +use strict; +use warnings; + +if (@ARGV < 2) { + print <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 25047b5c..b25ebad9 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 fb411963..a9850c66 100644 --- a/vswitchd/vswitch.xml +++ b/vswitchd/vswitch.xml @@ -499,50 +499,96 @@ -

A bridge port must be configured for VLANs in one of two - mutually exclusive ways: -

    -
  • A ``trunk port'' has an empty value for . Its value may be - empty or non-empty.
  • -
  • An ``implicitly tagged VLAN port'' or ``access port'' - has an nonempty value for . Its - value must be empty.
  • -
- If and are both - nonempty, the configuration is ill-formed. +

Bridge ports support the following types of VLAN configuration:

+
+
trunk
+
+

+ A trunk port carries packets on one or more specified VLANs + specified in the 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). +

+ +

+ Any packet that ingresses on a trunk port tagged with a VLAN that + the port does not trunk is dropped. +

+
+ +
access
+
+

+ An access port carries packets on exactly one VLAN specified in the + column. Packets ingressing and egressing on an + access port have no 802.1Q header. +

+ +

+ 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. +

+
+ +
native-tagged
+
+ 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 + column). +
+ +
native-untagged
+
+ 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. +
+
+

+ A packet will only egress through bridge ports that carry the VLAN of + the packet, as described by the rules above.

- -

- If this is an access port (see above), the port's implicitly - tagged VLAN. Must be empty if this is a trunk port. -

+

- Frames arriving on trunk ports will be forwarded to this - port only if they are tagged with the given VLAN (or, if - 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 - 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:

+
    +
  • + If contains a value, the port is an access + port. The column should be empty. +
  • +
  • + Otherwise, the port is a trunk port. The + column value is honored if it is present. +
  • +
+
+ +

- 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.

- 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.

- 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 includes that + VLAN.

-- 2.30.2