On Wed, Feb 4, 2026 at 12:08 PM Alexandra Rukomoinikova <[email protected]> wrote:
> This commit adds new 'ct_lb_mark_local' action to enable > per‑chassis load balancing, action filters backends by local > chassis bindings and builds OpenFlow groups containing only local backends. > This mechanism will later support fully distributed load balancers. > > Suggested-by: Vladislav Odintsov <[email protected]> > Signed-off-by: Alexandra Rukomoinikova <[email protected]> > Acked-by: Lorenzo Bianconi <[email protected]> > --- > v6 --> v7: fixed Ales comments > --- > Hi Alexandra, thank you for v7. I have some small nits down below. > controller/lflow.c | 17 +++++ > controller/lflow.h | 1 + > controller/ovn-controller.c | 1 + > include/ovn/actions.h | 11 +++ > lib/actions.c | 145 ++++++++++++++++++++++++++++++------ > lib/ovn-util.c | 2 +- > tests/ovn.at | 32 ++++++++ > tests/test-ovn.c | 11 +++ > utilities/ovn-trace.c | 14 +++- > 9 files changed, 207 insertions(+), 27 deletions(-) > > diff --git a/controller/lflow.c b/controller/lflow.c > index 915b24269..2bc389f08 100644 > --- a/controller/lflow.c > +++ b/controller/lflow.c > @@ -64,6 +64,7 @@ struct lookup_port_aux { > const struct sbrec_logical_flow *lflow; > struct objdep_mgr *deps_mgr; > const struct hmap *chassis_tunnels; > + const struct shash *local_bindings; > }; > > struct condition_aux { > @@ -153,6 +154,20 @@ lookup_port_cb(const void *aux_, const char > *port_name, unsigned int *portp) > return false; > } > > +/* Given the OVN port name, get true if port locates on local chassis, > + * false otherwise. */ > +static bool > +lookup_local_port_cb(const void *aux_, const char *port_name) > +{ > + const struct lookup_port_aux *aux = aux_; > + > + if (local_binding_get_primary_pb(aux->local_bindings, port_name)) { > + return true; > + } > + > + return false; > +} > + > /* Given the OVN port name, get its openflow port */ > static bool > tunnel_ofport_cb(const void *aux_, const char *port_name, ofp_port_t > *ofport) > @@ -857,6 +872,7 @@ add_matches_to_flow_table(const struct > sbrec_logical_flow *lflow, > .lflow = lflow, > .deps_mgr = l_ctx_out->lflow_deps_mgr, > .chassis_tunnels = l_ctx_in->chassis_tunnels, > + .local_bindings = l_ctx_in->lbinding_lports, > }; > > /* Parse any meter to be used if this flow should punt packets to > @@ -871,6 +887,7 @@ add_matches_to_flow_table(const struct > sbrec_logical_flow *lflow, > struct ofpbuf ofpacts = OFPBUF_STUB_INITIALIZER(ofpacts_stub); > struct ovnact_encode_params ep = { > .lookup_port = lookup_port_cb, > + .lookup_local_port = lookup_local_port_cb, > .tunnel_ofport = tunnel_ofport_cb, > .aux = &aux, > .is_switch = ldp->is_switch, > diff --git a/controller/lflow.h b/controller/lflow.h > index 30c6dbebd..cd714ffef 100644 > --- a/controller/lflow.h > +++ b/controller/lflow.h > @@ -138,6 +138,7 @@ struct lflow_ctx_in { > const struct smap *template_vars; > const struct flow_collector_ids *collector_ids; > const struct hmap *local_lbs; > + const struct shash *lbinding_lports; > bool localnet_learn_fdb; > bool localnet_learn_fdb_changed; > bool explicit_arp_ns_output; > diff --git a/controller/ovn-controller.c b/controller/ovn-controller.c > index 2d9b3e033..cd3ef616c 100644 > --- a/controller/ovn-controller.c > +++ b/controller/ovn-controller.c > @@ -3898,6 +3898,7 @@ init_lflow_ctx(struct engine_node *node, > l_ctx_in->template_vars = &template_vars->local_templates; > l_ctx_in->collector_ids = &fo->collector_ids; > l_ctx_in->local_lbs = &lb_data->local_lbs; > + l_ctx_in->lbinding_lports = &rt_data->lbinding_data.bindings; > > l_ctx_out->flow_table = &fo->flow_table; > l_ctx_out->group_table = &fo->group_table; > diff --git a/include/ovn/actions.h b/include/ovn/actions.h > index d3f0bfd04..2ca8dac8f 100644 > --- a/include/ovn/actions.h > +++ b/include/ovn/actions.h > @@ -75,6 +75,7 @@ struct collector_set_ids; > OVNACT(CT_SNAT_IN_CZONE, ovnact_ct_nat) \ > OVNACT(CT_LB, ovnact_ct_lb) \ > OVNACT(CT_LB_MARK, ovnact_ct_lb) \ > + OVNACT(CT_LB_MARK_LOCAL, ovnact_ct_lb) \ > OVNACT(SELECT, ovnact_select) \ > OVNACT(CT_CLEAR, ovnact_null) \ > OVNACT(CT_COMMIT_NAT, ovnact_ct_commit_to_zone) \ > @@ -311,6 +312,12 @@ struct ovnact_ct_commit_to_zone { > uint8_t ltable; > }; > > +enum ovnact_ct_lb_type { > + OVNACT_CT_LB_TYPE_LABEL, > + OVNACT_CT_LB_TYPE_MARK, > + OVNACT_CT_LB_LOCAL_TYPE_MARK, > +}; > + > enum ovnact_ct_lb_flag { > OVNACT_CT_LB_FLAG_NONE, > OVNACT_CT_LB_FLAG_SKIP_SNAT, > @@ -324,6 +331,7 @@ struct ovnact_ct_lb_dst { > ovs_be32 ipv4; > }; > uint16_t port; > + char *port_name; > }; > > /* OVNACT_CT_LB/OVNACT_CT_LB_MARK. */ > @@ -891,6 +899,9 @@ struct ovnact_encode_params { > bool (*lookup_port)(const void *aux, const char *port_name, > unsigned int *portp); > > + /* Check if the logical port is bound to this chassis. */ > + bool (*lookup_local_port)(const void *aux, const char *port_name); > + > /* Looks up tunnel port to a chassis by its port name. If found, > stores > * its openflow port number in '*ofport' and returns true; > * otherwise, returns false. */ > diff --git a/lib/actions.c b/lib/actions.c > index a2e389701..ecef81493 100644 > --- a/lib/actions.c > +++ b/lib/actions.c > @@ -1187,8 +1187,25 @@ ovnact_ct_commit_to_zone_free(struct > ovnact_ct_commit_to_zone *cn OVS_UNUSED) > { > } > > + > +static bool > +parse_ct_lb_logical_port_name(struct action_context *ctx, > + struct ovnact_ct_lb_dst *dst) > +{ > + if (ctx->lexer->token.type != LEX_T_STRING) { > + return false; > + } > + > + dst->port_name = xstrdup(ctx->lexer->token.s); > + > + lexer_get(ctx->lexer); > + > + return true; > +} > + > static void > -parse_ct_lb_action(struct action_context *ctx, bool ct_lb_mark) > +parse_ct_lb_action(struct action_context *ctx, > + enum ovnact_ct_lb_type type) > { > if (ctx->pp->cur_ltable >= ctx->pp->n_tables) { > lexer_error(ctx->lexer, "\"ct_lb\" action not allowed in last > table."); > @@ -1211,7 +1228,19 @@ parse_ct_lb_action(struct action_context *ctx, bool > ct_lb_mark) > > while (!lexer_match(ctx->lexer, LEX_T_SEMICOLON) && > !lexer_match(ctx->lexer, LEX_T_RPAREN)) { > - struct ovnact_ct_lb_dst dst; > + struct ovnact_ct_lb_dst dst = {0}; > + > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK) { > + if (!parse_ct_lb_logical_port_name(ctx, &dst)) { > + vector_destroy(&dsts); > + lexer_syntax_error(ctx->lexer, > + "expecting logical port name " > + "for distributed load balancer"); > + return; > + } > + lexer_match(ctx->lexer, LEX_T_COLON); > nit: Should be lexer_force_match(...). > + } > + > if (lexer_match(ctx->lexer, LEX_T_LSQUARE)) { > /* IPv6 address and port */ > if (ctx->lexer->token.type != LEX_T_INTEGER > @@ -1298,8 +1327,21 @@ parse_ct_lb_action(struct action_context *ctx, bool > ct_lb_mark) > } > } > > - struct ovnact_ct_lb *cl = ct_lb_mark ? > ovnact_put_CT_LB_MARK(ctx->ovnacts) > - : ovnact_put_CT_LB(ctx->ovnacts); > + struct ovnact_ct_lb *cl; > + switch (type) { > + case OVNACT_CT_LB_TYPE_LABEL: > + cl = ovnact_put_CT_LB(ctx->ovnacts); > + break; > + case OVNACT_CT_LB_TYPE_MARK: > + cl = ovnact_put_CT_LB_MARK(ctx->ovnacts); > + break; > + case OVNACT_CT_LB_LOCAL_TYPE_MARK: > + cl = ovnact_put_CT_LB_MARK_LOCAL(ctx->ovnacts); > + break; > + default: > + OVS_NOT_REACHED(); > + } > + > cl->ltable = ctx->pp->cur_ltable + 1; > cl->n_dsts = vector_len(&dsts); > cl->dsts = vector_steal_array(&dsts); > @@ -1308,13 +1350,16 @@ parse_ct_lb_action(struct action_context *ctx, > bool ct_lb_mark) > } > > static void > -format_ct_lb(const struct ovnact_ct_lb *cl, struct ds *s, bool ct_lb_mark) > +format_ct_lb(const struct ovnact_ct_lb *cl, struct ds *s, > + enum ovnact_ct_lb_type type) > { > - if (ct_lb_mark) { > - ds_put_cstr(s, "ct_lb_mark"); > - } else { > - ds_put_cstr(s, "ct_lb"); > - } > + static const char *const lb_action_strings[] = { > + [OVNACT_CT_LB_TYPE_LABEL] = "ct_lb", > + [OVNACT_CT_LB_TYPE_MARK] = "ct_lb_mark", > + [OVNACT_CT_LB_LOCAL_TYPE_MARK] = "ct_lb_mark_local", > + }; > + ds_put_cstr(s, lb_action_strings[type]); > + > if (cl->n_dsts) { > ds_put_cstr(s, "(backends="); > for (size_t i = 0; i < cl->n_dsts; i++) { > @@ -1323,6 +1368,9 @@ format_ct_lb(const struct ovnact_ct_lb *cl, struct > ds *s, bool ct_lb_mark) > } > > const struct ovnact_ct_lb_dst *dst = &cl->dsts[i]; > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK) { > + ds_put_format(s, "\"%s\":", dst->port_name); > + } > if (dst->family == AF_INET) { > ds_put_format(s, IP_FMT, IP_ARGS(dst->ipv4)); > if (dst->port) { > @@ -1363,20 +1411,26 @@ format_ct_lb(const struct ovnact_ct_lb *cl, struct > ds *s, bool ct_lb_mark) > static void > format_CT_LB(const struct ovnact_ct_lb *cl, struct ds *s) > { > - format_ct_lb(cl, s, false); > + format_ct_lb(cl, s, OVNACT_CT_LB_TYPE_LABEL); > } > > static void > format_CT_LB_MARK(const struct ovnact_ct_lb *cl, struct ds *s) > { > - format_ct_lb(cl, s, true); > + format_ct_lb(cl, s, OVNACT_CT_LB_TYPE_MARK); > +} > + > +static void > +format_CT_LB_MARK_LOCAL(const struct ovnact_ct_lb *cl, struct ds *s) > +{ > + format_ct_lb(cl, s, OVNACT_CT_LB_LOCAL_TYPE_MARK); > } > > static void > encode_ct_lb(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts, > - bool ct_lb_mark) > + enum ovnact_ct_lb_type type) > { > uint8_t recirc_table = cl->ltable + first_ptable(ep, ep->pipeline); > if (!cl->n_dsts) { > @@ -1408,7 +1462,8 @@ encode_ct_lb(const struct ovnact_ct_lb *cl, > struct ofpact_group *og; > uint32_t zone_reg = ep->is_switch ? MFF_LOG_CT_ZONE - MFF_REG0 > : MFF_LOG_DNAT_ZONE - MFF_REG0; > - const char *flag_reg = ct_lb_mark ? "ct_mark" : "ct_label"; > + const char *flag_reg = (type == OVNACT_CT_LB_TYPE_LABEL) > + ? "ct_label" : "ct_mark"; > > const char *ct_flag_value; > switch (cl->ct_flag) { > @@ -1435,32 +1490,55 @@ encode_ct_lb(const struct ovnact_ct_lb *cl, > BUILD_ASSERT(MFF_LOG_CT_ZONE < MFF_REG0 + FLOW_N_REGS); > BUILD_ASSERT(MFF_LOG_DNAT_ZONE >= MFF_REG0); > BUILD_ASSERT(MFF_LOG_DNAT_ZONE < MFF_REG0 + FLOW_N_REGS); > + > + size_t n_active_backends = 0; > for (size_t bucket_id = 0; bucket_id < cl->n_dsts; bucket_id++) { > const struct ovnact_ct_lb_dst *dst = &cl->dsts[bucket_id]; > char ip_addr[INET6_ADDRSTRLEN]; > + > + if (type == OVNACT_CT_LB_LOCAL_TYPE_MARK > + && !ep->lookup_local_port(ep->aux, dst->port_name)) { > + continue; > + } > + > if (dst->family == AF_INET) { > inet_ntop(AF_INET, &dst->ipv4, ip_addr, sizeof ip_addr); > } else { > inet_ntop(AF_INET6, &dst->ipv6, ip_addr, sizeof ip_addr); > } > - ds_put_format(&ds, > ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions=" > - "ct(nat(dst=%s%s%s", bucket_id, > - dst->family == AF_INET6 && dst->port ? "[" : "", > - ip_addr, > - dst->family == AF_INET6 && dst->port ? "]" : ""); > + > + ds_put_format(&ds, > ",bucket=bucket_id=%"PRIuSIZE",weight:100,actions=", > + bucket_id); > + > + bool is_ipv6_address = (dst->family == AF_INET6 && dst->port); > + ds_put_format(&ds, "ct(nat(dst="); > + if (is_ipv6_address) { > + ds_put_format(&ds, "[%s]", ip_addr); > + } else { > + ds_put_format(&ds, "%s", ip_addr); > + } > + > if (dst->port) { > ds_put_format(&ds, ":%"PRIu16, dst->port); > } > + > nit: Unrelated change. > ds_put_format(&ds, "),commit,table=%d,zone=NXM_NX_REG%d[0..15]," > "exec(set_field:" > OVN_CT_MASKED_STR(OVN_CT_NATTED) > "->%s", > recirc_table, zone_reg, flag_reg); > + > nit: Unrelated change. > if (ct_flag_value) { > ds_put_format(&ds, ",set_field:%s->%s", ct_flag_value, > flag_reg); > } > > ds_put_cstr(&ds, "))"); > + > + n_active_backends++; > + } > + > + if (!n_active_backends) { > + return; > } > > table_id = ovn_extend_table_assign_id(ep->group_table, ds_cstr(&ds), > @@ -1480,7 +1558,7 @@ encode_CT_LB(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts) > { > - encode_ct_lb(cl, ep, ofpacts, false); > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_TYPE_LABEL); > } > > static void > @@ -1488,13 +1566,30 @@ encode_CT_LB_MARK(const struct ovnact_ct_lb *cl, > const struct ovnact_encode_params *ep, > struct ofpbuf *ofpacts) > { > - encode_ct_lb(cl, ep, ofpacts, true); > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_TYPE_MARK); > } > > static void > -ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb) > +encode_CT_LB_MARK_LOCAL(const struct ovnact_ct_lb *cl, > + const struct ovnact_encode_params *ep, > + struct ofpbuf *ofpacts) > +{ > + encode_ct_lb(cl, ep, ofpacts, OVNACT_CT_LB_LOCAL_TYPE_MARK); > +} > + > +static void > +ovnact_ct_lb_free_dsts(struct ovnact_ct_lb *ct_lb) > { > + for (size_t i = 0; i < ct_lb->n_dsts; i++) { > + free(ct_lb->dsts[i].port_name); > + } > free(ct_lb->dsts); > +} > + > +static void > +ovnact_ct_lb_free(struct ovnact_ct_lb *ct_lb) > +{ > + ovnact_ct_lb_free_dsts(ct_lb); > free(ct_lb->hash_fields); > } > > @@ -5905,9 +6000,11 @@ parse_action(struct action_context *ctx) > } else if (lexer_match_id(ctx->lexer, "ct_lb")) { > VLOG_WARN_RL(&rl, "The \"ct_lb\" action is deprecated please " > "consider using a different action."); > - parse_ct_lb_action(ctx, false); > + parse_ct_lb_action(ctx, OVNACT_CT_LB_TYPE_LABEL); > } else if (lexer_match_id(ctx->lexer, "ct_lb_mark")) { > - parse_ct_lb_action(ctx, true); > + parse_ct_lb_action(ctx, OVNACT_CT_LB_TYPE_MARK); > + } else if (lexer_match_id(ctx->lexer, "ct_lb_mark_local")) { > + parse_ct_lb_action(ctx, OVNACT_CT_LB_LOCAL_TYPE_MARK); > } else if (lexer_match_id(ctx->lexer, "ct_clear")) { > ovnact_put_CT_CLEAR(ctx->ovnacts); > } else if (lexer_match_id(ctx->lexer, "ct_commit_nat")) { > diff --git a/lib/ovn-util.c b/lib/ovn-util.c > index 71098d478..4ee500590 100644 > --- a/lib/ovn-util.c > +++ b/lib/ovn-util.c > @@ -967,7 +967,7 @@ ip_address_and_port_from_lb_key(const char *key, char > **ip_address, > * > * NOTE: If OVN_NORTHD_PIPELINE_CSUM is updated make sure to double check > * whether an update of OVN_INTERNAL_MINOR_VER is required. */ > -#define OVN_NORTHD_PIPELINE_CSUM "2164662054 10958" > +#define OVN_NORTHD_PIPELINE_CSUM "4239189964 11014" > #define OVN_INTERNAL_MINOR_VER 11 > > /* Returns the OVN version. The caller must free the returned value. */ > diff --git a/tests/ovn.at b/tests/ovn.at > index 84d37dfdf..3dd09ef6c 100644 > --- a/tests/ovn.at > +++ b/tests/ovn.at > @@ -2375,6 +2375,38 @@ mirror("lsp1"); > mirror(lsp1); > Syntax error at `lsp1' expecting port name string. > > +ct_lb_mark_local(backends="lsp1":192.168.0.101); > + encodes as group:25 > + uses group: id(25), > name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=192.168.0.101),commit,table=19,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark))) > + has prereqs ip > + > +ct_lb_mark_local(backends="lsp1":192.168.0.101:10880); > + encodes as group:26 > + uses group: id(26), > name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst= > 192.168.0.101:10880 > ),commit,table=oflow_in_table,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark))) > + has prereqs ip > + > +ct_lb_mark_local(backends="lsp1":192.168.0.101:10880; > hash_fields="eth_src,eth_dst,ip_src,ip_dst,sctp_src,sctp_dst"); > + encodes as group:27 > + uses group: id(27), > name(type=select,selection_method=hash,fields(eth_src,eth_dst,ip_src,ip_dst,sctp_src,sctp_dst),bucket=bucket_id=0,weight:100,actions=ct(nat(dst= > 192.168.0.101:10880 > ),commit,table=oflow_in_table,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark))) > + has prereqs ip > + > +ct_lb_mark_local(backends=192.168.0.101:80); > + Syntax error at `192.168.0.101' expecting logical port name for > distributed load balancer. > + > +ct_lb_mark_local(backends=lsp1:192.168.0.101:10880); > + Syntax error at `lsp1' expecting logical port name for distributed > load balancer. > + > +# add hash fields parsing > +ct_lb_mark_local(backends="lsp1":fd0f::2); > + encodes as group:28 > + uses group: id(28), > name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=fd0f::2),commit,table=19,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark))) > + has prereqs ip > + > +ct_lb_mark_local(backends="lsp1":[[fd0f::2]]:80); > + encodes as group:29 > + uses group: id(29), > name(type=select,selection_method=dp_hash,bucket=bucket_id=0,weight:100,actions=ct(nat(dst=[[fd0f::2]]:80),commit,table=19,zone=NXM_NX_REG13[[0..15]],exec(set_field:2/2->ct_mark))) > + has prereqs ip > + > # Miscellaneous negative tests. > ; > Syntax error at `;'. > diff --git a/tests/test-ovn.c b/tests/test-ovn.c > index fae7e7bd5..1132714f6 100644 > --- a/tests/test-ovn.c > +++ b/tests/test-ovn.c > @@ -265,6 +265,16 @@ lookup_port_cb(const void *ports_, const char > *port_name, unsigned int *portp) > return true; > } > > +static bool > +lookup_local_port_cb(const void *ports_, const char *port_name) > +{ > + const struct simap *ports = ports_; > + const struct simap_node *node = simap_find(ports, port_name); > + > + > + return node ? true : false; > nit: We could return !!simap_find(...). > +} > + > static bool > lookup_tunnel_ofport(const void *ports_, const char *port_name, > ofp_port_t *ofport) > @@ -1361,6 +1371,7 @@ test_parse_actions(struct ovs_cmdl_context *ctx > OVS_UNUSED) > /* Encode the actions into OpenFlow and print. */ > const struct ovnact_encode_params ep = { > .lookup_port = lookup_port_cb, > + .lookup_local_port = lookup_local_port_cb, > .tunnel_ofport = lookup_tunnel_ofport, > .aux = &ports, > .is_switch = true, > diff --git a/utilities/ovn-trace.c b/utilities/ovn-trace.c > index 3e87acc6a..8708a50c3 100644 > --- a/utilities/ovn-trace.c > +++ b/utilities/ovn-trace.c > @@ -2675,9 +2675,13 @@ execute_ct_lb(const struct ovnact_ct_lb *ct_lb, > ct_lb_flow.ct_state |= next_ct_state(&comment); > } > > + const char *action_type = > + ct_lb->ovnact.type == OVNACT_CT_LB_MARK ? "ct_lb_mark" : > + ct_lb->ovnact.type == OVNACT_CT_LB_MARK_LOCAL ? > "ct_lb_mark_local" : > + "ct_lb"; > + struct ovntrace_node *node = ovntrace_node_append( > - super, OVNTRACE_NODE_TRANSFORMATION, "%s%s", > - ct_lb->ovnact.type == OVNACT_CT_LB_MARK ? "ct_lb_mark" : "ct_lb", > + super, OVNTRACE_NODE_TRANSFORMATION, "%s%s", action_type, > ds_cstr_ro(&comment)); > ds_destroy(&comment); > > @@ -3334,6 +3338,12 @@ trace_actions(const struct ovnact *ovnacts, size_t > ovnacts_len, > super); > break; > > + case OVNACT_CT_LB_MARK_LOCAL: > + /* Treat all ports as local. */ > + execute_ct_lb(ovnact_get_CT_LB_MARK_LOCAL(a), dp, uflow, > + pipeline, super); > + break; > + > case OVNACT_SELECT: > execute_select(ovnact_get_SELECT(a), dp, uflow, > pipeline, super); > -- > 2.48.1 > > _______________________________________________ > dev mailing list > [email protected] > https://mail.openvswitch.org/mailman/listinfo/ovs-dev I took care of the nits and merged this into main. Regards, Ales _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
