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 <jmcdow...@paloaltonetworks.com> Signed-off-by: Flavio Fernandes <fla...@flaviof.com> --- 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 d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev