With this change, logical rules will include a new entry in
the pipeline for handling SCF. ACLs with 'sfc' action will
invoke the newly added function to steer matched packets into
the SFC chain.
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!
Reported-at: http://openvswitch.org/pipermail/discuss/2016-March/020628.html
Reported-at:
http://openvswitch.org/pipermail/discuss/2016-May/thread.html#21201
Co-authored-by: John McDowall <[email protected]>
Signed-off-by: Flavio Fernandes <[email protected]>
---
ovn/northd/ovn-northd.c | 310 ++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 288 insertions(+), 22 deletions(-)
diff --git a/ovn/northd/ovn-northd.c b/ovn/northd/ovn-northd.c
index 0ab850e..711cf30 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") \
@@ -2365,8 +2366,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);
@@ -2523,14 +2528,17 @@ build_acls(struct ovn_datapath *od, struct hmap *lflows)
ds_destroy(&match);
}
} else if (!strcmp(acl->action, "sfc")) {
- struct ds match = DS_EMPTY_INITIALIZER;
+ struct ds ds_action = DS_EMPTY_INITIALIZER;
+
+ if (!build_chain_classifier_entry(od, ports, acl, &ds_action)) {
+ ds_put_format(&ds_action, "drop;");
+ }
- // XXX FIXME (FF): Do something amazing here
ovn_lflow_add(lflows, od, stage,
acl->priority + OVN_ACL_PRI_OFFSET,
- acl->match, "drop;");
+ acl->match, ds_cstr_ro(&ds_action));
- ds_destroy(&match);
+ ds_destroy(&ds_action);
} else if (!strcmp(acl->action, "drop")
|| !strcmp(acl->action, "reject")) {
struct ds match = DS_EMPTY_INITIALIZER;
@@ -2641,6 +2649,263 @@ 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 +3034,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 +3044,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,7 +3118,7 @@ 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
+ /* Ingress table 11: ARP/ND responder, skip requests coming from localnet
* ports. (priority 100). */
HMAP_FOR_EACH (op, key_node, ports) {
if (!op->nbsp) {
@@ -2867,7 +3133,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) {
@@ -2960,7 +3226,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) {
@@ -2970,7 +3236,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) {
@@ -3049,7 +3315,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) {
@@ -3061,7 +3327,7 @@ 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) {
@@ -3081,7 +3347,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;
@@ -3131,7 +3397,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.7.4
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev