[ovs-dev] [RFC PATCH 2/5] OVN: SFC Implementation: New stage for SFC and modified ACL stage
John McDowall
jmcdowall at paloaltonetworks.com
Tue Dec 27 22:19:15 UTC 2016
This is the major body of code that implements SFC. There is a new L2 stage added to
perform the chaining operations and modifications to the ACL stage to direct flows
to the service chain.
Co-authored-by: Flavio Fernandes <flavio at flaviof.com>
Reported at: https://mail.openvswitch.org/pipermail/ovs-discuss/2016-March/040381.html
Reported at: https://mail.openvswitch.org/pipermail/ovs-discuss/2016-May/041359.html
Signed-off-by: John McDowall <jmcdowall at paloaltonetworks.com>
---
ovn/northd/ovn-northd.c | 315 +++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 295 insertions(+), 20 deletions(-)
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index a28327b4d..8afc8a806 100644
--- a/ovn/northd/ovn-northd.c
+++ b/ovn/northd/ovn-northd.c
@@ -105,13 +105,14 @@ enum ovn_stage {
PIPELINE_STAGE(SWITCH, IN, PRE_LB, 4, "ls_in_pre_lb") \
PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 5, "ls_in_pre_stateful") \
PIPELINE_STAGE(SWITCH, IN, ACL, 6, "ls_in_acl") \
- PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 7, "ls_in_qos_mark") \
- PIPELINE_STAGE(SWITCH, IN, LB, 8, "ls_in_lb") \
- PIPELINE_STAGE(SWITCH, IN, STATEFUL, 9, "ls_in_stateful") \
- PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 10, "ls_in_arp_rsp") \
- PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 11, "ls_in_dhcp_options") \
- PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 12, "ls_in_dhcp_response") \
- PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 13, "ls_in_l2_lkup") \
+ PIPELINE_STAGE(SWITCH, IN, CHAIN, 7, "ls_in_chain") \
+ PIPELINE_STAGE(SWITCH, IN, QOS_MARK, 8, "ls_in_qos_mark") \
+ PIPELINE_STAGE(SWITCH, IN, LB, 9, "ls_in_lb") \
+ PIPELINE_STAGE(SWITCH, IN, STATEFUL, 10, "ls_in_stateful") \
+ PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 11, "ls_in_arp_rsp") \
+ PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 12, "ls_in_dhcp_options") \
+ PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 13, "ls_in_dhcp_response") \
+ PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 14, "ls_in_l2_lkup") \
\
/* Logical switch egress stages. */ \
PIPELINE_STAGE(SWITCH, OUT, PRE_LB, 0, "ls_out_pre_lb") \
@@ -2374,8 +2375,12 @@ build_pre_stateful(struct ovn_datapath *od, struct hmap *lflows)
REGBIT_CONNTRACK_DEFRAG" == 1", "ct_next;");
}
+static bool
+build_chain_classifier_entry(struct ovn_datapath *od, struct hmap *ports,
+ const struct nbrec_acl *acl, struct ds *ds_action);
+
static void
-build_acls(struct ovn_datapath *od, struct hmap *lflows)
+build_acls(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
{
bool has_stateful = has_stateful_acl(od);
@@ -2531,6 +2536,18 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
ds_destroy(&match);
}
+ } else if (!strcmp(acl->action, "sfc")) {
+ struct ds ds_action = DS_EMPTY_INITIALIZER;
+
+ if (!build_chain_classifier_entry(od, ports, acl, &ds_action)) {
+ ds_put_format(&ds_action, "drop;");
+ }
+
+ ovn_lflow_add(lflows, od, stage,
+ acl->priority + OVN_ACL_PRI_OFFSET,
+ acl->match, ds_cstr_ro(&ds_action));
+
+ ds_destroy(&ds_action);
} else if (!strcmp(acl->action, "drop")
|| !strcmp(acl->action, "reject")) {
struct ds match = DS_EMPTY_INITIALIZER;
@@ -2641,6 +2658,262 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
}
}
+static int
+cmp_port_pair_groups(const void *ppg1_, const void *ppg2_)
+{
+ const struct nbrec_logical_port_pair_group *const *ppg1p = ppg1_;
+ const struct nbrec_logical_port_pair_group *const *ppg2p = ppg2_;
+ const struct nbrec_logical_port_pair_group *ppg1 = *ppg1p;
+ const struct nbrec_logical_port_pair_group *ppg2 = *ppg2p;
+
+ if (ppg1->n_sortkey == 0 || ppg2->n_sortkey == 0) {
+ return 0;
+ }
+
+ const int64_t key1 = ppg1->sortkey[0];
+ const int64_t key2 = ppg2->sortkey[0];
+
+ if (key1 < key2) {
+ return -1;
+ } else if (key1 > key2) {
+ return 1;
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * Build the rules to insert service chains
+ */
+static bool
+build_chain_classifier_entry(struct ovn_datapath *od, struct hmap *ports,
+ const struct nbrec_acl *acl, struct ds *ds_action /*output*/)
+{
+ /* TODO: match needs to explicitly include inport. That is needed until sfc
+ * has safeguards to tell apart initial packet from a packet that is
+ * already in the chain.
+ */
+ if (!strstr(acl->match, "inport")) {
+ VLOG_INFO("Acl match clause for sfc action needs to provide inport -- %s\n", acl->match);
+ return false;
+ }
+
+ const char *const chain_id = smap_get(&acl->options, "sfc-port-chain");
+ if (!chain_id) {
+ VLOG_INFO("Acl match %s with sfc action requires sfc-port-chain option\n", acl->match);
+ return false;
+ }
+
+ struct uuid lsp_chain_uuid;
+ const bool is_uuid = uuid_from_string(&lsp_chain_uuid, chain_id);
+ bool chain_found = false;
+ const struct nbrec_logical_port_chain *lsp_chain = NULL;
+
+ /* Find port chain referred by acl option */
+ for (size_t i = 0; i < od->nbs->n_port_chains; i++) {
+ lsp_chain = od->nbs->port_chains[i];
+ if (is_uuid) {
+ if (uuid_equals(&lsp_chain->header_.uuid, &lsp_chain_uuid)) {
+ chain_found = true;
+ break;
+ }
+ } else {
+ if (!strcmp(chain_id, lsp_chain->name)) {
+ chain_found = true;
+ break;
+ }
+ }
+ }
+
+ if (!chain_found) {
+ VLOG_INFO("Acl match for sfc action could not locate chain %s\n", chain_id);
+ return false;
+ }
+
+ /* extract first logical port of chain, by looking at the initial port pair group */
+ const struct nbrec_logical_port_pair_group *lppg = NULL;
+ const struct nbrec_logical_port_pair *lpp = NULL;
+ struct ovn_port *first_ovn_port = NULL;
+
+ /* Identify initial port pair group to be used, according to their sortkey */
+ if (lsp_chain->n_port_pair_groups > 0) {
+ struct nbrec_logical_port_pair_group **port_pair_groups
+ = xmalloc(sizeof *port_pair_groups * lsp_chain->n_port_pair_groups);
+ memcpy(port_pair_groups, lsp_chain->port_pair_groups,
+ sizeof *port_pair_groups * lsp_chain->n_port_pair_groups);
+ qsort(port_pair_groups, lsp_chain->n_port_pair_groups, sizeof *port_pair_groups,
+ cmp_port_pair_groups);
+ lppg = port_pair_groups[0];
+ free(port_pair_groups);
+ }
+
+ // TODO: any port pair off of the intial pair group of chain could be used
+ lpp = (lppg && lppg->n_port_pairs > 0) ? lppg->port_pairs[0] : NULL;
+ first_ovn_port = (lpp && lpp->inport) ? ovn_port_find(ports, lpp->inport->name) : NULL;
+
+ if (!first_ovn_port) {
+ VLOG_INFO("Acl match for sfc action could not locate logical port from chain %s" \
+ " port-group %p port-chain %p\n", chain_id, lppg, lpp);
+ return false;
+ }
+
+ ds_put_format(ds_action, "outport = %s;"" output;", first_ovn_port->json_key);
+ return true;
+}
+
+static void
+build_chain(struct ovn_datapath *od, struct hmap *lflows, struct hmap *ports)
+{
+ /*
+ * TODO Items
+ * * IPV6 support
+ * * Load balancing support
+ * * Bidirectional parameter support
+ * * Support modes of VNF (BitW, L2, L3)
+ * * Remove port-security on VNF Ports (if set by CMS)
+ * * Add some state to allow match that does not require 'inport'
+ * * Support visiting the same VNF more than once
+ * * Unit tests!
+ */
+ const uint16_t ingress_inner_priority = 150;
+ //const uint16_t egress_inner_priority = 150;
+
+ struct ovn_port **input_port_array = NULL;
+ struct ovn_port **output_port_array = NULL;
+
+ struct ovn_port *dst_port = NULL;
+ struct ovn_port *src_port = NULL;
+
+ struct nbrec_logical_port_chain *lpc;
+ struct nbrec_logical_port_pair_group *lppg;
+ struct nbrec_logical_port_pair *lpp;
+
+ VLOG_INFO("beginning port-chain insertion\n");
+ /* Ingress table ls_in_chain: default to passing through to the next table
+ * (priority 0)
+ */
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_CHAIN, 0, "1", "next;");
+
+ if (!od->nbs->port_chains) {
+ return; // no shoes, no shirt, no chains: no further work to do here :)
+ }
+
+ struct ds ds_match = DS_EMPTY_INITIALIZER;
+ struct ds ds_action = DS_EMPTY_INITIALIZER;
+
+ VLOG_INFO("Iterating through %"PRIuSIZE" port-chains\n", od->nbs->n_port_chains);
+ /*
+ * Iterate through all the port-chains defined for this datapath.
+ */
+ for (size_t i = 0; i < od->nbs->n_port_chains; i++) {
+ bool obtainedAllNeededInfo = true;
+
+ lpc = od->nbs->port_chains[i];
+ VLOG_INFO("Iterating through %"PRIuSIZE" port-pair-groups for %s\n", lpc->n_port_pair_groups, lpc->name);
+ /*
+ * Allocate space for port-pairs + 1. The Extra 1 represents the final hop to reach desired destination.
+ * TODO: We are going to allocate enough space to hold all the hops: 1 x portGroups + 1. This needs
+ * to enhanced to: SUM(port pairs of each port group) + 1
+ */
+ input_port_array = xmalloc(sizeof *src_port * lpc->n_port_pair_groups + 1);
+ output_port_array = xmalloc(sizeof *dst_port * (lpc->n_port_pair_groups + 1));
+
+ /* Copy port groups from chain and sort them according to their sortkey */
+ struct nbrec_logical_port_pair_group **port_pair_groups
+ = xmalloc(sizeof *port_pair_groups * lpc->n_port_pair_groups);
+ memcpy(port_pair_groups, lpc->port_pair_groups,
+ sizeof *port_pair_groups * lpc->n_port_pair_groups);
+ qsort(port_pair_groups, lpc->n_port_pair_groups, sizeof *port_pair_groups,
+ cmp_port_pair_groups);
+
+ /*
+ * For each port-pair-group in a port chain pull out the port-pairs
+ */
+ for (size_t j = 0; j < lpc->n_port_pair_groups; j++) {
+ lppg = port_pair_groups[j];
+ VLOG_INFO("Iterating through %"PRIuSIZE" port-pair-group %s for chain %s\n", j, lppg->name, lpc->name);
+ for (size_t k = 0; k < lppg->n_port_pairs; k++){
+ /*
+ * TODO: Need to add load balancing logic when LB becomes available.
+ * Until LB is available just take the first PP in the PPG.
+ */
+ if (k > 0) {
+ VLOG_INFO("Warning: Currently lacking support for more than one port-pair %"PRIuSIZE"\n", \
+ lppg->n_port_pairs);
+ break;
+ }
+ lpp = lppg->port_pairs[k];
+
+ input_port_array[j] = lpp->inport ? ovn_port_find(ports, lpp->inport->name) : NULL;
+ VLOG_INFO("In port for port-pair-group %s : %s %p\n", lppg->name,
+ (lpp->inport ? lpp->inport->name : ""), input_port_array[j]);
+ output_port_array[j] = lpp->outport ? ovn_port_find(ports, lpp->outport->name) : NULL;
+ VLOG_INFO("Out port for port-pair-group %s : %s %p \n", lppg->name,
+ (lpp->outport ? lpp->outport->name : ""), output_port_array[j]);
+
+ if (!input_port_array[j] || !output_port_array[j]) obtainedAllNeededInfo = false;
+ }
+ }
+
+ /*
+ * Set last entry in port_array as the last port in chain. Note that for reverse
+ * output_port_array[lpc->n_port_pair_groups] would become the first inport.
+ */
+ dst_port = lpc->last_hop_port ? ovn_port_find(ports, lpc->last_hop_port->name) : NULL;
+ input_port_array[lpc->n_port_pair_groups] = dst_port;
+ VLOG_INFO("In port for last port-pair-group %s %p\n",
+ (lpc->last_hop_port ? lpc->last_hop_port->name : ""),
+ input_port_array[lpc->n_port_pair_groups]);
+
+ struct eth_addr dst_logical_port_ea;
+ ovs_be32 dst_logical_port_ip;
+ if (dst_port && dst_port->nbsp) {
+ if (!ovs_scan(dst_port->nbsp->addresses[0], ETH_ADDR_SCAN_FMT" "IP_SCAN_FMT,
+ ETH_ADDR_SCAN_ARGS(dst_logical_port_ea), IP_SCAN_ARGS(&dst_logical_port_ip))) {
+ obtainedAllNeededInfo = false;
+ }
+ } else {
+ obtainedAllNeededInfo = false;
+ }
+
+ output_port_array[lpc->n_port_pair_groups] = NULL;
+
+ /*
+ * Steer traffic through the port-chain (FORWARD)
+ */
+ if (obtainedAllNeededInfo) {
+ for (size_t j = 0; j < lpc->n_port_pair_groups; j++){
+#ifdef _SFC_CHAIN_IS_IP_BASED
+ ds_put_format(&ds_match, "inport == %s && ip4.dst == " IP_FMT,
+ output_port_array[j]->json_key,
+ IP_ARGS(dst_logical_port_ip));
+#else // #ifdef _SFC_CHAIN_IS_IP_BASED
+ ds_put_format(&ds_match, "inport == %s && eth.dst == " ETH_ADDR_FMT,
+ output_port_array[j]->json_key,
+ ETH_ADDR_ARGS(dst_logical_port_ea));
+#endif // #ifdef _SFC_CHAIN_IS_IP_BASED
+
+ ds_put_format(&ds_action, "outport = %s;"" output;",
+ input_port_array[j+1]->json_key);
+
+ VLOG_INFO("Port chain action %s\n", ds_cstr_ro(&ds_action));
+ ovn_lflow_add(lflows, od, S_SWITCH_IN_CHAIN, ingress_inner_priority,
+ ds_cstr_ro(&ds_match), ds_cstr_ro(&ds_action));
+
+ ds_clear(&ds_match);
+ ds_clear(&ds_action);
+ }
+ }
+
+ free(input_port_array);
+ free(output_port_array);
+ free(port_pair_groups);
+ }
+
+ ds_destroy(&ds_match);
+ ds_destroy(&ds_action);
+}
+
static void
build_qos(struct ovn_datapath *od, struct hmap *lflows) {
ovn_lflow_add(lflows, od, S_SWITCH_IN_QOS_MARK, 0, "1", "next;");
@@ -2769,7 +3042,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
struct ds actions = DS_EMPTY_INITIALIZER;
/* Build pre-ACL and ACL tables for both ingress and egress.
- * Ingress tables 3 through 9. Egress tables 0 through 6. */
+ * Ingress tables 3 through 10. Egress tables 0 through 6. */
struct ovn_datapath *od;
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
@@ -2779,7 +3052,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
build_pre_acls(od, lflows);
build_pre_lb(od, lflows);
build_pre_stateful(od, lflows);
- build_acls(od, lflows);
+ build_acls(od, lflows, ports);
+ build_chain(od, lflows, ports);
build_qos(od, lflows);
build_lb(od, lflows);
build_stateful(od, lflows);
@@ -2852,9 +3126,9 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_PORT_SEC_IP, 0, "1", "next;");
}
- /* Ingress table 10: ARP/ND responder, skip requests coming from localnet
- * and vtep ports. (priority 100); see ovn-northd.8.xml for the
- * rationale. */
+ /* Ingress table 11: ARP/ND responder, skip requests coming from localnet
+ * ports. (priority 100). */
+
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
continue;
@@ -2869,7 +3143,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 10: ARP/ND responder, reply for known IPs.
+ /* Ingress table 11: ARP/ND responder, reply for known IPs.
* (priority 50). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
@@ -2962,7 +3236,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 10: ARP/ND responder, by default goto next.
+ /* Ingress table 11: ARP/ND responder, by default goto next.
* (priority 0)*/
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
@@ -2972,7 +3246,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_ARP_ND_RSP, 0, "1", "next;");
}
- /* Logical switch ingress table 11 and 12: DHCP options and response
+ /* Logical switch ingress table 12 and 13: DHCP options and response
* priority 100 flows. */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
@@ -3051,7 +3325,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 11 and 12: DHCP options and response, by default goto next.
+ /* Ingress table 12 and 13: DHCP options and response, by default goto next.
* (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
@@ -3063,7 +3337,8 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
ovn_lflow_add(lflows, od, S_SWITCH_IN_DHCP_RESPONSE, 0, "1", "next;");
}
- /* Ingress table 13: Destination lookup, broadcast and multicast handling
+
+ /* Ingress table 14: Destination lookup, broadcast and multicast handling
* (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
@@ -3083,7 +3358,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
"outport = \""MC_FLOOD"\"; output;");
}
- /* Ingress table 13: Destination lookup, unicast handling (priority 50), */
+ /* Ingress table 14: Destination lookup, unicast handling (priority 50), */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
continue;
@@ -3133,7 +3408,7 @@ build_lswitch_flows(struct hmap *datapaths, struct hmap *ports,
}
}
- /* Ingress table 13: Destination lookup for unknown MACs (priority 0). */
+ /* Ingress table 14: Destination lookup for unknown MACs (priority 0). */
HMAP_FOR_EACH (od, key_node, datapaths) {
if (!od->nbs) {
continue;
--
2.11.0
More information about the dev
mailing list