2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-31 06:15:47 +00:00

Add support for 802.1ad (QinQ tunneling)

Flow key handling changes:
 - Add VLAN header array in struct flow, to record multiple 802.1q VLAN
   headers.
 - Add dpif multi-VLAN capability probing. If datapath supports
   multi-VLAN, increase the maximum depth of nested OVS_KEY_ATTR_ENCAP.

Refactor VLAN handling in dpif-xlate:
 - Introduce 'xvlan' to track VLAN stack during flow processing.
 - Input and output VLAN translation according to the xbundle type.

Push VLAN action support:
 - Allow ethertype 0x88a8 in VLAN headers and push_vlan action.
 - Support push_vlan on dot1q packets.

Use other_config:vlan-limit in table Open_vSwitch to limit maximum VLANs
that can be matched. This allows us to preserve backwards compatibility.

Add test cases for VLAN depth limit, Multi-VLAN actions and QinQ VLAN
handling

Co-authored-by: Thomas F Herbert <thomasfherbert@gmail.com>
Signed-off-by: Thomas F Herbert <thomasfherbert@gmail.com>
Co-authored-by: Xiao Liang <shaw.leon@gmail.com>
Signed-off-by: Xiao Liang <shaw.leon@gmail.com>
Signed-off-by: Eric Garver <e@erig.me>
Signed-off-by: Ben Pfaff <blp@ovn.org>
This commit is contained in:
Eric Garver
2017-03-01 17:47:59 -05:00
committed by Ben Pfaff
parent 4c71600d22
commit f0fb825a37
33 changed files with 1109 additions and 577 deletions

View File

@@ -4423,7 +4423,8 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
bool export_mask, struct ofpbuf *buf)
{
struct ovs_key_ethernet *eth_key;
size_t encap;
size_t encap[FLOW_MAX_VLAN_HEADERS] = {0};
size_t max_vlans;
const struct flow *flow = parms->flow;
const struct flow *data = export_mask ? parms->mask : parms->flow;
@@ -4488,19 +4489,33 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
sizeof *eth_key);
get_ethernet_key(data, eth_key);
if (flow->vlan_tci != htons(0) || flow->dl_type == htons(ETH_TYPE_VLAN)) {
if (OVS_UNLIKELY(parms->probe)) {
max_vlans = FLOW_MAX_VLAN_HEADERS;
} else {
max_vlans = MIN(parms->support.max_vlan_headers, flow_vlan_limit);
}
for (int encaps = 0; encaps < max_vlans; encaps++) {
ovs_be16 tpid = flow->vlans[encaps].tpid;
if (flow->vlans[encaps].tci == htons(0)) {
if (eth_type_vlan(flow->dl_type)) {
/* If VLAN was truncated the tpid is in dl_type */
tpid = flow->dl_type;
} else {
break;
}
}
if (export_mask) {
nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, OVS_BE16_MAX);
} else {
nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, htons(ETH_TYPE_VLAN));
nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, tpid);
}
nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN, data->vlan_tci);
encap = nl_msg_start_nested(buf, OVS_KEY_ATTR_ENCAP);
if (flow->vlan_tci == htons(0)) {
nl_msg_put_be16(buf, OVS_KEY_ATTR_VLAN, data->vlans[encaps].tci);
encap[encaps] = nl_msg_start_nested(buf, OVS_KEY_ATTR_ENCAP);
if (flow->vlans[encaps].tci == htons(0)) {
goto unencap;
}
} else {
encap = 0;
}
if (ntohs(flow->dl_type) < ETH_TYPE_MIN) {
@@ -4523,6 +4538,10 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
nl_msg_put_be16(buf, OVS_KEY_ATTR_ETHERTYPE, data->dl_type);
if (eth_type_vlan(flow->dl_type)) {
goto unencap;
}
if (flow->dl_type == htons(ETH_TYPE_IP)) {
struct ovs_key_ipv4 *ipv4_key;
@@ -4616,8 +4635,10 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
}
unencap:
if (encap) {
nl_msg_end_nested(buf, encap);
for (int encaps = max_vlans - 1; encaps >= 0; encaps--) {
if (encap[encaps]) {
nl_msg_end_nested(buf, encap[encaps]);
}
}
}
@@ -5211,63 +5232,78 @@ parse_8021q_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5);
bool is_mask = src_flow != flow;
const struct nlattr *encap
= (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP)
? attrs[OVS_KEY_ATTR_ENCAP] : NULL);
const struct nlattr *encap;
enum odp_key_fitness encap_fitness;
enum odp_key_fitness fitness;
enum odp_key_fitness fitness = ODP_FIT_ERROR;
int encaps = 0;
/* Calculate fitness of outer attributes. */
if (!is_mask) {
expected_attrs |= ((UINT64_C(1) << OVS_KEY_ATTR_VLAN) |
(UINT64_C(1) << OVS_KEY_ATTR_ENCAP));
} else {
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN)) {
expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_VLAN);
}
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP)) {
expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_ENCAP);
}
}
fitness = check_expectations(present_attrs, out_of_range_attr,
expected_attrs, key, key_len);
while (encaps < flow_vlan_limit &&
(is_mask
? (src_flow->vlans[encaps].tci & htons(VLAN_CFI)) != 0
: eth_type_vlan(flow->dl_type))) {
/* Set vlan_tci.
* Remove the TPID from dl_type since it's not the real Ethertype. */
flow->dl_type = htons(0);
flow->vlan_tci = (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN)
? nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN])
: htons(0));
if (!is_mask) {
if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN))) {
return ODP_FIT_TOO_LITTLE;
} else if (flow->vlan_tci == htons(0)) {
/* Corner case for a truncated 802.1Q header. */
if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
return ODP_FIT_TOO_MUCH;
encap = (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP)
? attrs[OVS_KEY_ATTR_ENCAP] : NULL);
/* Calculate fitness of outer attributes. */
if (!is_mask) {
expected_attrs |= ((UINT64_C(1) << OVS_KEY_ATTR_VLAN) |
(UINT64_C(1) << OVS_KEY_ATTR_ENCAP));
} else {
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN)) {
expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_VLAN);
}
return fitness;
} else if (!(flow->vlan_tci & htons(VLAN_CFI))) {
VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN 0x%04"PRIx16" is nonzero "
"but CFI bit is not set", ntohs(flow->vlan_tci));
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP)) {
expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_ENCAP);
}
}
fitness = check_expectations(present_attrs, out_of_range_attr,
expected_attrs, key, key_len);
/* Set vlan_tci.
* Remove the TPID from dl_type since it's not the real Ethertype. */
flow->vlans[encaps].tpid = flow->dl_type;
flow->dl_type = htons(0);
flow->vlans[encaps].tci =
(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN)
? nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN])
: htons(0));
if (!is_mask) {
if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN))) {
return ODP_FIT_TOO_LITTLE;
} else if (flow->vlans[encaps].tci == htons(0)) {
/* Corner case for a truncated 802.1Q header. */
if (fitness == ODP_FIT_PERFECT && nl_attr_get_size(encap)) {
return ODP_FIT_TOO_MUCH;
}
return fitness;
} else if (!(flow->vlans[encaps].tci & htons(VLAN_CFI))) {
VLOG_ERR_RL(&rl, "OVS_KEY_ATTR_VLAN 0x%04"PRIx16" is nonzero "
"but CFI bit is not set",
ntohs(flow->vlans[encaps].tci));
return ODP_FIT_ERROR;
}
} else {
if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP))) {
return fitness;
}
}
/* Now parse the encapsulated attributes. */
if (!parse_flow_nlattrs(nl_attr_get(encap), nl_attr_get_size(encap),
attrs, &present_attrs, &out_of_range_attr)) {
return ODP_FIT_ERROR;
}
} else {
if (!(present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_ENCAP))) {
return fitness;
expected_attrs = 0;
if (!parse_ethertype(attrs, present_attrs, &expected_attrs,
flow, src_flow)) {
return ODP_FIT_ERROR;
}
encaps++;
}
/* Now parse the encapsulated attributes. */
if (!parse_flow_nlattrs(nl_attr_get(encap), nl_attr_get_size(encap),
attrs, &present_attrs, &out_of_range_attr)) {
return ODP_FIT_ERROR;
}
expected_attrs = 0;
if (!parse_ethertype(attrs, present_attrs, &expected_attrs, flow, src_flow)) {
return ODP_FIT_ERROR;
}
encap_fitness = parse_l2_5_onward(attrs, present_attrs, out_of_range_attr,
expected_attrs, flow, key, key_len,
src_flow);
@@ -5399,16 +5435,17 @@ odp_flow_key_to_flow__(const struct nlattr *key, size_t key_len,
}
if (is_mask
? (src_flow->vlan_tci & htons(VLAN_CFI)) != 0
: src_flow->dl_type == htons(ETH_TYPE_VLAN)) {
? (src_flow->vlans[0].tci & htons(VLAN_CFI)) != 0
: eth_type_vlan(src_flow->dl_type)) {
return parse_8021q_onward(attrs, present_attrs, out_of_range_attr,
expected_attrs, flow, key, key_len, src_flow);
}
if (is_mask) {
/* A missing VLAN mask means exact match on vlan_tci 0 (== no VLAN). */
flow->vlan_tci = htons(0xffff);
flow->vlans[0].tpid = htons(0xffff);
flow->vlans[0].tci = htons(0xffff);
if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_VLAN)) {
flow->vlan_tci = nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN]);
flow->vlans[0].tci = nl_attr_get_be16(attrs[OVS_KEY_ATTR_VLAN]);
expected_attrs |= (UINT64_C(1) << OVS_KEY_ATTR_VLAN);
}
}
@@ -5659,35 +5696,28 @@ commit_set_ether_addr_action(const struct flow *flow, struct flow *base_flow,
}
static void
pop_vlan(struct flow *base,
struct ofpbuf *odp_actions, struct flow_wildcards *wc)
{
memset(&wc->masks.vlan_tci, 0xff, sizeof wc->masks.vlan_tci);
if (base->vlan_tci & htons(VLAN_CFI)) {
nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
base->vlan_tci = 0;
}
}
static void
commit_vlan_action(ovs_be16 vlan_tci, struct flow *base,
commit_vlan_action(const struct flow* flow, struct flow *base,
struct ofpbuf *odp_actions, struct flow_wildcards *wc)
{
if (base->vlan_tci == vlan_tci) {
return;
int base_n = flow_count_vlan_headers(base);
int flow_n = flow_count_vlan_headers(flow);
flow_skip_common_vlan_headers(base, &base_n, flow, &flow_n);
/* Pop all mismatching vlan of base, push those of flow */
for (; base_n >= 0; base_n--) {
nl_msg_put_flag(odp_actions, OVS_ACTION_ATTR_POP_VLAN);
wc->masks.vlans[base_n].qtag = OVS_BE32_MAX;
}
pop_vlan(base, odp_actions, wc);
if (vlan_tci & htons(VLAN_CFI)) {
for (; flow_n >= 0; flow_n--) {
struct ovs_action_push_vlan vlan;
vlan.vlan_tpid = htons(ETH_TYPE_VLAN);
vlan.vlan_tci = vlan_tci;
vlan.vlan_tpid = flow->vlans[flow_n].tpid;
vlan.vlan_tci = flow->vlans[flow_n].tci;
nl_msg_put_unspec(odp_actions, OVS_ACTION_ATTR_PUSH_VLAN,
&vlan, sizeof vlan);
}
base->vlan_tci = vlan_tci;
memcpy(base->vlans, flow->vlans, sizeof(base->vlans));
}
/* Wildcarding already done at action translation time. */
@@ -6132,7 +6162,7 @@ commit_odp_actions(const struct flow *flow, struct flow *base,
if (!mpls_done) {
commit_mpls_action(flow, base, odp_actions);
}
commit_vlan_action(flow->vlan_tci, base, odp_actions, wc);
commit_vlan_action(flow, base, odp_actions, wc);
commit_set_priority_action(flow, base, odp_actions, wc, use_masked);
commit_set_pkt_mark_action(flow, base, odp_actions, wc, use_masked);