Skip to content

Commit

Permalink
treewide: Introduce OVN overlay port mirroring support.
Browse files Browse the repository at this point in the history
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 <arukomoinikova@k2.cloud>
Signed-off-by: Vladislav Odintsov <vlodintsov@k2.cloud>
Co-authored-by: Vladislav Odintsov <vlodintsov@k2.cloud>
Tested-by: Ivan Burnin <iburnin@k2.cloud>
Signed-off-by: 0-day Robot <robot@bytheb.org>
  • Loading branch information
2 people authored and ovsrobot committed Dec 28, 2024
1 parent 072b8ec commit 417ab62
Show file tree
Hide file tree
Showing 18 changed files with 997 additions and 101 deletions.
12 changes: 6 additions & 6 deletions controller/lflow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions controller/mirror.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
7 changes: 5 additions & 2 deletions controller/physical.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 6 additions & 0 deletions lib/ovn-util.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
3 changes: 2 additions & 1 deletion lib/ovn-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions northd/en-northd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
2 changes: 2 additions & 0 deletions northd/inc-proc-northd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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") \
Expand Down Expand Up @@ -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);

Expand Down
223 changes: 223 additions & 0 deletions northd/northd.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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.
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
}

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand Down
Loading

0 comments on commit 417ab62

Please sign in to comment.