On 1/15/25 2:07 PM, Odintsov Vladislav wrote: > Hi Ales, > Hi all,
> thanks for your comments! > > Please, see inline. > > On 15.01.2025 15:41, Ales Musil wrote: >> On Sat, Dec 28, 2024 at 4:49 PM Alexandra Rukomoinikova >> <[email protected]> wrote: >> >>> Today the mirror feature in OVN supports only tunnel to local >>> and remote to ports that locate outside of OVN claster. >>> >>> With this feature, traffic to/from a virtual port that >>> can be mirrored to dedicated OVN port. >>> >>> To enable overlay port mirroring with filter functions, >>> we have introduced the necessary schemas in the Northbound db >>> and the associated XML configuration: >>> 1. A field for mirror rules has been added to the mirror table >>> to add reflection rules of mirrored traffic. >>> 2. A new table, titled "Mirror Rule," has been established >>> to filter overlay remote traffic. >>> 3. A new mirror type, titled "lport", has been established >>> for encapsulate mirror traffic to another ovn port >>> >>> For lport mirrors, all processing occurs within OVN, making it >>> unnecessary to involve OVS. In the case of lport mirror we add >>> logical flows about the necessary actions with the package. >>> >>> A new stages named "MIRROR" in logical flow table has been >>> introduced, allowing specification of mirror rule filters for >>> the lport mirror type. >>> >>> Added stage number 2 in the ingress pipeline of the logical switch >>> and table number 7 in the egress pipeline of the logical switch. >>> >>> Packets that meet these criteria are duplicated and delivered to >>> the target port, while the original packet follows its designated >>> pipeline. Northbound's mirror rule table enables the creation of >>> these filters. >>> >>> In case of creating a mirror with the lport type without any rules >>> attached to it, default logical flows are added that duplicate all >>> incoming/outgoing traffic to the target port. >>> >>> At the time of attaching lport mirror to logical switch port, a new >>> port binding mp-target port is created, it's a contrainer port with >>> parent with a parent that is the target port, tagging is unnecessary, >>> packets are sent without VLAN header encapsulation. >>> >>> Signed-off-by: Alexandra Rukomoinikova <[email protected]> >>> Signed-off-by: Vladislav Odintsov <[email protected]> >>> Co-authored-by: Vladislav Odintsov <[email protected]> >>> Tested-by: Ivan Burnin <[email protected]> >>> --- >>> >> Hi Alexandra, Vladislav, >> >> I remember us discussing this during the OvS/OVN conference. I was >> looking through the code and there are two main things that I don't >> feel are right: >> >> 1) The implicit creation of a container port with a bunch of if >> statements to skip the tag. That doesn't feel right and if I remember >> correctly we have suggested creating a new port type that would >> share most of the code with container ports but would allow it to be >> without a tag. This would also avoid the problem of storing a bunch >> of references and flags if the port is actually the mirror port or not. > I'll let Alexandra to comment on this part, if she agree or have any > questions. I agree with Ales on this one. It would be preferable if we didn't overload container ports functionality and have a new port type instead. Under the hood we can refactor the container ports code and use what's relevant from it for both container ports and "mirror" ports. >> 2) Addition of new pipeline stage, are we certain that this is the >> point where we want to mirror traffic? What if there is a use case to >> mirror before ACLs/after LB etc.? That made me think this is very >> similar to the idea of composable services, I know that composable >> services at this point are still a bit away. So my suggestion would be >> to implement this as a sort of "hacky" composable service as a side >> table. That would allow us to avoid an additional stage, we could add >> a "tap point" which would indicate at which stage we want to mirror >> and send it to the side table. > > We've considered mirroring of traffic should not have the choice here. > We must mirror traffic from the nearest point, where the end user sees > it - right from VIF. In the similar way OVS "local"/"erspan" mirroring > works, IIRC, so it seems consistent with new "lport" mirror type, and it > is impossible to change that behaviour. > > The logic here is easy: which traffic we see on the VM/container VIF, > the same traffic we should mirror. If "to-lport" ACL has dropped traffic > before it reached VIF, then we don't mirror it. The same applies to > reverse path: "from-lport" ACL drops traffic, which was sent by the > VM/container, after it appeared on the VIF, so we do mirror it. If user > wants to skip some traffic from being mirrored, mirror filtering rules > can be used. I'm not aware of users, which want to mirror traffic, > which finally didn't reach the VIF because was dropped by platform-level > ACL (usually this is done by ACL/Security Groups logging > functionality). But again, this can be revised in the discussion. > Thinking more about it, this might be good enough indeed. We have other means of tracking what traffic was dropped by ACLs (e.g., ACL sampling). >> >> All of this would be considered as an experimental feature that we >> could eventually convert to composable service once it arrives. Does >> that sound like an acceptable solution for you? My main worry is >> about introducing another feature that could be made potentially >> easily redundant by composable services. >> Unfortunately it's not clear when we'll have support for composable services. So until then I vote +1 for adding this new feature as experimental. We can revisit its support (and decide whether we make it non-experimental) in the 25.09 dev cycle when we'll know more about composable services. >> I have cc'ed all maintainers to hear their opinion in this matter >> if any. >> Regards, Dumitru >> >> controller/lflow.h | 12 +- >>> controller/mirror.c | 4 + >>> controller/physical.c | 7 +- >>> lib/ovn-util.c | 6 + >>> lib/ovn-util.h | 3 +- >>> northd/en-northd.c | 2 + >>> northd/inc-proc-northd.c | 2 + >>> northd/northd.c | 223 ++++++++++++++++++++++++++++++++++++ >>> northd/northd.h | 76 +++++++------ >>> northd/ovn-northd.8.xml | 121 ++++++++++++++------ >>> ovn-nb.ovsschema | 25 +++- >>> ovn-nb.xml | 45 +++++++- >>> ovn-sb.ovsschema | 7 +- >>> ovn-sb.xml | 2 +- >>> tests/ovn-macros.at | 10 +- >>> tests/ovn-nbctl.at | 99 +++++++++++++++- >>> tests/ovn-northd.at | 240 +++++++++++++++++++++++++++++++++++++++ >>> utilities/ovn-nbctl.c | 214 ++++++++++++++++++++++++++++++++-- >>> 18 files changed, 997 insertions(+), 101 deletions(-) >>> >>> diff --git a/controller/lflow.h b/controller/lflow.h >>> index 206328f9e..a059300b1 100644 >>> --- a/controller/lflow.h >>> +++ b/controller/lflow.h >>> @@ -67,17 +67,17 @@ struct uuid; >>> >>> /* Start of LOG_PIPELINE_LEN tables. */ >>> #define OFTABLE_LOG_INGRESS_PIPELINE 8 >>> -#define OFTABLE_OUTPUT_LARGE_PKT_DETECT 40 >>> -#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 41 >>> -#define OFTABLE_REMOTE_OUTPUT 42 >>> -#define OFTABLE_LOCAL_OUTPUT 43 >>> -#define OFTABLE_CHECK_LOOPBACK 44 >>> +#define OFTABLE_OUTPUT_LARGE_PKT_DETECT 41 >>> +#define OFTABLE_OUTPUT_LARGE_PKT_PROCESS 42 >>> +#define OFTABLE_REMOTE_OUTPUT 43 >>> +#define OFTABLE_LOCAL_OUTPUT 44 >>> +#define OFTABLE_CHECK_LOOPBACK 45 >>> >>> /* Start of the OUTPUT section of the pipeline. */ >>> #define OFTABLE_OUTPUT_INIT OFTABLE_OUTPUT_LARGE_PKT_DETECT >>> >>> /* Start of LOG_PIPELINE_LEN tables. */ >>> -#define OFTABLE_LOG_EGRESS_PIPELINE 45 >>> +#define OFTABLE_LOG_EGRESS_PIPELINE 46 >>> >> #define OFTABLE_SAVE_INPORT 64 >>> #define OFTABLE_LOG_TO_PHY 65 >>> #define OFTABLE_MAC_BINDING 66 >>> diff --git a/controller/mirror.c b/controller/mirror.c >>> index b557b96da..2f1c811a0 100644 >>> --- a/controller/mirror.c >>> +++ b/controller/mirror.c >>> @@ -121,6 +121,10 @@ mirror_run(struct ovsdb_idl_txn *ovs_idl_txn, >>> /* Iterate through sb mirrors and build the 'ovn_mirrors'. */ >>> const struct sbrec_mirror *sb_mirror; >>> SBREC_MIRROR_TABLE_FOR_EACH (sb_mirror, sb_mirror_table) { >>> + /* We don't need to add mirror to ovs if it is lport mirror. */ >>> + if (!strcmp(sb_mirror->type, "lport")) { >>> + continue; >>> + } >>> >> struct ovn_mirror *m = ovn_mirror_create(sb_mirror->name); >>> m->sb_mirror = sb_mirror; >>> ovn_mirror_add(&ovn_mirrors, m); >>> diff --git a/controller/physical.c b/controller/physical.c >>> index c56c73c20..a0db3fa33 100644 >>> --- a/controller/physical.c >>> +++ b/controller/physical.c >>> @@ -1652,14 +1652,17 @@ consider_port_binding(struct ovsdb_idl_index >>> *sbrec_port_binding_by_name, >>> bool nested_container = false; >>> const struct sbrec_port_binding *parent_port = NULL; >>> ofp_port_t ofport; >>> + bool is_mirror = smap_get_bool(&binding->options, "is-mirror", false); >>> if (binding->parent_port && *binding->parent_port) { >>> - if (!binding->tag) { >>> + if (!binding->tag && !is_mirror) { >>> return; >>> } >>> ofport = local_binding_get_lport_ofport(local_bindings, >>> binding->parent_port); >>> if (ofport) { >>> - tag = *binding->tag; >>> + if (!is_mirror) { >>> + tag = *binding->tag; >>> + } >>> nested_container = true; >>> parent_port = lport_lookup_by_name( >>> sbrec_port_binding_by_name, binding->parent_port); >>> diff --git a/lib/ovn-util.c b/lib/ovn-util.c >>> index b78bdbfa1..b87b1d3b7 100644 >>> --- a/lib/ovn-util.c >>> +++ b/lib/ovn-util.c >>> @@ -1351,3 +1351,9 @@ ovn_update_swconn_at(struct rconn *swconn, const >>> char *target, >>> >>> return notify; >>> } >>> + >>> +char * >>> +ovn_mirror_port_name(const char *port_name) >>> +{ >>> + return xasprintf("mp-%s", port_name); >>> +} >>> diff --git a/lib/ovn-util.h b/lib/ovn-util.h >>> index 899bd9d12..afaaa584a 100644 >>> --- a/lib/ovn-util.h >>> +++ b/lib/ovn-util.h >>> @@ -198,6 +198,7 @@ get_sb_port_group_name(const char *nb_pg_name, int64_t >>> dp_tunnel_key, >>> } >>> >>> char *ovn_chassis_redirect_name(const char *port_name); >>> +char *ovn_mirror_port_name(const char *port_name); >>> void ovn_set_pidfile(const char *name); >>> >>> bool ip46_parse_cidr(const char *str, struct in6_addr *prefix, >>> @@ -310,7 +311,7 @@ BUILD_ASSERT_DECL( >>> #define SCTP_ABORT_CHUNK_FLAG_T (1 << 0) >>> >>> /* The number of tables for the ingress and egress pipelines. */ >>> -#define LOG_PIPELINE_LEN 30 >>> +#define LOG_PIPELINE_LEN 31 >>> >>> static inline uint32_t >>> hash_add_in6_addr(uint32_t hash, const struct in6_addr *addr) >>> diff --git a/northd/en-northd.c b/northd/en-northd.c >>> index c7d1ebcb3..abe7a55a0 100644 >>> --- a/northd/en-northd.c >>> +++ b/northd/en-northd.c >>> @@ -72,6 +72,8 @@ northd_get_input_data(struct engine_node *node, >>> EN_OVSDB_GET(engine_get_input("NB_chassis_template_var", node)); >>> input_data->nbrec_mirror_table = >>> EN_OVSDB_GET(engine_get_input("NB_mirror", node)); >>> + input_data->nbrec_mirror_rule_table = >>> + EN_OVSDB_GET(engine_get_input("NB_mirror_rule", node)); >>> >>> input_data->sbrec_datapath_binding_table = >>> EN_OVSDB_GET(engine_get_input("SB_datapath_binding", node)); >>> diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c >>> index 6e0aa04c4..8413c94e4 100644 >>> --- a/northd/inc-proc-northd.c >>> +++ b/northd/inc-proc-northd.c >>> @@ -58,6 +58,7 @@ static unixctl_cb_func chassis_features_list; >>> NB_NODE(acl, "acl") \ >>> NB_NODE(logical_router, "logical_router") \ >>> NB_NODE(mirror, "mirror") \ >>> + NB_NODE(mirror_rule, "mirror_rule") \ >>> NB_NODE(meter, "meter") \ >>> NB_NODE(bfd, "bfd") \ >>> NB_NODE(static_mac_binding, "static_mac_binding") \ >>> @@ -187,6 +188,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, >>> engine_add_input(&en_global_config, &en_sampling_app, NULL); >>> >>> engine_add_input(&en_northd, &en_nb_mirror, NULL); >>> + engine_add_input(&en_northd, &en_nb_mirror_rule, NULL); >>> engine_add_input(&en_northd, &en_nb_static_mac_binding, NULL); >>> engine_add_input(&en_northd, &en_nb_chassis_template_var, NULL); >>> >>> diff --git a/northd/northd.c b/northd/northd.c >>> index b01e40ecd..ba9646cb6 100644 >>> --- a/northd/northd.c >>> +++ b/northd/northd.c >>> @@ -2129,6 +2129,36 @@ parse_lsp_addrs(struct ovn_port *op) >>> op->n_ps_addrs++; >>> } >>> } >>> + >>> +static void >>> +create_mirror_port(struct ovn_port *op, struct hmap *ports, >>> + struct ovs_list *both_dbs, struct ovs_list *nb_only, >>> + const struct nbrec_mirror *nb_mirror) >>> +{ >>> + char *mp_name = ovn_mirror_port_name(nb_mirror->sink); >>> + struct ovn_port *mp = ovn_port_find(ports, mp_name); >>> + struct ovn_port *target_port = ovn_port_find(ports, nb_mirror->sink); >>> + >>> + if (!target_port) { >>> + return; >>> + } >>> + if (mp && mp->sb) { >>> + ovn_port_set_nb(mp, op->nbsp, NULL); >>> + ovs_list_remove(&mp->list); >>> + ovs_list_push_back(both_dbs, &mp->list); >>> + } else { >>> + mp = ovn_port_create(ports, mp_name, op->nbsp, NULL, NULL); >>> + ovs_list_push_back(nb_only, &mp->list); >>> + } >>> + >>> + op->is_mirror_source_port = true; >>> + mp->primary_port = op; >>> + mp->mirror_parent_port = xstrdup(nb_mirror->sink); >>> + mp->od = op->od; >>> + >>> + free(mp_name); >>> +} >>> + >>> static struct ovn_port * >>> join_logical_ports_lsp(struct hmap *ports, >>> struct ovs_list *nb_only, struct ovs_list *both, >>> @@ -2218,6 +2248,15 @@ join_logical_ports_lsp(struct hmap *ports, >>> hmap_insert(&od->ports, &op->dp_node, >>> hmap_node_hash(&op->key_node)); >>> tag_alloc_add_existing_tags(tag_alloc_table, nbsp); >>> + >>> + op->is_mirror_source_port = false; >>> + for (size_t j = 0; j < nbsp->n_mirror_rules; j++) { >>> + struct nbrec_mirror *mirror = nbsp->mirror_rules[j]; >>> + if (!strcmp(mirror->type, "lport")) { >>> + create_mirror_port(op, ports, both, nb_only, mirror); >>> + } >>> + } >>> + >>> return op; >>> } >>> >>> @@ -3182,6 +3221,20 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn >>> *ovnsb_txn, >>> >>> sbrec_port_binding_set_external_ids(op->sb, >>> &op->nbrp->external_ids); >>> } else { >>> + >>> + if (op->mirror_parent_port) { >>> + /* In case of using a lport mirror, we establish a port >>> binding >>> + * with parent port to act it like container port without >>> + * tag it by vlan tag. */ >>> + struct smap new; >>> + smap_init(&new); >>> + smap_add(&new, "is-mirror", "true"); >>> + sbrec_port_binding_set_parent_port(op->sb, >>> op->mirror_parent_port); >>> + sbrec_port_binding_set_options(op->sb, &new); >>> + smap_destroy(&new); >>> + goto common; >>> + } >>> + >>> if (!lsp_is_router(op->nbsp)) { >>> uint32_t queue_id = smap_get_int( >>> &op->sb->options, "qdisc_queue_id", 0); >>> @@ -3375,6 +3428,8 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn >>> *ovnsb_txn, >>> } >>> >>> } >>> + >>> +common: >>> if (op->tunnel_key != op->sb->tunnel_key) { >>> sbrec_port_binding_set_tunnel_key(op->sb, op->tunnel_key); >>> } >>> @@ -4637,6 +4692,30 @@ check_lsp_changes_other_than_up(const struct >>> nbrec_logical_switch_port *nbsp) >>> return false; >>> } >>> >>> +static bool >>> +is_lsp_mirror_target_port(const struct northd_input *ni, >>> + struct ovn_port *port) >>> +{ >>> + const struct nbrec_mirror *nb_mirror; >>> + NBREC_MIRROR_TABLE_FOR_EACH (nb_mirror, ni->nbrec_mirror_table) { >>> + if (!strcmp(nb_mirror->type, "lport") && >>> + !strcmp(nb_mirror->sink, port->key)) { >>> + return true; >>> + } >>> + } >>> + return false; >>> +} >>> + >>> +static bool >>> +lsp_handle_mirror_rules_changes(const struct nbrec_logical_switch_port >>> *nbsp) >>> +{ >>> + if (nbrec_logical_switch_port_is_updated(nbsp, >>> + NBREC_LOGICAL_SWITCH_PORT_COL_MIRROR_RULES)) { >>> + return false; >>> + } >>> + return true; >>> +} >>> + >>> /* Handles logical switch port changes of a changed logical switch. >>> * Returns false, if any logical port can't be incrementally handled. >>> */ >>> @@ -4707,6 +4786,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn >>> *ovnsb_idl_txn, >>> * by this change. Fallback to recompute. */ >>> goto fail; >>> } >>> + if (!lsp_handle_mirror_rules_changes(new_nbsp) || >>> + is_lsp_mirror_target_port(ni, op)) { >>> + /* Fallback to recompute. */ >>> + goto fail; >>> + } >>> if (!check_lsp_is_up && >>> !check_lsp_changes_other_than_up(new_nbsp)) { >>> /* If the only change is the "up" column while the >>> @@ -4755,6 +4839,11 @@ ls_handle_lsp_changes(struct ovsdb_idl_txn >>> *ovnsb_idl_txn, >>> sbrec_port_binding_delete(op->sb); >>> delete_fdb_entries(ni->sbrec_fdb_by_dp_and_port, >>> od->tunnel_key, >>> op->tunnel_key); >>> + if (is_lsp_mirror_target_port(ni, op)) { >>> + /* This port was used ad target mirror port, fallback >>> + * to recompute. */ >>> + goto fail; >>> + } >>> } >>> } >>> >>> @@ -5862,6 +5951,138 @@ build_dhcpv6_action(struct ovn_port *op, struct >>> in6_addr *offer_ip, >>> return true; >>> } >>> >>> +enum mirror_filter { >>> + IN_MIRROR, >>> + OUT_MIRROR, >>> + BOTH_MIRROR, >>> +}; >>> + >>> +static void >>> +build_mirror_default_lflow(struct ovn_datapath *od, >>> + struct lflow_table *lflows) >>> +{ >>> + ovn_lflow_add(lflows, od, S_SWITCH_IN_MIRROR, 0, "1", "next;", NULL); >>> + ovn_lflow_add(lflows, od, S_SWITCH_OUT_MIRROR, 0, "1", "next;", NULL); >>> +} >>> + >>> +static void >>> +build_mirror_lflow(struct ovn_port *op, >>> + struct lflow_table *lflows, >>> + struct nbrec_mirror *mirror, >>> + struct nbrec_mirror_rule *rule, bool egress) >>> +{ >>> + struct ds match = DS_EMPTY_INITIALIZER; >>> + struct ds action = DS_EMPTY_INITIALIZER; >>> + enum ovn_stage stage; >>> + const char *dir; >>> + >>> + if (egress) { >>> + dir = "outport"; >>> + stage = S_SWITCH_OUT_MIRROR; >>> + } else { >>> + dir = "inport"; >>> + stage = S_SWITCH_IN_MIRROR; >>> + } >>> + >>> + ds_put_format(&match, "%s == %s && %s", dir, op->json_key, >>> rule->match); >>> + >>> + if (!strcmp(rule->action, "mirror")) { >>> + ds_put_format(&action, "clone {outport = \"%s\"; ", >>> + ovn_mirror_port_name(mirror->sink)); >>> + if (egress) { >>> + ds_put_format(&action, "next(pipeline=ingress, table=%d);}; ", >>> + ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN)); >>> + } else { >>> + ds_put_format(&action, "output;}; "); >>> + } >>> + } >>> + >>> + ds_put_cstr(&action, "next;"); >>> + >>> + ovn_lflow_add(lflows, op->od, stage, rule->priority, ds_cstr(&match), >>> + ds_cstr(&action), op->lflow_ref); >>> + >>> + ds_clear(&match); >>> + ds_clear(&action); >>> +} >>> + >>> +static void >>> +build_mirror_pass_lflow(struct ovn_port *op, >>> + struct lflow_table *lflows, >>> + struct nbrec_mirror *mirror, bool egress) >>> +{ >>> + struct ds match = DS_EMPTY_INITIALIZER; >>> + struct ds action = DS_EMPTY_INITIALIZER; >>> + enum ovn_stage stage; >>> + const char *dir; >>> + >>> + ds_put_format(&action, "clone {outport = \"%s\"; ", >>> + ovn_mirror_port_name(mirror->sink)); >>> + >>> + if (egress) { >>> + dir = "outport"; >>> + stage = S_SWITCH_OUT_MIRROR; >>> + ds_put_format(&action, "next(pipeline=ingress, table=%d);}; >>> next;", >>> + ovn_stage_get_table(S_SWITCH_IN_L2_UNKNOWN)); >>> + } else { >>> + dir = "inport"; >>> + stage = S_SWITCH_IN_MIRROR; >>> + ds_put_format(&action, "output;}; next;"); >>> + } >>> + >>> + ds_put_format(&match, "%s == %s", dir, op->json_key); >>> + >>> + ovn_lflow_add(lflows, op->od, stage, 100, >>> + ds_cstr(&match), ds_cstr(&action), op->lflow_ref); >>> + >>> + ds_clear(&match); >>> + ds_clear(&action); >>> +} >>> + >>> +static void >>> +build_mirror_lflows(struct ovn_port *op, >>> + const struct hmap *ls_ports, >>> + struct lflow_table *lflows) >>> +{ >>> + enum mirror_filter filter; >>> + >>> + if (!op->is_mirror_source_port) { >>> + return; >>> + } >>> + >>> + for (size_t i = 0; i < op->nbsp->n_mirror_rules; i++) { >>> + struct nbrec_mirror *mirror = op->nbsp->mirror_rules[i]; >>> + struct ovn_port *target_port = ovn_port_find(ls_ports, >>> + ovn_mirror_port_name(mirror->sink)); >>> + >>> + if (strcmp(mirror->type, "lport") || !target_port) { >>> + continue; >>> + } >>> + >>> + filter = !strcmp(mirror->filter, "from-lport") ? IN_MIRROR : >>> + !strcmp(mirror->filter, "to-lport") ? OUT_MIRROR >>> + : BOTH_MIRROR; >>> + >>> + if (filter == IN_MIRROR || filter == BOTH_MIRROR) { >>> + build_mirror_pass_lflow(op, lflows, mirror, false); >>> + } >>> + if (filter == OUT_MIRROR || filter == BOTH_MIRROR) { >>> + build_mirror_pass_lflow(op, lflows, mirror, true); >>> + } >>> + >>> + for (size_t j = 0; j < mirror->n_mirror_rules; j++) { >>> + struct nbrec_mirror_rule *rule = mirror->mirror_rules[j]; >>> + >>> + if (filter == IN_MIRROR || filter == BOTH_MIRROR) { >>> + build_mirror_lflow(op, lflows, mirror, rule, false); >>> + } >>> + if (filter == OUT_MIRROR || filter == BOTH_MIRROR) { >>> + build_mirror_lflow(op, lflows, mirror, rule, true); >>> + } >>> + } >>> + } >>> +} >>> + >>> /* Adds the logical flows in the (in/out) check port sec stage only if >>> * - the lport is disabled or >>> * - lport is of type vtep - to skip the ingress pipeline. >>> @@ -17366,6 +17587,7 @@ build_lswitch_and_lrouter_iterate_by_ls(struct >>> ovn_datapath *od, >>> struct lswitch_flow_build_info >>> *lsi) >>> { >>> ovs_assert(od->nbs); >>> + build_mirror_default_lflow(od, lsi->lflows); >>> build_lswitch_lflows_pre_acl_and_acl(od, lsi->lflows, >>> lsi->meter_groups, NULL); >>> >>> @@ -17441,6 +17663,7 @@ build_lswitch_and_lrouter_iterate_by_lsp(struct >>> ovn_port *op, >>> ovs_assert(op->nbsp); >>> >>> /* Build Logical Switch Flows. */ >>> + build_mirror_lflows(op, ls_ports, lflows); >>> build_lswitch_port_sec_op(op, lflows, actions, match); >>> build_lswitch_learn_fdb_op(op, lflows, actions, match); >>> build_lswitch_arp_nd_responder_skip_local(op, lflows, match); >>> diff --git a/northd/northd.h b/northd/northd.h >>> index 9457a7be6..e661be7c7 100644 >>> --- a/northd/northd.h >>> +++ b/northd/northd.h >>> @@ -35,6 +35,7 @@ struct northd_input { >>> const struct nbrec_chassis_template_var_table >>> *nbrec_chassis_template_var_table; >>> const struct nbrec_mirror_table *nbrec_mirror_table; >>> + const struct nbrec_mirror_rule_table *nbrec_mirror_rule_table; >>> >>> /* Southbound table references */ >>> const struct sbrec_datapath_binding_table >>> *sbrec_datapath_binding_table; >>> @@ -429,37 +430,38 @@ enum ovn_stage { >>> /* Logical switch ingress stages. */ >>> \ >>> PIPELINE_STAGE(SWITCH, IN, CHECK_PORT_SEC, 0, >>> "ls_in_check_port_sec") \ >>> PIPELINE_STAGE(SWITCH, IN, APPLY_PORT_SEC, 1, >>> "ls_in_apply_port_sec") \ >>> - PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB , 2, "ls_in_lookup_fdb") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 3, "ls_in_put_fdb") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 4, "ls_in_pre_acl") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, PRE_LB, 5, "ls_in_pre_lb") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 6, "ls_in_pre_stateful") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 7, "ls_in_acl_hint") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_EVAL, 8, "ls_in_acl_eval") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_SAMPLE, 9, "ls_in_acl_sample") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_ACTION, 10, "ls_in_acl_action") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, QOS, 11, "ls_in_qos") \ >>> - PIPELINE_STAGE(SWITCH, IN, LB_AFF_CHECK, 12, "ls_in_lb_aff_check") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, LB, 13, "ls_in_lb") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, LB_AFF_LEARN, 14, "ls_in_lb_aff_learn") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 15, "ls_in_pre_hairpin") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 16, "ls_in_nat_hairpin") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 17, "ls_in_hairpin") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_EVAL, 18, \ >>> + PIPELINE_STAGE(SWITCH, IN, MIRROR, 2, "ls_in_mirror") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, LOOKUP_FDB, 3, "ls_in_lookup_fdb") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, PUT_FDB, 4, "ls_in_put_fdb") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, PRE_ACL, 5, "ls_in_pre_acl") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, PRE_LB, 6, "ls_in_pre_lb") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, PRE_STATEFUL, 7, "ls_in_pre_stateful") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_HINT, 8, "ls_in_acl_hint") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_EVAL, 9, "ls_in_acl_eval") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_SAMPLE, 10, "ls_in_acl_sample") \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_ACTION, 11, "ls_in_acl_action") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, QOS, 12, "ls_in_qos") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, LB_AFF_CHECK, 13, "ls_in_lb_aff_check") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, LB, 14, "ls_in_lb") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, LB_AFF_LEARN, 15, "ls_in_lb_aff_learn") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, PRE_HAIRPIN, 16, "ls_in_pre_hairpin") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, NAT_HAIRPIN, 17, "ls_in_nat_hairpin") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, HAIRPIN, 18, "ls_in_hairpin") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_EVAL, 19, \ >>> "ls_in_acl_after_lb_eval") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_SAMPLE, 19, \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_SAMPLE, 20, \ >>> "ls_in_acl_after_lb_sample") \ >>> - PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_ACTION, 20, \ >>> + PIPELINE_STAGE(SWITCH, IN, ACL_AFTER_LB_ACTION, 21, \ >>> "ls_in_acl_after_lb_action") \ >>> - PIPELINE_STAGE(SWITCH, IN, STATEFUL, 21, "ls_in_stateful") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 22, "ls_in_arp_rsp") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 23, "ls_in_dhcp_options") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 24, "ls_in_dhcp_response") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 25, "ls_in_dns_lookup") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 26, "ls_in_dns_response") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 27, "ls_in_external_port") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 28, "ls_in_l2_lkup") >>> \ >>> - PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 29, "ls_in_l2_unknown") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, STATEFUL, 22, "ls_in_stateful") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, ARP_ND_RSP, 23, "ls_in_arp_rsp") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, DHCP_OPTIONS, 24, "ls_in_dhcp_options") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, DHCP_RESPONSE, 25, "ls_in_dhcp_response") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, DNS_LOOKUP, 26, "ls_in_dns_lookup") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, DNS_RESPONSE, 27, "ls_in_dns_response") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, EXTERNAL_PORT, 28, "ls_in_external_port") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, L2_LKUP, 29, "ls_in_l2_lkup") >>> \ >>> + PIPELINE_STAGE(SWITCH, IN, L2_UNKNOWN, 30, "ls_in_l2_unknown") >>> \ >>> >>> \ >>> /* Logical switch egress stages. */ >>> \ >>> PIPELINE_STAGE(SWITCH, OUT, PRE_ACL, 0, "ls_out_pre_acl") >>> \ >>> @@ -469,11 +471,12 @@ enum ovn_stage { >>> PIPELINE_STAGE(SWITCH, OUT, ACL_EVAL, 4, "ls_out_acl_eval") >>> \ >>> PIPELINE_STAGE(SWITCH, OUT, ACL_SAMPLE, 5, "ls_out_acl_sample") >>> \ >>> PIPELINE_STAGE(SWITCH, OUT, ACL_ACTION, 6, "ls_out_acl_action") >>> \ >>> - PIPELINE_STAGE(SWITCH, OUT, QOS, 7, "ls_out_qos") \ >>> - PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 8, "ls_out_stateful") >>> \ >>> - PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 9, >>> "ls_out_check_port_sec") \ >>> - PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 10, >>> "ls_out_apply_port_sec") \ >>> - \ >>> + PIPELINE_STAGE(SWITCH, OUT, MIRROR, 7, "ls_out_mirror") >>> \ >>> + PIPELINE_STAGE(SWITCH, OUT, QOS, 8, "ls_out_qos") >>> \ >>> + PIPELINE_STAGE(SWITCH, OUT, STATEFUL, 9, "ls_out_stateful") >>> \ >>> + PIPELINE_STAGE(SWITCH, OUT, CHECK_PORT_SEC, 10, >>> "ls_out_check_port_sec") \ >>> + PIPELINE_STAGE(SWITCH, OUT, APPLY_PORT_SEC, 11, >>> "ls_out_apply_port_sec") \ >>> + >>> \ >>> /* Logical router ingress stages. */ \ >>> PIPELINE_STAGE(ROUTER, IN, ADMISSION, 0, "lr_in_admission") >>> \ >>> PIPELINE_STAGE(ROUTER, IN, LOOKUP_NEIGHBOR, 1, >>> "lr_in_lookup_neighbor") \ >>> @@ -636,6 +639,13 @@ struct ovn_port { >>> * to NULL. */ >>> struct ovn_port *cr_port; >>> >>> + /* If this ovn_port is a mirror source port, this field is set to >>> true. */ >>> + bool is_mirror_source_port; >>> + >>> + /* If this ovn_port is a mirror target port, this field is set for >>> + * a parent port. */ >>> + const char *mirror_parent_port; >>> + >>> bool has_unknown; /* If the addresses have 'unknown' defined. */ >>> >>> /* The port's peer: >>> diff --git a/northd/ovn-northd.8.xml b/northd/ovn-northd.8.xml >>> index ef5cd0c8c..6ee1e3b75 100644 >>> --- a/northd/ovn-northd.8.xml >>> +++ b/northd/ovn-northd.8.xml >>> @@ -398,7 +398,35 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 2: Lookup MAC address learning table</h3> >>> + <h3>Ingress Table 2: Mirror </h3> >>> + >>> + <p> >>> + Overlay remote mirror table contains the following >>> + logical flows: >>> + </p> >>> + >>> + <ul> >>> + <li> >>> + A priority-100 logical flow is added for each lport mirror >>> + attached to logical switch port, matches all incoming pakcets >>> + to attached port and clones the packet and sends cloned pakcet >>> + to the mirror target port. >>> + </li> >>> + >>> + <li> >>> + A priority 0 flow is added which matches on all packets and >>> applies >>> + the action <code>next;</code>. >>> + </li> >>> + >>> + <li> >>> + A logical flow added for each Mirror Rule in Mirror table attached >>> + to logical switch ports, matches all incoming packets that match >>> + rules and clones the packet and sends cloned packet to mirror >>> + target port. >>> + </li> >>> + </ul> >>> + >>> + <h3>Ingress Table 3: Lookup MAC address learning table</h3> >>> >>> <p> >>> This table looks up the MAC learning table of the logical switch >>> @@ -448,7 +476,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 3: Learn MAC of 'unknown' ports.</h3> >>> + <h3>Ingress Table 4: Learn MAC of 'unknown' ports.</h3> >>> >>> <p> >>> This table learns the MAC addresses seen on the VIF logical ports >>> @@ -485,7 +513,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 4: <code>from-lport</code> Pre-ACLs</h3> >>> + <h3>Ingress Table 5: <code>from-lport</code> Pre-ACLs</h3> >>> >>> <p> >>> This table prepares flows for possible stateful ACL processing in >>> @@ -519,7 +547,7 @@ >>> db="OVN_Northbound"/> table. >>> </p> >>> >>> - <h3>Ingress Table 5: Pre-LB</h3> >>> + <h3>Ingress Table 6: Pre-LB</h3> >>> >>> <p> >>> This table prepares flows for possible stateful load balancing >>> processing >>> @@ -595,7 +623,7 @@ >>> logical router datapath to logical switch datapath. >>> </p> >>> >>> - <h3>Ingress Table 6: Pre-stateful</h3> >>> + <h3>Ingress Table 7: Pre-stateful</h3> >>> >>> <p> >>> This table prepares flows for all possible stateful processing >>> @@ -629,7 +657,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 7: <code>from-lport</code> ACL hints</h3> >>> + <h3>Ingress Table 8: <code>from-lport</code> ACL hints</h3> >>> >>> <p> >>> This table consists of logical flows that set hints >>> @@ -714,7 +742,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress table 8: <code>from-lport</code> ACL evaluation before >>> LB</h3> >>> + <h3>Ingress table 9: <code>from-lport</code> ACL evaluation before >>> LB</h3> >>> >>> <p> >>> Logical flows in this table closely reproduce those in the >>> @@ -893,7 +921,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 9: <code>from-lport</code> ACL sampling</h3> >>> + <h3>Ingress Table 10: <code>from-lport</code> ACL sampling</h3> >>> >>> <p> >>> Logical flows in this table sample traffic matched by >>> @@ -933,7 +961,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 10: <code>from-lport</code> ACL action</h3> >>> + <h3>Ingress Table 11: <code>from-lport</code> ACL action</h3> >>> >>> <p> >>> Logical flows in this table decide how to proceed based on the >>> values of >>> @@ -973,7 +1001,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 11: <code>from-lport</code> QoS</h3> >>> + <h3>Ingress Table 12: <code>from-lport</code> QoS</h3> >>> >>> <p> >>> Logical flows in this table closely reproduce those in the >>> @@ -996,7 +1024,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 12: Load balancing affinity check</h3> >>> + <h3>Ingress Table 13: Load balancing affinity check</h3> >>> >>> <p> >>> Load balancing affinity check table contains the following >>> @@ -1024,7 +1052,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 13: LB</h3> >>> + <h3>Ingress Table 14: LB</h3> >>> >>> <ul> >>> <li> >>> @@ -1104,7 +1132,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 14: Load balancing affinity learn</h3> >>> + <h3>Ingress Table 15: Load balancing affinity learn</h3> >>> >>> <p> >>> Load balancing affinity learn table contains the following >>> @@ -1135,7 +1163,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 15: Pre-Hairpin</h3> >>> + <h3>Ingress Table 16: Pre-Hairpin</h3> >>> <ul> >>> <li> >>> If the logical switch has load balancer(s) configured, then a >>> @@ -1153,7 +1181,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 16: Nat-Hairpin</h3> >>> + <h3>Ingress Table 17: Nat-Hairpin</h3> >>> <ul> >>> <li> >>> If the logical switch has load balancer(s) configured, then a >>> @@ -1188,7 +1216,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 17: Hairpin</h3> >>> + <h3>Ingress Table 18: Hairpin</h3> >>> <ul> >>> <li> >>> <p> >>> @@ -1226,7 +1254,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress table 18: <code>from-lport</code> ACL evaluation after >>> LB</h3> >>> + <h3>Ingress table 19: <code>from-lport</code> ACL evaluation after >>> LB</h3> >>> >>> <p> >>> Logical flows in this table closely reproduce those in the >>> @@ -1311,7 +1339,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 19: <code>from-lport</code> ACL sampling after >>> LB</h3> >>> + <h3>Ingress Table 20: <code>from-lport</code> ACL sampling after >>> LB</h3> >>> >>> <p> >>> Logical flows in this table sample traffic matched by >>> @@ -1351,7 +1379,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 20: <code>from-lport</code> ACL action after LB</h3> >>> + <h3>Ingress Table 21: <code>from-lport</code> ACL action after LB</h3> >>> >>> <p> >>> Logical flows in this table decide how to proceed based on the >>> values of >>> @@ -1391,7 +1419,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 21: Stateful</h3> >>> + <h3>Ingress Table 22: Stateful</h3> >>> >>> <ul> >>> <li> >>> @@ -1414,7 +1442,7 @@ >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 22: ARP/ND responder</h3> >>> + <h3>Ingress Table 23: ARP/ND responder</h3> >>> >>> <p> >>> This table implements ARP/ND responder in a logical switch for known >>> @@ -1749,7 +1777,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 23: DHCP option processing</h3> >>> + <h3>Ingress Table 24: DHCP option processing</h3> >>> >>> <p> >>> This table adds the DHCPv4 options to a DHCPv4 packet from the >>> @@ -1810,7 +1838,7 @@ next; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 24: DHCP responses</h3> >>> + <h3>Ingress Table 25: DHCP responses</h3> >>> >>> <p> >>> This table implements DHCP responder for the DHCP replies generated >>> by >>> @@ -1891,7 +1919,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 25 DNS Lookup</h3> >>> + <h3>Ingress Table 26 DNS Lookup</h3> >>> >>> <p> >>> This table looks up and resolves the DNS names to the corresponding >>> @@ -1920,7 +1948,7 @@ reg0[4] = dns_lookup(); next; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 26 DNS Responses</h3> >>> + <h3>Ingress Table 27 DNS Responses</h3> >>> >>> <p> >>> This table implements DNS responder for the DNS replies generated by >>> @@ -1955,7 +1983,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Ingress table 27 External ports</h3> >>> + <h3>Ingress table 28 External ports</h3> >>> >>> <p> >>> Traffic from the <code>external</code> logical ports enter the >>> ingress >>> @@ -1998,7 +2026,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 28 Destination Lookup</h3> >>> + <h3>Ingress Table 29 Destination Lookup</h3> >>> >>> <p> >>> This table implements switching behavior. It contains these logical >>> @@ -2224,7 +2252,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Ingress Table 29 Destination unknown</h3> >>> + <h3>Ingress Table 30 Destination unknown</h3> >>> >>> <p> >>> This table handles the packets whose destination was not found or >>> @@ -2277,6 +2305,7 @@ output; >>> </li> >>> </ul> >>> >>> + >>> <h3>Egress Table 0: <code>to-lport</code> Pre-ACLs</h3> >>> >>> <p> >>> @@ -2442,21 +2471,49 @@ output; >>> This is similar to ingress table <code>ACL action</code>. >>> </p> >>> >>> - <h3>Egress Table 7: <code>to-lport</code> QoS</h3> >>> + <h3>Egress Table 7: Mirror </h3> >>> + >>> + <p> >>> + Overlay remote mirror table contains the following >>> + logical flows: >>> + </p> >>> + >>> + <ul> >>> + <li> >>> + A priority-100 logical flow is added for each lport mirror >>> + attached to logical switch port, matches all outcoming pakcets >>> + to attached port and clones the packet and sends cloned pakcet >>> + to the mirror target port. >>> + </li> >>> + >>> + <li> >>> + A priority 0 flow is added which matches on all packets and >>> applies >>> + the action <code>next;</code>. >>> + </li> >>> + >>> + <li> >>> + A logical flow added for each Mirror Rule in Mirror table attached >>> + to logical switch ports, matches all outcoming packets that match >>> + rules and clones the packet and sends cloned packet to mirror >>> + target port. >>> + </li> >>> + </ul> >>> + >>> + <h3>Egress Table 8: <code>to-lport</code> QoS</h3> >>> >>> <p> >>> This is similar to ingress table <code>QoS</code> except >>> they apply to <code>to-lport</code> QoS rules. >>> </p> >>> >>> - <h3>Egress Table 8: Stateful</h3> >>> + <h3>Egress Table 9: Stateful</h3> >>> >>> <p> >>> This is similar to ingress table <code>Stateful</code> except that >>> there are no rules added for load balancing new connections. >>> </p> >>> >>> - <h3>Egress Table 9: Egress Port Security - check</h3> >>> + <h3>Egress Table 10: Egress Port Security - check</h3> >>> >>> <p> >>> This is similar to the port security logic in table >>> @@ -2485,7 +2542,7 @@ output; >>> </li> >>> </ul> >>> >>> - <h3>Egress Table 10: Egress Port Security - Apply</h3> >>> + <h3>Egress Table 11: Egress Port Security - Apply</h3> >>> >>> <p> >>> This is similar to the ingress port security logic in ingress table >>> diff --git a/ovn-nb.ovsschema b/ovn-nb.ovsschema >>> index 09361920f..ef1cf1878 100644 >>> --- a/ovn-nb.ovsschema >>> +++ b/ovn-nb.ovsschema >>> @@ -1,7 +1,7 @@ >>> { >>> "name": "OVN_Northbound", >>> - "version": "7.8.0", >>> - "cksum": "3497747919 38626", >>> + "version": "7.9.0", >>> + "cksum": "158800519 39495", >>> "tables": { >>> "NB_Global": { >>> "columns": { >>> @@ -363,13 +363,32 @@ >>> "type": {"type": {"key": {"type": "string", >>> "enum": ["set", ["gre", >>> "erspan", >>> - "local"]]}}}, >>> + "local", >>> + "lport"]]}}}, >>> "index": {"type": "integer"}, >>> + "mirror_rules": { >>> + "type": { >>> + "key": { >>> + "type": "uuid", >>> + "refTable": "Mirror_Rule", >>> + "refType": "strong"}, >>> + "min": 0, >>> + "max": "unlimited"}}, >>> "external_ids": { >>> "type": {"key": "string", "value": "string", >>> "min": 0, "max": "unlimited"}}}, >>> "indexes": [["name"]], >>> "isRoot": true}, >>> + "Mirror_Rule": { >>> + "columns": { >>> + "match": {"type": "string"}, >>> + "action": {"type": { >>> + "key": {"type": "string", >>> + "enum": ["set", ["mirror", "skip"]]}}}, >>> + "priority": {"type": {"key": {"type": "integer", >>> + "minInteger": 0, >>> + "maxInteger": 32767}}}}, >>> + "isRoot": false}, >>> "Meter": { >>> "columns": { >>> "name": {"type": "string"}, >>> diff --git a/ovn-nb.xml b/ovn-nb.xml >>> index 4b86f432d..38f7c62ee 100644 >>> --- a/ovn-nb.xml >>> +++ b/ovn-nb.xml >>> @@ -3078,7 +3078,7 @@ or >>> <column name="type"> >>> <p> >>> The value of this field specifies the mirror type - >>> <code>gre</code>, >>> - <code>erspan</code> or <code>local</code>. >>> + <code>erspan</code>, <code>local</code> or <code>lport</code>. >>> </p> >>> </column> >>> >>> @@ -3092,11 +3092,54 @@ or >>> </p> >>> </column> >>> >>> + <column name="mirror_rules"> >>> + <p> >>> + The value of this field represents the mirror rule for filtering >>> + mirror traffic. >>> + </p> >>> + </column> >>> + >>> <column name="external_ids"> >>> See <em>External IDs</em> at the beginning of this document. >>> </column> >>> </table> >>> >>> + <table name="Mirror_Rule" title="Mirror rule entry"> >>> + <p> >>> + Each row in this table represents a mirror rule that can be used >>> + for filtering of <code>lport</code> mirrored traffic. >>> + </p> >>> + >>> + <column name="match"> >>> + <p> >>> + The match expression, describing which packets should be executed >>> + against Mirror Rule action. The Logical_Flow expression language >>> is >>> + used. >>> + </p> >>> + </column> >>> + >>> + <column name="action"> >>> + <p>The action to take when the Mirror Rule matches:</p> >>> + <ul> >>> + <li> >>> + <code>mirror</code>: Mirror the matched packet. >>> + </li> >>> + >>> + <li> >>> + <code>skip</code>: Do not mirror matched packet. >>> + </li> >>> + </ul> >>> + </column> >>> + >>> + <column name="priority"> >>> + <p> >>> + The Mirror Rule priority. Rule with higher priority takes >>> precedence >>> + over those with lower. A rule is uniquely identified by the >>> priority >>> + and match string. >>> + </p> >>> + </column> >>> + </table> >>> + >>> <table name="Meter" title="Meter entry"> >>> <p> >>> Each row in this table represents a meter that can be used for QoS >>> or >>> diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema >>> index 3056ecc38..5c48ab08e 100644 >>> --- a/ovn-sb.ovsschema >>> +++ b/ovn-sb.ovsschema >>> @@ -1,7 +1,7 @@ >>> { >>> "name": "OVN_Southbound", >>> - "version": "20.38.0", >>> - "cksum": "3113335473 33491", >>> + "version": "20.39.0", >>> + "cksum": "3604877222 33559", >>> "tables": { >>> "SB_Global": { >>> "columns": { >>> @@ -156,7 +156,8 @@ >>> "type": {"type": {"key": {"type": "string", >>> "enum": ["set", ["gre", >>> "erspan", >>> - "local"]]}}}, >>> + "local", >>> + "lport"]]}}}, >>> "index": {"type": "integer"}, >>> "external_ids": { >>> "type": {"key": "string", "value": "string", >>> diff --git a/ovn-sb.xml b/ovn-sb.xml >>> index 3919b44e3..e2010c8e2 100644 >>> --- a/ovn-sb.xml >>> +++ b/ovn-sb.xml >>> @@ -3076,7 +3076,7 @@ tcp.flags = RST; >>> <column name="type"> >>> <p> >>> The value of this field specifies the mirror type - >>> <code>gre</code>, >>> - <code>erspan</code> or <code>local</code>. >>> + <code>erspan</code>, <code>local</code> or <code>lport</code>. >>> </p> >>> </column> >>> >>> diff --git a/tests/ovn-macros.at b/tests/ovn-macros.at >>> index efb333a47..0d702db1a 100644 >>> --- a/tests/ovn-macros.at >>> +++ b/tests/ovn-macros.at >>> @@ -1384,11 +1384,11 @@ m4_define([OVN_CONTROLLER_EXIT], >>> >>> m4_define([OFTABLE_PHY_TO_LOG], [0]) >>> m4_define([OFTABLE_LOG_INGRESS_PIPELINE], [8]) >>> -m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [40]) >>> -m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [41]) >>> -m4_define([OFTABLE_REMOTE_OUTPUT], [42]) >>> -m4_define([OFTABLE_LOCAL_OUTPUT], [43]) >>> -m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [45]) >>> +m4_define([OFTABLE_OUTPUT_LARGE_PKT_DETECT], [41]) >>> +m4_define([OFTABLE_OUTPUT_LARGE_PKT_PROCESS], [42]) >>> +m4_define([OFTABLE_REMOTE_OUTPUT], [43]) >>> +m4_define([OFTABLE_LOCAL_OUTPUT], [44]) >>> +m4_define([OFTABLE_LOG_EGRESS_PIPELINE], [46]) >>> m4_define([OFTABLE_SAVE_INPORT], [64]) >>> m4_define([OFTABLE_LOG_TO_PHY], [65]) >>> m4_define([OFTABLE_MAC_BINDING], [66]) >>> diff --git a/tests/ovn-nbctl.at b/tests/ovn-nbctl.at >>> index 39c02d295..8f27d03b8 100644 >>> --- a/tests/ovn-nbctl.at >>> +++ b/tests/ovn-nbctl.at >>> @@ -457,13 +457,15 @@ check ovn-nbctl ls-add sw0 >>> check ovn-nbctl lsp-add sw0 sw0-port1 >>> check ovn-nbctl lsp-add sw0 sw0-port2 >>> check ovn-nbctl lsp-add sw0 sw0-port3 >>> +check ovn-nbctl lsp-add sw0 sw0-port4 >>> +check ovn-nbctl mirror-add mirror-lport lport to-lport sw0-port1 >>> >>> dnl Add duplicate mirror name >>> AT_CHECK([ovn-nbctl mirror-add mirror1 gre 0 from-lport 10.10.10.5], [1], >>> [], [stderr]) >>> AT_CHECK([grep 'already exists' stderr], [0], [ignore]) >>> >>> dnl Attach invalid source port to mirror >>> -AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port4 mirror3], [1], [], >>> [stderr]) >>> +AT_CHECK([ovn-nbctl lsp-attach-mirror sw0-port5 mirror3], [1], [], >>> [stderr]) >>> AT_CHECK([grep 'port name not found' stderr], [0], [ignore]) >>> >>> dnl Attach source port to invalid mirror >>> @@ -479,6 +481,11 @@ dnl Attach one more source port to mirror >>> check ovn-nbctl lsp-attach-mirror sw0-port3 mirror3 >>> check_column "$mirror3uuid" nb:Logical_Switch_Port mirror_rules >>> name=sw0-port3 >>> >>> +mirror_lportuuid=$(fetch_column nb:Mirror _uuid name=mirror-lport) >>> +dnl Attach source port to lport mirror >>> +check ovn-nbctl lsp-attach-mirror sw0-port4 mirror-lport >>> +check_column "$mirror_lportuuid" nb:Logical_Switch_Port mirror_rules >>> name=sw0-port4 >>> + >>> dnl Verify if multiple ports are attached to the same mirror properly >>> AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >>> mirror-local: >>> @@ -486,6 +493,11 @@ mirror-local: >>> Sink : 10.10.10.3 >>> Filter : both >>> >>> +mirror-lport: >>> + Type : lport >>> + Sink : sw0-port1 >>> + Filter : to-lport >>> + >>> mirror1: >>> Type : gre >>> Sink : 10.10.10.1 >>> @@ -523,6 +535,11 @@ check_column "" nb:Logical_Switch_Port mirror_rules >>> name=sw0-port1 >>> >>> dnl Check if the mirror deleted properly >>> AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >>> +mirror-lport: >>> + Type : lport >>> + Sink : sw0-port1 >>> + Filter : to-lport >>> + >>> mirror1: >>> Type : gre >>> Sink : 10.10.10.1 >>> @@ -537,9 +554,12 @@ mirror2: >>> >>> ]) >>> >>> -dnl Delete another mirror >>> +dnl Delete mirrors one more mirror >>> check ovn-nbctl mirror-del mirror2 >>> >>> +dnl Delete one more mirror >>> +check ovn-nbctl mirror-del mirror-lport >>> + >>> dnl Update the Sink address >>> check ovn-nbctl set mirror . sink=192.168.1.13 >>> >>> @@ -559,6 +579,81 @@ AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >>> >>> dnl --------------------------------------------------------------------- >>> >>> +OVN_NBCTL_TEST([ovn_nbctl_mirrors_rules], [mirror_rules], [ >>> +check ovn-nbctl ls-add sw0 >>> +check ovn-nbctl lsp-add sw0 sw0-p1 >>> +check ovn-nbctl mirror-add mirror1 lport from-lport sw0-p1 >>> +check ovn-nbctl mirror-add mirror2 lport to-lport sw0-p1 >>> + >>> +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror >>> +check ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror >>> + >>> +dnl Add mirror rule for non-exist mirror name >>> +AT_CHECK([ovn-nbctl mirror-rule-add mirror5 150 'ip' allow], [1], [], >>> [stderr]) >>> +AT_CHECK([grep 'not found' stderr], [0], [ignore]) >>> + >>> +dnl Add same mirror rule for mirror >>> +AT_CHECK([ovn-nbctl mirror-rule-add mirror2 150 'ip' mirror], [1], [], >>> [stderr]) >>> +AT_CHECK([grep 'exists' stderr], [0], [ignore]) >>> + >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >>> +mirror1: >>> + Type : lport >>> + Sink : sw0-p1 >>> + Filter : from-lport >>> + Rules : >>> + 100 1 mirror >>> + >>> +mirror2: >>> + Type : lport >>> + Sink : sw0-p1 >>> + Filter : to-lport >>> + Rules : >>> + 150 ip mirror >>> + >>> +]) >>> + >>> +dnl Add one more mirror rule to mirror >>> +check ovn-nbctl mirror-rule-add mirror2 200 'icmp' mirror >>> + >>> +check ovn-nbctl mirror-rule-add mirror2 250 'icmp' skip >>> + >>> +dnl Mirror rule attach mirror >>> +mirrorrule1uuid=$(fetch_column nb:Mirror_rule _uuid name=mirror1) >>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rule >>> mirror_rule="$mirrorrule1uuid" >>> + >>> +dnl Remove the mirror rule by name >>> +check ovn-nbctl mirror-rule-del mirror1 >>> + >>> +dnl Mirror rule dettach mirror >>> +mirrorr1uuid=$(fetch_column nb:Mirror _uuid name=mirror1) >>> +check_column "" nb:Mirror mirror_rule mirror_rule="mirrorr1uuid" >>> + >>> +dnl Delete mirror rule by priority >>> +check ovn-nbctl mirror-rule-del mirror2 200 >>> + >>> +dnl Remove the mirror rule by priority and match >>> +check ovn-nbctl mirror-rule-del mirror2 250 'icmp' >>> + >>> +AT_CHECK([ovn-nbctl mirror-list], [0], [dnl >>> +mirror1: >>> + Type : lport >>> + Sink : sw0-p1 >>> + Filter : from-lport >>> + >>> +mirror2: >>> + Type : lport >>> + Sink : sw0-p1 >>> + Filter : to-lport >>> + Rules : >>> + 150 ip mirror >>> + >>> +]) >>> + >>> +]) >>> + >>> +dnl --------------------------------------------------------------------- >>> + >>> OVN_NBCTL_TEST([ovn_nbctl_nats], [NATs], [ >>> AT_CHECK([ovn-nbctl lr-add lr0]) >>> AT_CHECK([ovn-nbctl lr-nat-add lr0 snatt 30.0.0.2 192.168.1.2], [1], [], >>> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at >>> index 21d9d63ab..ba23ec594 100644 >>> --- a/tests/ovn-northd.at >>> +++ b/tests/ovn-northd.at >>> @@ -14384,3 +14384,243 @@ AT_CHECK([ovn-sbctl lflow-list S1 | grep >>> ls_out_acl_action | grep priority=500 | >>> >>> AT_CLEANUP >>> ]) >>> + >>> +OVN_FOR_EACH_NORTHD_NO_HV([ >>> +AT_SETUP([Check NB mirror rules sync]) >>> +AT_KEYWORDS([mirror rules]) >>> +ovn_start >>> + >>> +ovn-nbctl mirror-add mirror1 lport to-lport lport4 >>> + >>> +check_column mirror1 nb:Mirror name >>> +check_column lport nb:Mirror type >>> +check_column to-lport nb:Mirror filter >>> +check_column lport4 nb:Mirror sink >>> + >>> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' skip >>> + >>> +check_column 100 nb:mirror_rule priority >>> +check_column ip nb:mirror_rule match >>> +check_column skip nb:mirror_rule action >>> + >>> +check ovn-nbctl set mirror_rule . priority=150 >>> +check_column 150 nb:mirror_rule priority >>> +check_column ip nb:mirror_rule match >>> +check_column skip nb:mirror_rule action >>> + >>> +check ovn-nbctl set mirror_rule . match='icmp' >>> +check_column 150 nb:mirror_rule priority >>> +check_column icmp nb:mirror_rule match >>> +check_column skip nb:mirror_rule action >>> + >>> +check ovn-nbctl set mirror_rule . action=mirror >>> +check_column 150 nb:mirror_rule priority >>> +check_column icmp nb:mirror_rule match >>> +check_column mirror nb:mirror_rule action >>> + >>> +dnl Mirror rule attach mirror >>> +mirrorrule1uuid=$(fetch_column nb:mirror_rule _uuid priority=100) >>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules >>> mirror_rules="$mirrorrule1uuid" >>> + >>> +check ovn-nbctl mirror-rule-add mirror1 200 'ip' mirror >>> +mirrorrule2uuid=$(fetch_column nb:mirror_rule _uuid priority=150) >>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules >>> mirror_rules="$mirrorrule1uuid","$mirrorrule2uuid" >>> + >>> +check ovn-nbctl mirror-rule-del mirror1 >>> +check_column "$mirrorrule1uuid" nb:Mirror mirror_rules mirror_rules="" >>> + >>> +AT_CLEANUP >>> +]) >>> + >>> +OVN_FOR_EACH_NORTHD_NO_HV([ >>> +AT_SETUP([Mirror rule lflows]) >>> +AT_KEYWORDS([mirror rules]) >>> +ovn_start >>> + >>> +check ovn-nbctl ls-add sw0 >>> +check ovn-nbctl ls-add sw1 >>> +check ovn-nbctl lsp-add sw0 sw0-p1 -- lsp-set-addresses sw0-p1 >>> "50:54:00:00:00:01 10.0.0.11" >>> +check ovn-nbctl lsp-add sw0 sw0-target0 >>> +check ovn-nbctl lsp-add sw1 sw1-target1 >>> + >>> +check ovn-nbctl mirror-add mirror0 lport to-lport sw0-target0 >>> +check ovn-nbctl mirror-add mirror1 lport to-lport sw0-target0 >>> +check ovn-nbctl mirror-rule-add mirror1 100 'ip' mirror >>> +check ovn-nbctl mirror-rule-add mirror1 150 'icmp' skip >>> + >>> +check ovn-nbctl mirror-add mirror2 lport from-lport sw1-target1 >>> +check ovn-nbctl mirror-rule-add mirror2 100 '1' mirror >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> + >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> +]) >>> + >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror0 >>> +ovn-nbctl --wait=sb sync >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-target0], [0], [dnl >>> +logical_port : mp-sw0-target0 >>> +]) >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep is-mirror], [0], [dnl >>> +options : {is-mirror="true"} >>> +]) >>> + >>> +check_column sw0-target0 sb:port_binding parent_port >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> +]) >>> + >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1 >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1" && ip), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=150 , match=(outport == >>> "sw0-p1" && icmp), action=(next;) >>> +]) >>> + >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror2 >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw0-target0], [0], [dnl >>> +logical_port : mp-sw0-target0 >>> +]) >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw1-target1], [0], [dnl >>> +logical_port : mp-sw1-target1 >>> +]) >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep is-mirror], [0], [dnl >>> +options : {is-mirror="true"} >>> +options : {is-mirror="true"} >>> +]) >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1" && ip), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=150 , match=(outport == >>> "sw0-p1" && icmp), action=(next;) >>> +]) >>> + >>> +ovn-nbctl lsp-del sw0-target0 >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> +]) >>> + >>> +ovn-nbctl lsp-add sw0 sw0-target0 >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> + >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1" && 1), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1"), action=(clone {outport = "mp-sw1-target1"; output;}; next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1" && ip), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=100 , match=(outport == >>> "sw0-p1"), action=(clone {outport = "mp-sw0-target0"; >>> next(pipeline=ingress, table=??);}; next;) >>> + table=??(ls_out_mirror ), priority=150 , match=(outport == >>> "sw0-p1" && icmp), action=(next;) >>> +]) >>> + >>> + >>> +ovn-nbctl mirror-del >>> +ovn-nbctl --wait=sb sync >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" -e "ls_out_mirror" lflow-list | >>> ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_out_mirror ), priority=0 , match=(1), action=(next;) >>> +]) >>> + >>> +AT_CLEANUP >>> +]) >>> + >>> +OVN_FOR_EACH_NORTHD_NO_HV([ >>> +AT_SETUP([ovn-detrace mirror rule check]) >>> +AT_KEYWORDS([mirror rules]) >>> +ovn_start >>> + >>> +check ovn-nbctl ls-add sw0 >>> +check ovn-nbctl lsp-add sw0 sw0-p1 >>> +check ovn-nbctl lsp-add sw0 sw0-rp >>> + >>> +check ovn-nbctl lsp-set-type sw0-rp router >>> +check ovn-nbctl lsp-set-addresses sw0-rp router >>> +check ovn-nbctl lsp-set-options sw0-rp router-port=lrp1 >>> + >>> +check ovn-nbctl lsp-set-addresses sw0-p1 '00:00:00:00:00:01 1.1.1.1' >>> + >>> +check ovn-nbctl ls-add sw1 >>> +check ovn-nbctl lsp-add sw1 sw1-target >>> +check ovn-nbctl lsp-add sw0 sw0-target >>> + >>> +check ovn-nbctl lr-add lr1 >>> +check ovn-nbctl lrp-add lr1 lrp1 00:00:00:00:00:02 1.1.1.2/24 >>> + >>> +check ovn-nbctl mirror-add mirror1 lport from-lport sw1-target >>> +check ovn-nbctl mirror-rule-add mirror1 100 '1' mirror >>> + >>> +check ovn-nbctl lsp-attach-mirror sw0-p1 mirror1 >>> + >>> +AT_CHECK([ovn-sbctl list port_binding | grep mp-sw1-target ], [0], [dnl >>> +logical_port : mp-sw1-target >>> +]) >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1" && 1), action=(clone {outport = "mp-sw1-target"; output;}; next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1"), action=(clone {outport = "mp-sw1-target"; output;}; next;) >>> +]) >>> + >>> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == >>> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && >>> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) >>> + >>> +AT_CHECK([cat trace | grep "output"], [0], [dnl >>> + output("mp-sw1-target"); >>> + output("sw0-p1"); >>> + output("sw0-p1"); >>> +]) >>> + >>> +ovn-nbctl mirror-rule-del mirror1 >>> + >>> +ovn-sbctl lflow-list sw0 > lflow-list >>> +AT_CHECK([grep -e "ls_in_mirror" lflow-list | ovn_strip_lflows], [0], [dnl >>> + table=??(ls_in_mirror ), priority=0 , match=(1), action=(next;) >>> + table=??(ls_in_mirror ), priority=100 , match=(inport == >>> "sw0-p1"), action=(clone {outport = "mp-sw1-target"; output;}; next;) >>> +]) >>> + >>> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == >>> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && >>> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) >>> + >>> +AT_CHECK([cat trace | grep "output"], [0], [dnl >>> + output("mp-sw1-target"); >>> + output("sw0-p1"); >>> + output("sw0-p1"); >>> +]) >>> + >>> +ovn-nbctl mirror-del >>> + >>> +AT_CHECK([ovn_trace --minimal 'inport == "sw0-p1" && eth.src == >>> 00:00:00:00:00:01 && eth.dst == 00:00:00:00:00:02 && ip4.dst == 1.1.1.2 && >>> ip4.src == 1.1.1.1 && ip.ttl == 64' > trace], [0], [ignore]) >>> + >>> +AT_CHECK([cat trace | grep "output"], [0], [dnl >>> + output("sw0-p1"); >>> + output("sw0-p1"); >>> +]) >>> + >>> +AT_CLEANUP >>> +]) >>> diff --git a/utilities/ovn-nbctl.c b/utilities/ovn-nbctl.c >>> index 131d9f0bf..b9add6500 100644 >>> --- a/utilities/ovn-nbctl.c >>> +++ b/utilities/ovn-nbctl.c >>> @@ -290,9 +290,10 @@ QoS commands:\n\ >>> qos-list SWITCH print QoS rules for SWITCH\n\ >>> \n\ >>> Mirror commands:\n\ >>> - mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID} \n\ >>> + mirror-add NAME TYPE [INDEX] FILTER {IP | MIRROR-ID| TARGET-PORT} \n\ >>> add a mirror with given name\n\ >>> - specify TYPE 'gre', 'erspan', or 'local'\n\ >>> + specify TYPE 'gre', 'erspan', 'local'\n\ >>> + or 'lport'.\n\ >>> specify the tunnel INDEX value\n\ >>> (indicates key if GRE\n\ >>> erpsan_idx if ERSPAN)\n\ >>> @@ -301,8 +302,16 @@ Mirror commands:\n\ >>> specify Sink / Destination i.e. Remote IP, or >>> a\n\ >>> local interface with >>> external-ids:mirror-id\n\ >>> matching MIRROR-ID\n\ >>> + In case of lport type specify logical >>> switch\n\ >>> + port, which is a mirror target.\n\ >>> mirror-del [NAME] remove mirrors\n\ >>> mirror-list print mirrors\n\ >>> + mirror-rule-add MIRROR-NAME PRIORITY MATCH ACTION \n\ >>> + add a mirror rule selection to given lport\n\ >>> + mirror.\n\ >>> + specify MATCH for selecting mirrored >>> traffic.\n\ >>> + specify ACTION 'mirror' or 'skip'.\n\ >>> + mirror-rule-del MIRROR-NAME [PRIORITY | MATCH] remove mirrors\n\ >>> \n\ >>> Meter commands:\n\ >>> [--fair]\n\ >>> @@ -1808,11 +1817,11 @@ nbctl_lsp_attach_mirror(struct ctl_context *ctx) >>> return; >>> } >>> >>> + bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; >>> /* Check if same mirror rule already exists for the lsp */ >>> for (size_t i = 0; i < lsp->n_mirror_rules; i++) { >>> if (uuid_equals(&lsp->mirror_rules[i]->header_.uuid, >>> &mirror->header_.uuid)) { >>> - bool may_exist = shash_find(&ctx->options, "--may-exist") != >>> NULL; >>> if (!may_exist) { >>> ctl_error(ctx, "mirror %s is already attached to the " >>> "logical port %s.", >>> @@ -7694,16 +7703,18 @@ static char * OVS_WARN_UNUSED_RESULT >>> parse_mirror_type(const char *arg, const char **type_p) >>> { >>> /* Validate type. Only require the first letter. */ >>> - if (arg[0] == 'g') { >>> + if (!strcmp(arg, "gre")) { >>> *type_p = "gre"; >>> - } else if (arg[0] == 'e') { >>> + } else if (!strcmp(arg, "erspan")) { >>> *type_p = "erspan"; >>> - } else if (arg[0] == 'l') { >>> + } else if (!strcmp(arg, "local")) { >>> *type_p = "local"; >>> + } else if (!strcmp(arg, "lport")) { >>> + *type_p = "lport"; >>> } else { >>> *type_p = NULL; >>> return xasprintf("%s: type must be \"gre\", " >>> - "\"erspan\", or \"local\"", arg); >>> + "\"erspan\", or \"local\" or \"lport\"", arg); >>> } >>> return NULL; >>> } >>> @@ -7716,6 +7727,8 @@ nbctl_pre_mirror_add(struct ctl_context *ctx) >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index); >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink); >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_logical_switch_port_col_name); >>> } >>> >>> static void >>> @@ -7740,14 +7753,17 @@ nbctl_mirror_add(struct ctl_context *ctx) >>> } >>> } >>> >>> - /* Type - gre/erspan/local */ >>> + /* Type - gre/erspan/local/lport */ >>> error = parse_mirror_type(ctx->argv[pos++], &type); >>> if (error) { >>> ctx->error = error; >>> return; >>> } >>> >>> - if (strcmp(type, "local")) { >>> + int is_local = !strcmp(type, "local"); >>> + int is_lport = !strcmp(type, "lport"); >>> + >>> + if (!is_local && !is_lport) { >>> /* tunnel index / GRE key / ERSPAN idx */ >>> if (!str_to_long(ctx->argv[pos++], 10, (long int *) &index)) { >>> ctl_error(ctx, "Invalid Index"); >>> @@ -7766,7 +7782,7 @@ nbctl_mirror_add(struct ctl_context *ctx) >>> sink = ctx->argv[pos++]; >>> >>> /* check if it is a valid ip unless it is type 'local' */ >>> - if (strcmp(type, "local")) { >>> + if (!is_local && !is_lport) { >>> char *new_sink_ip = normalize_ipv4_addr_str(sink); >>> if (!new_sink_ip) { >>> new_sink_ip = normalize_ipv6_addr_str(sink); >>> @@ -7779,6 +7795,21 @@ nbctl_mirror_add(struct ctl_context *ctx) >>> free(new_sink_ip); >>> } >>> >>> + /* Check if it is an existing port for lport mirror type. */ >>> + if (is_lport) { >>> + const struct nbrec_logical_switch_port *lsp; >>> + error = lsp_by_name_or_uuid(ctx, sink, false, &lsp); >>> + if (error) { >>> + ctx->error = error; >>> + return; >>> + } >>> + >>> + if (!lsp) { >>> + VLOG_WARN("Attaching target to unexisting port with name %s.", >>> + sink); >>> + } >>> + } >>> + >>> /* Create the mirror. */ >>> struct nbrec_mirror *mirror = nbrec_mirror_insert(ctx->txn); >>> nbrec_mirror_set_name(mirror, name); >>> @@ -7818,6 +7849,143 @@ nbctl_mirror_del(struct ctl_context *ctx) >>> } >>> } >>> >>> +static int >>> +rule_cmp(const void *mirror1_, const void *mirror2_) >>> +{ >>> + const struct nbrec_mirror_rule *const *mirror_1 = mirror1_; >>> + const struct nbrec_mirror_rule *const *mirror_2 = mirror2_; >>> + >>> + const struct nbrec_mirror_rule *mirror1 = *mirror_1; >>> + const struct nbrec_mirror_rule *mirror2 = *mirror_2; >>> + >>> + int result = mirror1->priority - mirror2->priority; >>> + if (result) { >>> + return result; >>> + } >>> + >>> + result = strcmp(mirror1->match, mirror2->match); >>> + if (result) { >>> + return result; >>> + } >>> + >>> + return strcmp(mirror1->action, mirror2->action); >>> +} >>> + >>> +static void >>> +nbctl_pre_mirror_rule_add(struct ctl_context *ctx) >>> +{ >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); >>> +} >>> + >>> +static void >>> +nbctl_mirror_rule_add(struct ctl_context *ctx) >>> +{ >>> + const struct nbrec_mirror_rule *mirror_rule; >>> + const struct nbrec_mirror *mirror; >>> + int64_t priority = 0; >>> + char *error; >>> + >>> + error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror); >>> + if (error) { >>> + ctx->error = error; >>> + return; >>> + } >>> + >>> + error = parse_priority(ctx->argv[2], &priority); >>> + if (error) { >>> + ctx->error = error; >>> + return; >>> + } >>> + >>> + const char *action = ctx->argv[4]; >>> + if (strcmp(action, "mirror") && strcmp(action, "skip")) { >>> + ctl_error(ctx, "%s: action must be one of \"mirror\", \"skip\"", >>> + action); >>> + } >>> + >>> + mirror_rule = nbrec_mirror_rule_insert(ctx->txn); >>> + nbrec_mirror_rule_set_match(mirror_rule, ctx->argv[3]); >>> + nbrec_mirror_rule_set_action(mirror_rule, action); >>> + nbrec_mirror_rule_set_priority(mirror_rule, priority); >>> + >>> + bool may_exist = shash_find(&ctx->options, "--may-exist") != NULL; >>> + /* Check if same mirror rule exists for this mirror. */ >>> + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { >>> + if (!rule_cmp(&mirror_rule, &mirror->mirror_rules[i])) { >>> + if (!may_exist) { >>> + ctl_error(ctx, "Same mirror-rule already exists on the " >>> + "mirror %s.", ctx->argv[1]); >>> + } else { >>> + nbrec_mirror_rule_delete(mirror_rule); >>> + } >>> + return; >>> + } >>> + } >>> + >>> + /* Insert mirror rule to mirror. */ >>> + nbrec_mirror_update_mirror_rules_addvalue(mirror, mirror_rule); >>> +} >>> + >>> +static void >>> +nbctl_pre_mirror_rule_del(struct ctl_context *ctx) >>> +{ >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_name); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); >>> +} >>> + >>> +static void >>> +nbctl_mirror_rule_del(struct ctl_context *ctx) >>> +{ >>> + const struct nbrec_mirror *mirror = NULL; >>> + int64_t priority = 0; >>> + char *error = NULL; >>> + >>> + error = mirror_by_name_or_uuid(ctx, ctx->argv[1], true, &mirror); >>> + if (error) { >>> + ctx->error = error; >>> + return; >>> + } >>> + >>> + if (ctx->argc == 2) { >>> + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { >>> + nbrec_mirror_update_mirror_rules_delvalue(mirror, >>> + mirror->mirror_rules[i]); >>> + } >>> + return; >>> + } >>> + >>> + error = parse_priority(ctx->argv[2], &priority); >>> + if (error) { >>> + ctx->error = error; >>> + return; >>> + } >>> + >>> + if (ctx->argc == 3) { >>> + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { >>> + struct nbrec_mirror_rule *rule = mirror->mirror_rules[i]; >>> + if (priority == rule->priority) { >>> + nbrec_mirror_update_mirror_rules_delvalue(mirror, rule); >>> + return; >>> + } >>> + } >>> + } >>> + >>> + for (size_t i = 0; i < mirror->n_mirror_rules; i++) { >>> + struct nbrec_mirror_rule *rule = mirror->mirror_rules[i]; >>> + if (priority == rule->priority && !strcmp(ctx->argv[3], >>> + rule->match)) { >>> + nbrec_mirror_update_mirror_rules_delvalue(mirror, rule); >>> + } >>> + } >>> +} >>> + >>> static void >>> nbctl_pre_mirror_list(struct ctl_context *ctx) >>> { >>> @@ -7827,6 +7995,10 @@ nbctl_pre_mirror_list(struct ctl_context *ctx) >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_index); >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_sink); >>> ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_type); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_col_mirror_rules); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_action); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_match); >>> + ovsdb_idl_add_column(ctx->idl, &nbrec_mirror_rule_col_priority); >>> } >>> >>> static void >>> @@ -7853,15 +8025,27 @@ nbctl_mirror_list(struct ctl_context *ctx) >>> >>> for (size_t i = 0; i < n_mirrors; i++) { >>> mirror = mirrors[i]; >>> + bool is_lport = !strcmp(mirror->type, "lport"); >>> + bool is_local = !strcmp(mirror->type, "local"); >>> + >>> ds_put_format(&ctx->output, "%s:\n", mirror->name); >>> /* print all the values */ >>> ds_put_format(&ctx->output, " Type : %s\n", mirror->type); >>> ds_put_format(&ctx->output, " Sink : %s\n", mirror->sink); >>> ds_put_format(&ctx->output, " Filter : %s\n", mirror->filter); >>> - if (strcmp(mirror->type, "local")) { >>> + if (!is_local && !is_lport) { >>> ds_put_format(&ctx->output, " Index/Key: %"PRId64"\n", >>> mirror->index); >>> } >>> + if (mirror->n_mirror_rules) { >>> + ds_put_cstr(&ctx->output, " Rules :\n"); >>> + for (size_t j = 0; j < mirror->n_mirror_rules; j++) { >>> + ds_put_format(&ctx->output, " %5"PRId64" %30s >>> %15s\n", >>> + mirror->mirror_rules[j]->priority, >>> + mirror->mirror_rules[j]->match, >>> + mirror->mirror_rules[j]->action); >>> + } >>> + } >>> ds_put_cstr(&ctx->output, "\n"); >>> } >>> >>> @@ -7966,13 +8150,19 @@ static const struct ctl_command_syntax >>> nbctl_commands[] = { >>> >>> /* mirror commands. */ >>> { "mirror-add", 4, 5, >>> - "NAME TYPE INDEX FILTER IP", >>> + "NAME TYPE INDEX FILTER SINK", >>> nbctl_pre_mirror_add, nbctl_mirror_add, NULL, "--may-exist", RW }, >>> { "mirror-del", 0, 1, "[NAME]", >>> nbctl_pre_mirror_del, nbctl_mirror_del, NULL, "", RW }, >>> { "mirror-list", 0, 0, "", nbctl_pre_mirror_list, nbctl_mirror_list, >>> NULL, "", RO }, >>> >>> + /* Mirror rule commands. */ >>> + { "mirror-rule-add", 4, 4, "MIRROR-NAME PRIORITY MATCH ACTION", >>> + nbctl_pre_mirror_rule_add, nbctl_mirror_rule_add, NULL, "", RW}, >>> + { "mirror-rule-del", 1, 3, "MIRROR-NAME [PRIORITY MATCH]", >>> + nbctl_pre_mirror_rule_del, nbctl_mirror_rule_del, NULL, "", RW }, >>> + >>> /* meter commands. */ >>> { "meter-add", 4, 5, "NAME ACTION RATE UNIT [BURST]", >>> nbctl_pre_meter_add, >>> nbctl_meter_add, NULL, "--fair,--may-exist", RW }, >>> -- >>> 2.34.1 >>> >>> _______________________________________________ >>> dev mailing list >>> [email protected] >>> https://mail.openvswitch.org/mailman/listinfo/ovs-dev >>> >>> >> _______________________________________________ >> dev mailing list >> [email protected] >> https://mail.openvswitch.org/mailman/listinfo/ovs-dev > _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
