Calling sbrec_datapath_binding_insert() returns a pointer to the datapath binding that will be inserted once the transaction is committed. This pointer is only valid until the point the transaction is committed. At that point, the IDL will free the pointer and it is no longer valid. Caching this pointer, therefore, is dangerous, since you could be holding a pointer to freed data. Once the IDL runs again, it will provide a permanent pointer to the datapath binding, which is safe to cache.
This patch introduces a wrapper structure, ovn_datapath_binding. This wrapper structure is safe to cache since it is not deleted by the IDL. The inner sbrec_datapath_binding pointer can be updated by datapath syncing nodes when the IDL provides a new datapath binding pointer. This particular patch does nothing other than to introduce the structure and cache it where necessary. Upcoming commits will cause datapath syncing nodes to update their inner pointers as necessary, making downstream engine node caching safe. Signed-off-by: Mark Michelson <[email protected]> --- v13 * This is the first version of the series with this patch. --- northd/aging.c | 8 ++--- northd/datapath-sync.h | 21 ++++++++++++ northd/en-advertised-route-sync.c | 10 +++--- northd/en-datapath-logical-router.c | 4 ++- northd/en-datapath-logical-router.h | 3 +- northd/en-datapath-logical-switch.c | 4 ++- northd/en-datapath-logical-switch.h | 3 +- northd/en-multicast.c | 4 +-- northd/en-port-group.c | 2 +- northd/lflow-mgr.c | 4 +-- northd/northd.c | 50 ++++++++++++++--------------- northd/northd.h | 2 +- 12 files changed, 71 insertions(+), 44 deletions(-) diff --git a/northd/aging.c b/northd/aging.c index aa62a90dc..6afeeacff 100644 --- a/northd/aging.c +++ b/northd/aging.c @@ -412,7 +412,7 @@ en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED) HMAP_FOR_EACH (od, key_node, &northd_data->lr_datapaths.datapaths) { ovs_assert(od->nbr); - if (!od->sb) { + if (!od->dp->sb_dp) { continue; } @@ -425,7 +425,7 @@ en_mac_binding_aging_run(struct engine_node *node, void *data OVS_UNUSED) aging_context_set_threshold(&ctx, &threshold_config); - mac_binding_aging_run_for_datapath(od->sb, + mac_binding_aging_run_for_datapath(od->dp->sb_dp, sbrec_mac_binding_by_datapath, &ctx); threshold_config_destroy(&threshold_config); @@ -544,7 +544,7 @@ en_fdb_aging_run(struct engine_node *node, void *data OVS_UNUSED) HMAP_FOR_EACH (od, key_node, &northd_data->ls_datapaths.datapaths) { ovs_assert(od->nbs); - if (!od->sb) { + if (!od->dp->sb_dp) { continue; } @@ -553,7 +553,7 @@ en_fdb_aging_run(struct engine_node *node, void *data OVS_UNUSED) threshold_config.default_threshold = smap_get_uint(&od->nbs->other_config, "fdb_age_threshold", 0); aging_context_set_threshold(&ctx, &threshold_config); - fdb_run_for_datapath(od->sb, sbrec_fdb_by_dp_key, &ctx); + fdb_run_for_datapath(od->dp->sb_dp, sbrec_fdb_by_dp_key, &ctx); if (aging_context_is_at_limit(&ctx)) { /* Schedule the next run after specified delay. */ diff --git a/northd/datapath-sync.h b/northd/datapath-sync.h index 246500c7b..aa374dd15 100644 --- a/northd/datapath-sync.h +++ b/northd/datapath-sync.h @@ -74,6 +74,27 @@ struct ovn_synced_datapaths { struct hmap synced_dps; }; +/* This is a simple wrapper for a southbound datapath binding. Its purpose + * is to allow for engine nodes to cache datapath binding pointers safely. + * + * When an sbrec_datapath_binding is inserted into the southbound + * database, the pointer from sbrec_datapath_binding_insert() is not + * safe to cache. The pointer is freed when the IDL loop is run and + * the transaction is committed. Once the next IDL loop is run and the + * southbound database sends an update message with the newly-inserted + * datapath binding, the pointer provided by the IDL is now safe to cache + * until the datapath binding is deleted. + * + * Engine nodes can safely cache an ovn_datapath_binding, though. The + * reason is that datapath syncing nodes will update the inner sb_dp + * pointer to be the permanent version of the pointer once the IDL + * provides the pointer. So long as the outer ovn_datapath_binding is + * what is cached, it is perfectly safe. + */ +struct ovn_datapath_binding { + const struct sbrec_datapath_binding *sb_dp; +}; + struct ovn_unsynced_datapath *ovn_unsynced_datapath_alloc( const char *name, enum ovn_datapath_type type, uint32_t requested_tunnel_key, const struct ovsdb_idl_row *nb_row); diff --git a/northd/en-advertised-route-sync.c b/northd/en-advertised-route-sync.c index e75ab15c5..b2eeefa85 100644 --- a/northd/en-advertised-route-sync.c +++ b/northd/en-advertised-route-sync.c @@ -56,7 +56,7 @@ ar_entry_add_nocopy(struct hmap *routes, const struct ovn_datapath *od, route_e->ip_prefix = ip_prefix; route_e->tracked_port = tracked_port; route_e->source = source; - uint32_t hash = uuid_hash(&od->sb->header_.uuid); + uint32_t hash = uuid_hash(&od->dp->sb_dp->header_.uuid); hash = hash_string(op->sb->logical_port, hash); hash = hash_string(ip_prefix, hash); hmap_insert(routes, &route_e->hmap_node, hash); @@ -92,7 +92,7 @@ ar_entry_find(struct hmap *route_map, HMAP_FOR_EACH_WITH_HASH (route_e, hmap_node, hash, route_map) { if (!uuid_equals(&sb_db->header_.uuid, - &route_e->od->sb->header_.uuid)) { + &route_e->od->dp->sb_dp->header_.uuid)) { continue; } if (!uuid_equals(&logical_port->header_.uuid, @@ -281,7 +281,7 @@ build_nat_route_for_port(const struct ovn_port *advertising_op, ? ovn_port_find(ls_ports, nat->nb->logical_port) : nat->l3dgw_port; - if (!ar_entry_find(routes, advertising_od->sb, advertising_op->sb, + if (!ar_entry_find(routes, advertising_od->dp->sb_dp, advertising_op->sb, nat->nb->external_ip, tracked_port ? tracked_port->sb : NULL)) { ar_entry_add(routes, advertising_od, advertising_op, @@ -767,7 +767,7 @@ advertised_route_table_sync( const struct sbrec_port_binding *tracked_pb = route_e->tracked_port ? route_e->tracked_port->sb : NULL; - if (ar_entry_find(&sync_routes, route_e->od->sb, route_e->op->sb, + if (ar_entry_find(&sync_routes, route_e->od->dp->sb_dp, route_e->op->sb, route_e->ip_prefix, tracked_pb)) { /* We could already have advertised route entry for LRP IP that * corresponds to "snat" when "connected-as-host" is combined @@ -802,7 +802,7 @@ advertised_route_table_sync( HMAP_FOR_EACH_POP (route_e, hmap_node, &sync_routes) { const struct sbrec_advertised_route *sr = sbrec_advertised_route_insert(ovnsb_txn); - sbrec_advertised_route_set_datapath(sr, route_e->od->sb); + sbrec_advertised_route_set_datapath(sr, route_e->od->dp->sb_dp); sbrec_advertised_route_set_logical_port(sr, route_e->op->sb); sbrec_advertised_route_set_ip_prefix(sr, route_e->ip_prefix); if (route_e->tracked_port) { diff --git a/northd/en-datapath-logical-router.c b/northd/en-datapath-logical-router.c index 86c3eb365..d15907d6b 100644 --- a/northd/en-datapath-logical-router.c +++ b/northd/en-datapath-logical-router.c @@ -172,7 +172,9 @@ en_datapath_synced_logical_router_run(struct engine_node *node , void *data) *lr = (struct ovn_synced_logical_router) { .nb = CONTAINER_OF(sdp->nb_row, struct nbrec_logical_router, header_), - .sb = sdp->sb_dp, + .dp = (struct ovn_datapath_binding) { + .sb_dp = sdp->sb_dp, + } }; hmap_insert(&router_map->synced_routers, &lr->hmap_node, uuid_hash(&lr->nb->header_.uuid)); diff --git a/northd/en-datapath-logical-router.h b/northd/en-datapath-logical-router.h index 31e70fbf5..ce1b321e5 100644 --- a/northd/en-datapath-logical-router.h +++ b/northd/en-datapath-logical-router.h @@ -18,6 +18,7 @@ #define EN_DATAPATH_LOGICAL_ROUTER_H #include "lib/inc-proc-eng.h" +#include "datapath-sync.h" #include "openvswitch/hmap.h" void *en_datapath_logical_router_init(struct engine_node *, @@ -30,7 +31,7 @@ void en_datapath_logical_router_cleanup(void *data); struct ovn_synced_logical_router { struct hmap_node hmap_node; const struct nbrec_logical_router *nb; - const struct sbrec_datapath_binding *sb; + struct ovn_datapath_binding dp; }; struct ovn_synced_logical_router_map { diff --git a/northd/en-datapath-logical-switch.c b/northd/en-datapath-logical-switch.c index 23fa9725f..3a6cd2e0a 100644 --- a/northd/en-datapath-logical-switch.c +++ b/northd/en-datapath-logical-switch.c @@ -166,7 +166,9 @@ en_datapath_synced_logical_switch_run(struct engine_node *node , void *data) *lsw = (struct ovn_synced_logical_switch) { .nb = CONTAINER_OF(sdp->nb_row, struct nbrec_logical_switch, header_), - .sb = sdp->sb_dp, + .dp = (struct ovn_datapath_binding) { + .sb_dp = sdp->sb_dp, + }, }; hmap_insert(&switch_map->synced_switches, &lsw->hmap_node, uuid_hash(&lsw->nb->header_.uuid)); diff --git a/northd/en-datapath-logical-switch.h b/northd/en-datapath-logical-switch.h index 1bd5fea83..55928d705 100644 --- a/northd/en-datapath-logical-switch.h +++ b/northd/en-datapath-logical-switch.h @@ -18,6 +18,7 @@ #define EN_DATAPATH_LOGICAL_SWITCH_H #include "lib/inc-proc-eng.h" +#include "datapath-sync.h" #include "openvswitch/hmap.h" void *en_datapath_logical_switch_init(struct engine_node *, @@ -30,7 +31,7 @@ void en_datapath_logical_switch_cleanup(void *data); struct ovn_synced_logical_switch { struct hmap_node hmap_node; const struct nbrec_logical_switch *nb; - const struct sbrec_datapath_binding *sb; + struct ovn_datapath_binding dp; }; struct ovn_synced_logical_switch_map { diff --git a/northd/en-multicast.c b/northd/en-multicast.c index d51db557e..fb34c55f5 100644 --- a/northd/en-multicast.c +++ b/northd/en-multicast.c @@ -434,7 +434,7 @@ sync_multicast_groups_to_sb( continue; } - sbmc = create_sb_multicast_group(ovnsb_txn, mc->datapath->sb, + sbmc = create_sb_multicast_group(ovnsb_txn, mc->datapath->dp->sb_dp, mc->group->name, mc->group->key); ovn_multicast_update_sbrec(mc, sbmc); } @@ -579,7 +579,7 @@ ovn_igmp_group_add(struct ovsdb_idl_index *sbrec_mcast_group_by_name_dp, const struct sbrec_multicast_group *mcgroup = mcast_group_lookup(sbrec_mcast_group_by_name_dp, address_s, - datapath->sb); + datapath->dp->sb_dp); igmp_group->datapath = datapath; igmp_group->address = *address; diff --git a/northd/en-port-group.c b/northd/en-port-group.c index 4fc1a4f24..b2dd4aa59 100644 --- a/northd/en-port-group.c +++ b/northd/en-port-group.c @@ -255,7 +255,7 @@ ls_port_group_process(struct ls_port_group_table *ls_port_groups, struct ls_port_group *ls_pg = ls_port_group_table_find(ls_port_groups, od->nbs); if (!ls_pg) { - ls_pg = ls_port_group_create(ls_port_groups, od->nbs, od->sb); + ls_pg = ls_port_group_create(ls_port_groups, od->nbs, od->dp->sb_dp); } struct ls_port_group_record *ls_pg_rec = diff --git a/northd/lflow-mgr.c b/northd/lflow-mgr.c index 18b88cf9e..28156c1e1 100644 --- a/northd/lflow-mgr.c +++ b/northd/lflow-mgr.c @@ -1118,7 +1118,7 @@ sync_lflow_to_sb(struct ovn_lflow *lflow, } if (lflow->od) { - sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->sb); + sbrec_logical_flow_set_logical_datapath(sbflow, lflow->od->dp->sb_dp); sbrec_logical_flow_set_logical_dp_group(sbflow, NULL); } else { sbrec_logical_flow_set_logical_datapath(sbflow, NULL); @@ -1226,7 +1226,7 @@ ovn_sb_insert_or_update_logical_dp_group( sb = xmalloc(bitmap_count1(dpg_bitmap, ods_size(datapaths)) * sizeof *sb); BITMAP_FOR_EACH_1 (index, ods_size(datapaths), dpg_bitmap) { - sb[n++] = datapaths->array[index]->sb; + sb[n++] = datapaths->array[index]->dp->sb_dp; } if (!dp_group) { struct uuid dpg_uuid = uuid_random(); diff --git a/northd/northd.c b/northd/northd.c index 4d5ed6701..992c18d23 100644 --- a/northd/northd.c +++ b/northd/northd.c @@ -518,11 +518,11 @@ static struct ovn_datapath * ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, const struct nbrec_logical_switch *nbs, const struct nbrec_logical_router *nbr, - const struct sbrec_datapath_binding *sb) + const struct ovn_datapath_binding *dp) { struct ovn_datapath *od = xzalloc(sizeof *od); od->key = *key; - od->sb = sb; + od->dp = dp; od->nbs = nbs; od->nbr = nbr; hmap_init(&od->port_tnlids); @@ -537,7 +537,7 @@ ovn_datapath_create(struct hmap *datapaths, const struct uuid *key, od->localnet_ports = VECTOR_EMPTY_INITIALIZER(struct ovn_port *); od->lb_with_stateless_mode = false; od->ipam_info_initialized = false; - od->tunnel_key = sb->tunnel_key; + od->tunnel_key = dp->sb_dp->tunnel_key; init_mcast_info_for_datapath(od); return od; } @@ -628,7 +628,7 @@ ovn_datapath_from_sbrec(const struct hmap *ls_datapaths, } struct ovn_datapath *od = ovn_datapath_find_(dps, &key); - if (od && (od->sb == sb)) { + if (od && (od->dp->sb_dp == sb)) { return od; } @@ -747,7 +747,7 @@ store_mcast_info_for_switch_datapath(const struct sbrec_ip_multicast *sb, { struct mcast_switch_info *mcast_sw_info = &od->mcast_info.sw; - sbrec_ip_multicast_set_datapath(sb, od->sb); + sbrec_ip_multicast_set_datapath(sb, od->dp->sb_dp); sbrec_ip_multicast_set_enabled(sb, &mcast_sw_info->enabled, 1); sbrec_ip_multicast_set_querier(sb, &mcast_sw_info->querier, 1); sbrec_ip_multicast_set_table_size(sb, &mcast_sw_info->table_size, 1); @@ -856,7 +856,7 @@ build_datapaths(const struct ovn_synced_logical_switch_map *ls_map, struct ovn_datapath *od = ovn_datapath_create(&ls_datapaths->datapaths, &ls->nb->header_.uuid, - ls->nb, NULL, ls->sb); + ls->nb, NULL, &ls->dp); init_ipam_info_for_datapath(od); if (smap_get_bool(&od->nbs->other_config, "enable-stateless-acl-with-lb", @@ -870,7 +870,7 @@ build_datapaths(const struct ovn_synced_logical_switch_map *ls_map, struct ovn_datapath *od = ovn_datapath_create(&lr_datapaths->datapaths, &lr->nb->header_.uuid, - NULL, lr->nb, lr->sb); + NULL, lr->nb, &lr->dp); if (smap_get(&od->nbr->options, "chassis")) { od->is_gw_router = true; } @@ -1441,7 +1441,7 @@ 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(ovn_datapath_name(op->od->sb), + char *mp_name = ovn_mirror_port_name(ovn_datapath_name(op->od->dp->sb_dp), 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); @@ -1485,7 +1485,7 @@ join_logical_ports_lsp(struct hmap *ports, = VLOG_RATE_LIMIT_INIT(5, 1); VLOG_WARN_RL(&rl, "duplicate logical port %s", name); return NULL; - } else if (op && (!op->sb || op->sb->datapath == od->sb)) { + } else if (op && (!op->sb || op->sb->datapath == od->dp->sb_dp)) { /* * Handle cases where lport type was explicitly changed * in the NBDB, in such cases: @@ -1582,7 +1582,7 @@ join_logical_ports_lrp(struct hmap *ports, name); destroy_lport_addresses(lrp_networks); return NULL; - } else if (op && (!op->sb || op->sb->datapath == od->sb)) { + } else if (op && (!op->sb || op->sb->datapath == od->dp->sb_dp)) { ovn_port_set_nb(op, NULL, nbrp); ovs_list_remove(&op->list); ovs_list_push_back(both, &op->list); @@ -1655,7 +1655,7 @@ create_cr_port(struct ovn_port *op, struct hmap *ports, op->nbsp ? op->nbsp->name : op->nbrp->name); struct ovn_port *crp = ovn_port_find(ports, redirect_name); - if (crp && crp->sb && crp->sb->datapath == op->od->sb) { + if (crp && crp->sb && crp->sb->datapath == op->od->dp->sb_dp) { ovn_port_set_nb(crp, op->nbsp, op->nbrp); ovs_list_remove(&crp->list); ovs_list_push_back(both_dbs, &crp->list); @@ -2513,7 +2513,7 @@ ovn_port_update_sbrec(struct ovsdb_idl_txn *ovnsb_txn, unsigned long *queue_id_bitmap, struct sset *active_ha_chassis_grps) { - sbrec_port_binding_set_datapath(op->sb, op->od->sb); + sbrec_port_binding_set_datapath(op->sb, op->od->dp->sb_dp); if (op->nbrp) { /* Note: SB port binding options for router ports are set in * sync_pbs(). */ @@ -3829,7 +3829,7 @@ build_ports(struct ovsdb_idl_txn *ovnsb_txn, /* When reusing stale Port_Bindings, make sure that stale * Mac_Bindings are purged. */ - if (op->od->sb != op->sb->datapath) { + if (op->od->dp->sb_dp != op->sb->datapath) { remove_mac_bindings = true; } if (op->nbsp) { @@ -5340,7 +5340,7 @@ build_mirror_lflows(struct ovn_port *op, } char *serving_port_name = ovn_mirror_port_name( - ovn_datapath_name(op->od->sb), + ovn_datapath_name(op->od->dp->sb_dp), mirror->sink); struct ovn_port *serving_port = ovn_port_find(ls_ports, @@ -18037,13 +18037,13 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn, const struct sbrec_multicast_group *sbmc_flood = mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp, - MC_FLOOD, op->od->sb); + MC_FLOOD, op->od->dp->sb_dp); const struct sbrec_multicast_group *sbmc_flood_l2 = mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp, - MC_FLOOD_L2, op->od->sb); + MC_FLOOD_L2, op->od->dp->sb_dp); const struct sbrec_multicast_group *sbmc_unknown = mcast_group_lookup(lflow_input->sbrec_mcast_group_by_name_dp, - MC_UNKNOWN, op->od->sb); + MC_UNKNOWN, op->od->dp->sb_dp); struct ds match = DS_EMPTY_INITIALIZER; struct ds actions = DS_EMPTY_INITIALIZER; @@ -18083,13 +18083,13 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn, /* Update SB multicast groups for the new port. */ if (!sbmc_flood) { sbmc_flood = create_sb_multicast_group(ovnsb_txn, - op->od->sb, MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY); + op->od->dp->sb_dp, MC_FLOOD, OVN_MCAST_FLOOD_TUNNEL_KEY); } sbrec_multicast_group_update_ports_addvalue(sbmc_flood, op->sb); if (!sbmc_flood_l2) { sbmc_flood_l2 = create_sb_multicast_group(ovnsb_txn, - op->od->sb, MC_FLOOD_L2, + op->od->dp->sb_dp, MC_FLOOD_L2, OVN_MCAST_FLOOD_L2_TUNNEL_KEY); } sbrec_multicast_group_update_ports_addvalue(sbmc_flood_l2, op->sb); @@ -18097,7 +18097,7 @@ lflow_handle_northd_port_changes(struct ovsdb_idl_txn *ovnsb_txn, if (op->has_unknown) { if (!sbmc_unknown) { sbmc_unknown = create_sb_multicast_group(ovnsb_txn, - op->od->sb, MC_UNKNOWN, + op->od->dp->sb_dp, MC_UNKNOWN, OVN_MCAST_UNKNOWN_TUNNEL_KEY); } sbrec_multicast_group_update_ports_addvalue(sbmc_unknown, @@ -18418,7 +18418,7 @@ sync_dns_entries(struct ovsdb_idl_txn *ovnsb_txn, dns_info->n_sbs++; dns_info->sbs = xrealloc(dns_info->sbs, dns_info->n_sbs * sizeof *dns_info->sbs); - dns_info->sbs[dns_info->n_sbs - 1] = od->sb; + dns_info->sbs[dns_info->n_sbs - 1] = od->dp->sb_dp; } } @@ -18535,7 +18535,7 @@ build_ip_mcast(struct ovsdb_idl_txn *ovnsb_txn, ovs_assert(od->nbs); const struct sbrec_ip_multicast *ip_mcast = - ip_mcast_lookup(sbrec_ip_mcast_by_dp, od->sb); + ip_mcast_lookup(sbrec_ip_mcast_by_dp, od->dp->sb_dp); if (!ip_mcast) { ip_mcast = sbrec_ip_multicast_insert(ovnsb_txn); @@ -18576,7 +18576,7 @@ build_static_mac_binding_table( } struct ovn_port *op = ovn_port_find(lr_ports, nb_smb->logical_port); - if (!op || !op->nbrp || !op->od || !op->od->sb) { + if (!op || !op->nbrp || !op->od || !op->od->dp->sb_dp) { sbrec_static_mac_binding_delete(sb_smb); } } @@ -18588,7 +18588,7 @@ build_static_mac_binding_table( struct ovn_port *op = ovn_port_find(lr_ports, nb_smb->logical_port); if (op && op->nbrp) { struct ovn_datapath *od = op->od; - if (od && od->sb) { + if (od && od->dp->sb_dp) { const struct uuid *nb_uuid = &nb_smb->header_.uuid; const struct sbrec_static_mac_binding *mb = sbrec_static_mac_binding_table_get_for_uuid( @@ -18603,7 +18603,7 @@ build_static_mac_binding_table( sbrec_static_mac_binding_set_mac(mb, nb_smb->mac); sbrec_static_mac_binding_set_override_dynamic_mac(mb, nb_smb->override_dynamic_mac); - sbrec_static_mac_binding_set_datapath(mb, od->sb); + sbrec_static_mac_binding_set_datapath(mb, od->dp->sb_dp); } else { /* Update existing entry if there is a change*/ if (strcmp(mb->mac, nb_smb->mac)) { diff --git a/northd/northd.h b/northd/northd.h index 2f1b8ceb5..e223a54da 100644 --- a/northd/northd.h +++ b/northd/northd.h @@ -369,7 +369,7 @@ struct ovn_datapath { const struct nbrec_logical_switch *nbs; /* May be NULL. */ const struct nbrec_logical_router *nbr; /* May be NULL. */ - const struct sbrec_datapath_binding *sb; /* May be NULL. */ + const struct ovn_datapath_binding *dp; /* May be NULL. */ struct ovs_list list; /* In list of similar records. */ -- 2.49.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
