On Thu, Feb 20, 2025 at 02:46:28PM +0100, Dumitru Ceara wrote:
> On 2/18/25 3:02 PM, Felix Huettner via dev wrote:
> > Add support for I+P handling of en-learned-route-sync for the
> > sb_learned_routes input.
> >
> > Signed-off-by: Felix Huettner <[email protected]>
> > ---
>
> Hi Felix,
>
> Overall the patch looks good to me. I do have some small comments below.
Hi Dumitru,
thanks a lot for the review.
>
> > v1-v2: Fix test error
> >
> > northd/en-learned-route-sync.c | 123 ++++++++++++++++++++++++++++++---
> > northd/en-learned-route-sync.h | 25 +++++++
> > northd/inc-proc-northd.c | 6 +-
> > northd/northd.c | 5 +-
> > northd/northd.h | 31 +++++----
> > tests/ovn-northd.at | 16 ++---
> > 6 files changed, 170 insertions(+), 36 deletions(-)
> >
> > diff --git a/northd/en-learned-route-sync.c b/northd/en-learned-route-sync.c
> > index 4e87b3265..312141208 100644
> > --- a/northd/en-learned-route-sync.c
> > +++ b/northd/en-learned-route-sync.c
> > @@ -60,6 +60,19 @@ learned_route_sync_northd_change_handler(struct
> > engine_node *node,
> > return true;
> > }
> >
> > +static void
> > +routes_sync_clear_tracked(struct learned_route_sync_data *data)
> > +{
> > + hmapx_clear(&data->trk_data.trk_created_parsed_route);
> > + struct hmapx_node *hmapx_node;
> > + HMAPX_FOR_EACH_SAFE (hmapx_node,
> > + &data->trk_data.trk_deleted_parsed_route) {
> > + parsed_route_free(hmapx_node->data);
> > + hmapx_delete(&data->trk_data.trk_deleted_parsed_route, hmapx_node);
> > + }
> > + data->trk_data.type = LEARNED_ROUTES_TRACKED_NONE;
> > +}
> > +
> > static void
> > routes_sync_clear(struct learned_route_sync_data *data)
> > {
> > @@ -67,12 +80,16 @@ routes_sync_clear(struct learned_route_sync_data *data)
> > HMAP_FOR_EACH_POP (r, key_node, &data->parsed_routes) {
> > parsed_route_free(r);
> > }
> > + routes_sync_clear_tracked(data);
> > }
> >
> > static void
> > routes_sync_init(struct learned_route_sync_data *data)
> > {
> > hmap_init(&data->parsed_routes);
> > + hmapx_init(&data->trk_data.trk_created_parsed_route);
> > + hmapx_init(&data->trk_data.trk_deleted_parsed_route);
> > + data->trk_data.type = LEARNED_ROUTES_TRACKED_NONE;
> > }
> >
> > static void
> > @@ -80,6 +97,8 @@ routes_sync_destroy(struct learned_route_sync_data *data)
> > {
> > routes_sync_clear(data);
> > hmap_destroy(&data->parsed_routes);
> > + hmapx_destroy(&data->trk_data.trk_created_parsed_route);
> > + hmapx_destroy(&data->trk_data.trk_deleted_parsed_route);
> > }
> >
> > void *
> > @@ -97,6 +116,12 @@ en_learned_route_sync_cleanup(void *data)
> > routes_sync_destroy(data);
> > }
> >
> > +void
> > +en_learned_route_sync_clear_tracked_data(void *data)
> > +{
> > + routes_sync_clear_tracked(data);
> > +}
> > +
> > void
> > en_learned_route_sync_run(struct engine_node *node, void *data)
> > {
> > @@ -122,7 +147,7 @@ en_learned_route_sync_run(struct engine_node *node,
> > void *data)
> > }
> >
> >
> > -static void
> > +static struct parsed_route *
> > parse_route_from_sbrec_route(struct hmap *parsed_routes_out,
> > const struct hmap *lr_ports,
> > const struct hmap *lr_datapaths,
> > @@ -132,7 +157,7 @@ parse_route_from_sbrec_route(struct hmap
> > *parsed_routes_out,
> > NULL, lr_datapaths, route->datapath);
> >
> > if (!od || ovn_datapath_is_stale(od)) {
> > - return;
> > + return NULL;
> > }
> >
> > /* Verify that the next hop is an IP address with an all-ones mask. */
> > @@ -144,7 +169,7 @@ parse_route_from_sbrec_route(struct hmap
> > *parsed_routes_out,
> > UUID_FMT, route->nexthop,
> > UUID_ARGS(&route->header_.uuid));
> > free(nexthop);
> > - return;
> > + return NULL;
> > }
> > if ((IN6_IS_ADDR_V4MAPPED(nexthop) && plen != 32) ||
> > (!IN6_IS_ADDR_V4MAPPED(nexthop) && plen != 128)) {
> > @@ -153,7 +178,7 @@ parse_route_from_sbrec_route(struct hmap
> > *parsed_routes_out,
> > UUID_FMT, route->nexthop,
> > UUID_ARGS(&route->header_.uuid));
> > free(nexthop);
> > - return;
> > + return NULL;
> > }
> >
> > /* Parse ip_prefix */
> > @@ -164,7 +189,7 @@ parse_route_from_sbrec_route(struct hmap
> > *parsed_routes_out,
> > UUID_FMT, route->ip_prefix,
> > UUID_ARGS(&route->header_.uuid));
> > free(nexthop);
> > - return;
> > + return NULL;
> > }
> >
> > /* Verify that ip_prefix and nexthop are on the same network. */
> > @@ -175,14 +200,18 @@ parse_route_from_sbrec_route(struct hmap
> > *parsed_routes_out,
> > IN6_IS_ADDR_V4MAPPED(&prefix),
> > false,
> > &out_port, &lrp_addr_s)) {
> > + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 1);
> > + VLOG_WARN_RL(&rl, "could not find output port %s for learned route
> > "
> > + UUID_FMT, route->logical_port->logical_port,
> > + UUID_ARGS(&route->header_.uuid));
> > free(nexthop);
> > - return;
> > + return NULL;
> > }
> >
> > - parsed_route_add(od, nexthop, &prefix, plen, false, lrp_addr_s,
> > - out_port, 0, false, false, NULL,
> > - ROUTE_SOURCE_LEARNED, &route->header_, NULL,
> > - parsed_routes_out);
> > + return parsed_route_add(od, nexthop, &prefix, plen, false, lrp_addr_s,
> > + out_port, 0, false, false, NULL,
> > + ROUTE_SOURCE_LEARNED, &route->header_, NULL,
> > + parsed_routes_out);
> > }
> >
> > static void
> > @@ -212,3 +241,77 @@ routes_table_sync(
> > parsed_route_hash(route));
> > }
> > }
> > +
> > +static struct parsed_route *
> > +find_learned_route(const struct sbrec_learned_route *learned_route,
> > + const struct ovn_datapaths *lr_datapaths,
> > + const struct hmap *routes)
> > +{
> > + const struct ovn_datapath *od = ovn_datapath_from_sbrec(
> > + NULL, &lr_datapaths->datapaths, learned_route->datapath);
> > + if (!od) {
> > + return NULL;
> > + }
> > + size_t hash = uuid_hash(&od->key);
>
>
> Let's not use parsed_route implementation internals. Let's expose a
> parsed_route_lookup_by_source(source, source_hint) or similar instead.
>
> Then we can just use that one directly in the change handler instead of
> re-implementing the logic in find_learned_route() here.
Sounds good.
>
> > + struct parsed_route *route;
> > + HMAP_FOR_EACH_WITH_HASH (route, key_node, hash, routes) {
> > + if (route->source == ROUTE_SOURCE_LEARNED &&
> > + uuid_equals(&route->source_hint->uuid,
> > + &learned_route->header_.uuid)) {
> > + return route;
> > + }
> > + }
> > + return NULL;
> > +}
> > +
> > +bool
> > +learned_route_sync_learned_route_change_handler(struct engine_node *node,
> > + void *data_ OVS_UNUSED)
> > +{
> > + struct learned_route_sync_data *data = data_;
> > +
> > + const struct sbrec_learned_route_table *sbrec_learned_route_table =
> > + EN_OVSDB_GET(engine_get_input("SB_learned_route", node));
> > + struct northd_data *northd_data = engine_get_input_data("northd",
> > node);
> > +
> > + const struct sbrec_learned_route *changed_route;
> > + SBREC_LEARNED_ROUTE_TABLE_FOR_EACH_TRACKED (changed_route,
> > + sbrec_learned_route_table)
> > {
> > + if (sbrec_learned_route_is_new(changed_route)) {
> > + struct parsed_route *route = parse_route_from_sbrec_route(
> > + &data->parsed_routes, &northd_data->lr_ports,
> > + &northd_data->lr_datapaths.datapaths, changed_route);
> > + if (route) {
> > + hmapx_add(&data->trk_data.trk_created_parsed_route, route);
> > + data->trk_data.type |= LEARNED_ROUTES_TRACKED_LEARNED;
> > + continue;
> > + }
> > + }
> > +
> > + if (sbrec_learned_route_is_deleted(changed_route)) {
> > + struct parsed_route *route = find_learned_route(
> > + changed_route, &northd_data->lr_datapaths,
> > + &data->parsed_routes);
> > + if (!route) {
> > + goto fail;
> > + }
> > + hmap_remove(&data->parsed_routes, &route->key_node);
> > + hmapx_add(&data->trk_data.trk_deleted_parsed_route, route);
> > + data->trk_data.type |= LEARNED_ROUTES_TRACKED_LEARNED;
> > + continue;
> > + }
> > +
> > + /* The learned routes should never be updated, so we just fail the
> > + * handler here if that happens. */
> > + goto fail;
> > + }
> > +
> > + if ((data->trk_data.type & LEARNED_ROUTES_TRACKED_LEARNED) != 0) {
> > + engine_set_node_state(node, EN_UPDATED);
> > + }
> > +
> > + return true;
>
> Nit: I'd add a newline here.
>
> > +fail:
> > + routes_sync_clear_tracked(data);
> > + return false;
> > +}
> > diff --git a/northd/en-learned-route-sync.h b/northd/en-learned-route-sync.h
> > index 1a5d6ff23..34baadaa1 100644
> > --- a/northd/en-learned-route-sync.h
> > +++ b/northd/en-learned-route-sync.h
> > @@ -18,15 +18,40 @@
> >
> > #include "lib/inc-proc-eng.h"
> > #include "openvswitch/hmap.h"
> > +#include "hmapx.h"
> > +
> > +enum learned_routes_tracked_data_type {
> > + LEARNED_ROUTES_TRACKED_NONE,
> > + LEARNED_ROUTES_TRACKED_LEARNED = (1 << 0),
> > +};
>
> I'm confused, will we ever need more than NONE / LEARNED? If not can we
> change this too bool?
I'm also confused what my orgiginal plan with this was. I'll change it :)
>
> > +
> > +/* Track what's changed in the learned routes engine node.
> > + * For now only tracks changed learned routes. */
> > +struct learned_routes_tracked_data {
> > + /* Indicates the type of data tracked. One or all of
> > ROUTES_TRACKED_*. */
>
>
> Nit: LEARNED_ROUTES_TRACKED_*?
>
> > + enum learned_routes_tracked_data_type type;
> > +
> > + /* Tracked created routes based on sb_learned_routes.
> > + * hmapx node is 'struct parsed_route *'. */
> > + struct hmapx trk_created_parsed_route;
> > +
> > + /* Tracked deleted routes based on sb_learned_routes.
> > + * hmapx node is 'struct parsed_route *'. */
> > + struct hmapx trk_deleted_parsed_route;
> > +};
> >
> > struct learned_route_sync_data {
> > struct hmap parsed_routes;
> > + struct learned_routes_tracked_data trk_data;
> > };
> >
> > bool learned_route_sync_northd_change_handler(struct engine_node *,
> > void *data);
> > +bool learned_route_sync_learned_route_change_handler(struct engine_node *,
> > + void *data);
> > void *en_learned_route_sync_init(struct engine_node *, struct engine_arg
> > *);
> > void en_learned_route_sync_cleanup(void *data);
> > +void en_learned_route_sync_clear_tracked_data(void *data);
> > void en_learned_route_sync_run(struct engine_node *, void *data);
> >
> > #endif /* EN_LEARNED_ROUTE_SYNC_H */
> > diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c
> > index 3fa99f637..bf0fbc62b 100644
> > --- a/northd/inc-proc-northd.c
> > +++ b/northd/inc-proc-northd.c
> > @@ -174,7 +174,8 @@ static ENGINE_NODE(ecmp_nexthop, "ecmp_nexthop");
> > static ENGINE_NODE(multicast_igmp, "multicast_igmp");
> > static ENGINE_NODE(acl_id, "acl_id");
> > static ENGINE_NODE(advertised_route_sync, "advertised_route_sync");
> > -static ENGINE_NODE(learned_route_sync, "learned_route_sync");
> > +static ENGINE_NODE_WITH_CLEAR_TRACK_DATA(learned_route_sync,
> > + "learned_route_sync");
> > static ENGINE_NODE(dynamic_routes, "dynamic_routes");
> >
> > void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> > @@ -307,7 +308,8 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb,
> > advertised_route_sync_northd_change_handler);
> >
> > engine_add_input(&en_learned_route_sync, &en_routes, NULL);
> > - engine_add_input(&en_learned_route_sync, &en_sb_learned_route, NULL);
> > + engine_add_input(&en_learned_route_sync, &en_sb_learned_route,
> > + learned_route_sync_learned_route_change_handler);
>
> Nit: to be inline with the rest of the handlers this should be:
>
> learned_route_sync_sb_learned_route_change_handler
>
> that is:
>
> <target_i_p_node>_<input_i_p_node>_change_handler
ok
>
> > engine_add_input(&en_learned_route_sync, &en_northd,
> > learned_route_sync_northd_change_handler);
> >
> > diff --git a/northd/northd.c b/northd/northd.c
> > index f1f1ede43..52bb67c0e 100644
> > --- a/northd/northd.c
> > +++ b/northd/northd.c
> > @@ -11079,7 +11079,7 @@ parsed_route_free(struct parsed_route *pr) {
> > * in there.
> > * Takes ownership of the provided nexthop. All other parameters are
> > cloned.
> > * Elements of the routes hmap need to be freed using parsed_route_free. */
> > -void
> > +struct parsed_route *
> > parsed_route_add(const struct ovn_datapath *od,
> > struct in6_addr *nexthop,
> > const struct in6_addr *prefix,
> > @@ -11108,9 +11108,11 @@ parsed_route_add(const struct ovn_datapath *od,
> > struct parsed_route *pr = parsed_route_lookup(routes, hash, new_pr);
> > if (!pr) {
> > hmap_insert(routes, &new_pr->key_node, hash);
> > + return new_pr;
> > } else {
> > pr->stale = false;
> > parsed_route_free(new_pr);
> > + return pr;
> > }
> > }
> >
> > @@ -19116,6 +19118,7 @@ routes_destroy(struct routes_data *data)
> >
> > simap_destroy(&data->route_tables);
> > __bfd_destroy(&data->bfd_active_connections);
> > +
>
> Nit: unrelated change.
>
> > }
> >
> > void
> > diff --git a/northd/northd.h b/northd/northd.h
> > index 1fbc93c8e..9402e4a4b 100644
> > --- a/northd/northd.h
> > +++ b/northd/northd.h
> > @@ -783,21 +783,22 @@ struct parsed_route *parsed_route_clone(const struct
> > parsed_route *);
> > size_t parsed_route_hash(const struct parsed_route *);
> > void parsed_route_free(struct parsed_route *);
> >
> > -void parsed_route_add(const struct ovn_datapath *od,
> > - struct in6_addr *nexthop,
> > - const struct in6_addr *prefix,
> > - unsigned int plen,
> > - bool is_discard_route,
> > - const char *lrp_addr_s,
> > - const struct ovn_port *out_port,
> > - uint32_t route_table_id,
> > - bool is_src_route,
> > - bool ecmp_symmetric_reply,
> > - const struct sset *ecmp_selection_fields,
> > - enum route_source source,
> > - const struct ovsdb_idl_row *source_hint,
> > - const struct ovn_port *tracked_port,
> > - struct hmap *routes);
> > +struct parsed_route * parsed_route_add(
>
>
> Nit: should be:
>
> struct parsed_route *parsed_route_add(
>
> > + const struct ovn_datapath *od,
> > + struct in6_addr *nexthop,
> > + const struct in6_addr *prefix,
> > + unsigned int plen,
> > + bool is_discard_route,
> > + const char *lrp_addr_s,
> > + const struct ovn_port *out_port,
> > + uint32_t route_table_id,
> > + bool is_src_route,
> > + bool ecmp_symmetric_reply,
> > + const struct sset *ecmp_selection_fields,
> > + enum route_source source,
> > + const struct ovsdb_idl_row *source_hint,
> > + const struct ovn_port *tracked_port,
> > + struct hmap *routes);
> >
> > bool
> > find_route_outport(const struct hmap *lr_ports, const char *output_port,
> > diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at
> > index 6a58ccc27..bb77f2abc 100644
> > --- a/tests/ovn-northd.at
> > +++ b/tests/ovn-northd.at
> > @@ -15324,8 +15324,8 @@ ovn-sbctl dump-flows lr0 > lr0flows
> > AT_CHECK([grep -w "lr_in_ip_routing" lr0flows | ovn_strip_lflows], [0],
> > [dnl
> > table=??(lr_in_ip_routing ), priority=0 , match=(1), action=(drop;)
> > table=??(lr_in_ip_routing ), priority=10550, match=(nd_rs || nd_ra),
> > action=(drop;)
> > - table=??(lr_in_ip_routing ), priority=194 , match=(reg7 == 0 &&
> > ip4.dst == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1;
> > reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
> > - table=??(lr_in_ip_routing ), priority=196 , match=(reg7 == 0 &&
> > ip4.dst == 192.168.0.0/24), action=(ip.ttl--; flags.loopback = 1;
> > reg8[[0..15]] = 2; reg8[[16..31]] = select(1, 2);)
> > + table=??(lr_in_ip_routing ), priority=194 , match=(reg7 == 0 &&
> > ip4.dst == 192.168.1.0/24), action=(ip.ttl--; flags.loopback = 1;
> > reg8[[0..15]] = 2; reg8[[16..31]] = select(1, 2);)
> > + table=??(lr_in_ip_routing ), priority=196 , match=(reg7 == 0 &&
> > ip4.dst == 192.168.0.0/24), action=(ip.ttl--; flags.loopback = 1;
> > reg8[[0..15]] = 1; reg8[[16..31]] = select(1, 2);)
> > table=??(lr_in_ip_routing ), priority=198 , match=(ip4.dst ==
> > 10.0.0.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 =
> > 10.0.0.1; eth.src = 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback
> > = 1; reg9[[9]] = 1; next;)
> > table=??(lr_in_ip_routing ), priority=198 , match=(ip4.dst ==
> > 10.0.1.0/24), action=(ip.ttl--; reg8[[0..15]] = 0; reg0 = ip4.dst; reg5 =
> > 10.0.1.1; eth.src = 00:00:00:00:ff:02; outport = "lr0-sw1"; flags.loopback
> > = 1; reg9[[9]] = 1; next;)
> > table=??(lr_in_ip_routing ), priority=518 , match=(inport ==
> > "lr0-sw0" && ip6.dst == fe80::/64), action=(ip.ttl--; reg8[[0..15]] = 0;
> > xxreg0 = ip6.dst; xxreg1 = fe80::200:ff:fe00:ff01; eth.src =
> > 00:00:00:00:ff:01; outport = "lr0-sw0"; flags.loopback = 1; reg9[[9]] = 0;
> > next;)
> > @@ -15333,10 +15333,10 @@ AT_CHECK([grep -w "lr_in_ip_routing" lr0flows |
> > ovn_strip_lflows], [0], [dnl
> > ])
> > AT_CHECK([grep -e "lr_in_ip_routing_ecmp" lr0flows | sed -e
> > 's/10\.0\..\./10.0.??./g' -e 's/lr0-sw./lr0-sw??/' -e
> > 's/00:ff:0./00:ff:0?/' | ovn_strip_lflows], [0], [dnl
> > table=??(lr_in_ip_routing_ecmp), priority=0 , match=(1),
> > action=(drop;)
> > - table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 1 && reg8[[16..31]] == 1), action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > - table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 1 && reg8[[16..31]] == 2), action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > - table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 2 && reg8[[16..31]] == 1), action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > - table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 2 && reg8[[16..31]] == 2), action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > + table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 1 && reg8[[16..31]] == 1), action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > + table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 1 && reg8[[16..31]] == 2), action=(reg0 = 10.0.??.10; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > + table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 2 && reg8[[16..31]] == 1), action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > + table=??(lr_in_ip_routing_ecmp), priority=100 , match=(reg8[[0..15]] ==
> > 2 && reg8[[16..31]] == 2), action=(reg0 = 10.0.??.20; reg5 = 10.0.??.1;
> > eth.src = 00:00:00:00:ff:0?; outport = "lr0-sw??"; reg9[[9]] = 1; next;)
> > table=??(lr_in_ip_routing_ecmp), priority=150 , match=(reg8[[0..15]] ==
> > 0), action=(next;)
> > ])
> >
> > @@ -15630,7 +15630,7 @@ check ovn-nbctl --wait=sb sync
> > check_engine_compute northd unchanged
> > check_engine_compute routes unchanged
> > check_engine_compute advertised_route_sync unchanged
> > -check_engine_compute learned_route_sync recompute
> > +check_engine_compute learned_route_sync incremental
> > check_engine_compute lflow recompute
> > CHECK_NO_CHANGE_AFTER_RECOMPUTE
> >
> > @@ -15640,7 +15640,7 @@ check ovn-nbctl --wait=sb sync
> > check_engine_compute northd unchanged
> > check_engine_compute routes unchanged
> > check_engine_compute advertised_route_sync unchanged
> > -check_engine_compute learned_route_sync recompute
> > +check_engine_compute learned_route_sync incremental
> > check_engine_compute lflow recompute
> > CHECK_NO_CHANGE_AFTER_RECOMPUTE
> >
Nits will be also addressed in the next version.
Thanks a lot,
Felix
>
> Regards,
> Dumitru
>
>
_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev