Collect vif and router ports IPs/MAC bindings in SB Advertised_Mac_Binding table in order to announce them as EVPN Type2 Route messages.
Signed-off-by: Lorenzo Bianconi <[email protected]> --- NEWS | 3 + northd/en-advertised-route-sync.c | 250 ++++++++++++++++++++++++++++++ northd/en-advertised-route-sync.h | 7 + northd/inc-proc-northd.c | 10 +- ovn-nb.xml | 6 + ovn-sb.ovsschema | 22 ++- ovn-sb.xml | 29 ++++ tests/ovn-northd.at | 48 +++++- 8 files changed, 366 insertions(+), 9 deletions(-) diff --git a/NEWS b/NEWS index fb4d63194..77a329669 100644 --- a/NEWS +++ b/NEWS @@ -14,6 +14,9 @@ Post v25.09.0 Switches. If set to "true" ovn-controller will give preference to SB (Static_)MAC_Bindings of adjacent logical routers over ARPs learned through EVPN on the switch. + * Add the "other_config:dynamic-routing-redistribute=ip" to Logical + Switches to announce local IPs/MAC bindings belonging to the same + EVPN domain. - Add support for Network Function insertion in OVN with stateful traffic redirection capability in Logical Switch datapath. The feature introduces three new NB database tables: diff --git a/northd/en-advertised-route-sync.c b/northd/en-advertised-route-sync.c index 423baaacf..1bcf1ebf9 100644 --- a/northd/en-advertised-route-sync.c +++ b/northd/en-advertised-route-sync.c @@ -834,3 +834,253 @@ advertised_route_table_sync( hmap_destroy(&sync_routes); } +struct evpn_type2 { + struct hmap_node hmap_node; + + const struct sbrec_port_binding *sb; + char *ip; + char *mac; +}; + +static bool +evpn_is_ip_redistribute_running(const struct ovn_datapath *od) +{ + int64_t vni = ovn_smap_get_llong(&od->nbs->other_config, + "dynamic-routing-vni", -1); + if (!ovn_is_valid_vni(vni)) { + return false; + } + + const char *redistribute = smap_get(&od->nbs->other_config, + "dynamic-routing-redistribute"); + return redistribute && !strcmp(redistribute, "ip"); +} + +static struct evpn_type2 * +evpn_type2_entry_find(struct hmap *map, + const char *ip, const char *mac) +{ + uint32_t hash = hash_string(ip, 0); + hash = hash_string(mac, hash); + + struct evpn_type2 *e; + HMAP_FOR_EACH_WITH_HASH (e, hmap_node, hash, map) { + if (!strcmp(e->ip, ip) && !strcmp(e->mac, mac)) { + return e; + } + } + + return NULL; +} + +static void +evpn_type2_entry_add(struct hmap *map, + const struct sbrec_port_binding *sb, + const char *ip, const char *mac) +{ + struct evpn_type2 *e = xmalloc(sizeof *e); + e->ip = xstrdup(ip); + e->mac = xstrdup(mac); + e->sb = sb; + + uint32_t hash = hash_string(ip, 0); + hash = hash_string(mac, hash); + hmap_insert(map, &e->hmap_node, hash); +} + +static void +evpn_type2_entry_destroy(struct evpn_type2 *e) +{ + free(e->ip); + free(e->mac); + free(e); +} + +static void +evpn_add_type2(struct hmap *map, const struct sbrec_port_binding *sb, + struct lport_addresses *addr) +{ + struct ds ip = DS_EMPTY_INITIALIZER; + + if (!addr) { + return; + } + + for (size_t i = 0; i < addr->n_ipv4_addrs; i++) { + ds_clear(&ip); + ds_put_format(&ip, "%s/32", addr->ipv4_addrs[i].addr_s); + if (!evpn_type2_entry_find(map, ds_cstr(&ip), addr->ea_s)) { + evpn_type2_entry_add(map, sb, ds_cstr(&ip), addr->ea_s); + } + } + + for (size_t i = 0; i < addr->n_ipv6_addrs; i++) { + if (prefix_is_link_local(&addr->ipv6_addrs[i].addr, 128)) { + continue; + } + + ds_clear(&ip); + ds_put_format(&ip, "%s/128", addr->ipv6_addrs[i].addr_s); + if (!evpn_type2_entry_find(map, ds_cstr(&ip), addr->ea_s)) { + evpn_type2_entry_add(map, sb, ds_cstr(&ip), addr->ea_s); + } + } + + ds_destroy(&ip); +} + +static void +build_evpn_type2_for_ls(const struct ovn_datapath *od, struct hmap *map) +{ + ovs_assert(od->nbs); + + if (!evpn_is_ip_redistribute_running(od)) { + return; + } + + struct ovn_port *op; + HMAP_FOR_EACH (op, dp_node, &od->ports) { + if (!op->sb) { + continue; + } + + if (lsp_is_router(op->nbsp) && op->peer) { + evpn_add_type2(map, op->sb, &op->peer->lrp_networks); + } + + if (!strcmp(op->nbsp->type, "")) { /* LSP */ + evpn_add_type2(map, op->sb, op->lsp_addrs); + } + } +} + +static void +build_evpn_type2_for_lr(const struct ovn_datapath *od, struct hmap *map) +{ + ovs_assert(od->nbr); + + struct ovn_port *op; + HMAP_FOR_EACH (op, dp_node, &od->ports) { + struct ovn_port *peer = op->peer; + if (!peer || !peer->od || !peer->od->nbs || !peer->sb) { + continue; + } + + struct ovn_datapath *peer_od = peer->od; + if (evpn_is_ip_redistribute_running(peer_od)) { + evpn_add_type2(map, peer->sb, &op->lrp_networks); + } + } +} + +void * +en_evpn_type2_sync_init(struct engine_node *node OVS_UNUSED, + struct engine_arg *arg OVS_UNUSED) +{ + return NULL; +} + +enum engine_node_state +en_evpn_type2_sync_run(struct engine_node *node, void *data OVS_UNUSED) +{ + struct northd_data *northd_data = engine_get_input_data("northd", node); + const struct sbrec_advertised_mac_binding_table *sbrec_adv_mb_table = + EN_OVSDB_GET(engine_get_input("SB_advertised_mac_binding", node)); + const struct engine_context *eng_ctx = engine_get_context(); + + struct hmap evpn_type2_map = HMAP_INITIALIZER(&evpn_type2_map); + + struct ovn_datapath *od; + HMAP_FOR_EACH (od, key_node, &northd_data->lr_datapaths.datapaths) { + build_evpn_type2_for_lr(od, &evpn_type2_map); + } + + HMAP_FOR_EACH (od, key_node, &northd_data->ls_datapaths.datapaths) { + build_evpn_type2_for_ls(od, &evpn_type2_map); + } + + struct evpn_type2 *e; + const struct sbrec_advertised_mac_binding *sb_adv_mb; + SBREC_ADVERTISED_MAC_BINDING_TABLE_FOR_EACH_SAFE (sb_adv_mb, + sbrec_adv_mb_table) { + e = evpn_type2_entry_find(&evpn_type2_map, sb_adv_mb->ip, + sb_adv_mb->mac); + if (!e) { + sbrec_advertised_mac_binding_delete(sb_adv_mb); + } else { + hmap_remove(&evpn_type2_map, &e->hmap_node); + evpn_type2_entry_destroy(e); + } + } + + HMAP_FOR_EACH_POP (e, hmap_node, &evpn_type2_map) { + sb_adv_mb = + sbrec_advertised_mac_binding_insert(eng_ctx->ovnsb_idl_txn); + sbrec_advertised_mac_binding_set_datapath(sb_adv_mb, e->sb->datapath); + sbrec_advertised_mac_binding_set_logical_port(sb_adv_mb, e->sb); + sbrec_advertised_mac_binding_set_ip(sb_adv_mb, e->ip); + sbrec_advertised_mac_binding_set_mac(sb_adv_mb, e->mac); + evpn_type2_entry_destroy(e); + } + + hmap_destroy(&evpn_type2_map); + + return EN_UPDATED; +} + +void +en_evpn_type2_sync_cleanup(void *data OVS_UNUSED) +{ +} + +enum engine_input_handler_result +evpn_type2_sync_northd_change_handler(struct engine_node *node, + void *data OVS_UNUSED) +{ + struct northd_data *northd_data = engine_get_input_data("northd", node); + if (!northd_has_tracked_data(&northd_data->trk_data)) { + return EN_UNHANDLED; + } + + struct northd_tracked_data *nd_changes = &northd_data->trk_data; + + struct hmapx_node *n; + HMAPX_FOR_EACH (n, &nd_changes->trk_switches.crupdated) { + const struct ovn_datapath *od = n->data; + if (evpn_is_ip_redistribute_running(od)) { + return EN_UNHANDLED; + } + } + HMAPX_FOR_EACH (n, &nd_changes->trk_switches.deleted) { + const struct ovn_datapath *od = n->data; + if (evpn_is_ip_redistribute_running(od)) { + return EN_UNHANDLED; + } + } + + HMAPX_FOR_EACH (n, &nd_changes->trk_routers.crupdated) { + const struct ovn_datapath *od = n->data; + struct ovn_port *op; + HMAP_FOR_EACH (op, dp_node, &od->ports) { + struct ovn_port *peer = op->peer; + if (peer && peer->od && peer->od->nbs && + evpn_is_ip_redistribute_running(peer->od)) { + return EN_UNHANDLED; + } + } + } + HMAPX_FOR_EACH (n, &nd_changes->trk_routers.deleted) { + const struct ovn_datapath *od = n->data; + struct ovn_port *op; + HMAP_FOR_EACH (op, dp_node, &od->ports) { + struct ovn_port *peer = op->peer; + if (peer && peer->od && peer->od->nbs && + evpn_is_ip_redistribute_running(peer->od)) { + return EN_UNHANDLED; + } + } + } + + return EN_HANDLED_UNCHANGED; +} + diff --git a/northd/en-advertised-route-sync.h b/northd/en-advertised-route-sync.h index 7a2927000..2557a6c7f 100644 --- a/northd/en-advertised-route-sync.h +++ b/northd/en-advertised-route-sync.h @@ -42,4 +42,11 @@ enum engine_node_state en_advertised_route_sync_run(struct engine_node *, void *en_dynamic_routes_init(struct engine_node *, struct engine_arg *); void en_dynamic_routes_cleanup(void *data); enum engine_node_state en_dynamic_routes_run(struct engine_node *, void *data); + +void *en_evpn_type2_sync_init(struct engine_node *, struct engine_arg *); +void en_evpn_type2_sync_cleanup(void *data); +enum engine_node_state en_evpn_type2_sync_run(struct engine_node *, + void *data); +enum engine_input_handler_result +evpn_type2_sync_northd_change_handler(struct engine_node *, void *data); #endif /* EN_ADVERTISED_ROUTE_SYNC_H */ diff --git a/northd/inc-proc-northd.c b/northd/inc-proc-northd.c index fdea550d7..762c72f2d 100644 --- a/northd/inc-proc-northd.c +++ b/northd/inc-proc-northd.c @@ -118,7 +118,8 @@ static unixctl_cb_func chassis_features_list; SB_NODE(ecmp_nexthop) \ SB_NODE(acl_id) \ SB_NODE(advertised_route) \ - SB_NODE(learned_route) + SB_NODE(learned_route) \ + SB_NODE(advertised_mac_binding) enum sb_engine_node { #define SB_NODE(NAME) SB_##NAME, @@ -181,6 +182,7 @@ static ENGINE_NODE(ecmp_nexthop); static ENGINE_NODE(multicast_igmp); static ENGINE_NODE(acl_id); static ENGINE_NODE(advertised_route_sync); +static ENGINE_NODE(evpn_type2_sync); static ENGINE_NODE(learned_route_sync, CLEAR_TRACKED_DATA); static ENGINE_NODE(dynamic_routes); static ENGINE_NODE(group_ecmp_route, CLEAR_TRACKED_DATA); @@ -355,6 +357,11 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_advertised_route_sync, &en_northd, advertised_route_sync_northd_change_handler); + engine_add_input(&en_evpn_type2_sync, &en_sb_advertised_mac_binding, NULL); + engine_add_input(&en_evpn_type2_sync, &en_sb_port_binding, NULL); + engine_add_input(&en_evpn_type2_sync, &en_northd, + evpn_type2_sync_northd_change_handler); + engine_add_input(&en_learned_route_sync, &en_sb_learned_route, learned_route_sync_sb_learned_route_change_handler); engine_add_input(&en_learned_route_sync, &en_northd, @@ -452,6 +459,7 @@ void inc_proc_northd_init(struct ovsdb_idl_loop *nb, engine_add_input(&en_sync_from_sb, &en_sb_ha_chassis_group, NULL); engine_add_input(&en_northd_output, &en_acl_id, NULL); + engine_add_input(&en_northd_output, &en_evpn_type2_sync, NULL); engine_add_input(&en_northd_output, &en_sync_from_sb, NULL); engine_add_input(&en_northd_output, &en_sync_to_sb, northd_output_sync_to_sb_handler); diff --git a/ovn-nb.xml b/ovn-nb.xml index 73b5f213f..c2d16f5eb 100644 --- a/ovn-nb.xml +++ b/ovn-nb.xml @@ -977,6 +977,12 @@ routers. </p> + <p> + If <code>ip</code> is specified then ovn-controller will advertise + all IPs/MACs bindings that are local to the chassis. The applies to + VIFs and router ports. + </p> + <p> NOTE: this feature is experimental and may be subject to removal/change in the future. diff --git a/ovn-sb.ovsschema b/ovn-sb.ovsschema index f22141489..9fddf042d 100644 --- a/ovn-sb.ovsschema +++ b/ovn-sb.ovsschema @@ -1,7 +1,7 @@ { "name": "OVN_Southbound", - "version": "21.6.0", - "cksum": "1200327755 35814", + "version": "21.7.0", + "cksum": "1565005821 36671", "tables": { "SB_Global": { "columns": { @@ -695,6 +695,22 @@ "id": {"type": {"key": {"type": "integer", "minInteger": 0, "maxInteger": 32767}}}}, - "isRoot": true} + "isRoot": true}, + "Advertised_Mac_Binding": { + "columns": { + "datapath": {"type": {"key": {"type": "uuid", + "refTable": "Datapath_Binding", + "refType": "strong"}}}, + "logical_port": {"type": {"key": {"type": "uuid", + "refTable": "Port_Binding", + "refType": "strong"}}}, + "ip": {"type": "string"}, + "mac": {"type": "string"}, + "external_ids": { + "type": {"key": "string", "value": "string", + "min": 0, "max": "unlimited"}}}, + "indexes": [["datapath", "logical_port", + "ip", "mac"]], + "isRoot": true} } } diff --git a/ovn-sb.xml b/ovn-sb.xml index a781129b7..3eab5f998 100644 --- a/ovn-sb.xml +++ b/ovn-sb.xml @@ -5457,4 +5457,33 @@ tcp.flags = RST; <code>allow-established</code> ACL. </column> </table> + + <table name="Advertised_Mac_Binding"> + <p> + Each record represents an ip/mac binding the + <code>OVN_Northbound.Logical_Switch</code> is announcing outside + the network fabric if <code>EVPN</code> is enabled on the datapath. + </p> + + <column name="datapath"> + The datapath belonging to the + <code>OVN_Northbound.Logical_Switch</code> this type2 route belongs to. + </column> + + <column name="logical_port"> + This is the <ref table="Port_Binding"/> this type2 route belongs to. + </column> + + <column name="ip"> + Announced host route (e.g. 192.168.100.0/32). + </column> + + <column name="mac"> + Announced mac address. + </column> + + <column name="external_ids"> + See <em>External IDs</em> at the beginning of this document. + </column> + </table> </database> diff --git a/tests/ovn-northd.at b/tests/ovn-northd.at index 07fb57bd6..66b36ae10 100644 --- a/tests/ovn-northd.at +++ b/tests/ovn-northd.at @@ -4077,7 +4077,7 @@ check_engine_stats northd recompute nocompute check_engine_stats bfd recompute nocompute check_engine_stats routes recompute nocompute check_engine_stats lflow recompute nocompute -check_engine_stats northd_output norecompute compute +check_engine_stats northd_output recompute nocompute CHECK_NO_CHANGE_AFTER_RECOMPUTE check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats @@ -4093,7 +4093,7 @@ check_engine_stats northd recompute nocompute check_engine_stats bfd recompute nocompute check_engine_stats routes recompute nocompute check_engine_stats lflow recompute nocompute -check_engine_stats northd_output norecompute compute +check_engine_stats northd_output recompute nocompute CHECK_NO_CHANGE_AFTER_RECOMPUTE check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats @@ -4105,7 +4105,7 @@ check_engine_stats northd recompute nocompute check_engine_stats bfd recompute nocompute check_engine_stats route_policies recompute nocompute check_engine_stats lflow recompute nocompute -check_engine_stats northd_output norecompute compute +check_engine_stats northd_output recompute nocompute CHECK_NO_CHANGE_AFTER_RECOMPUTE check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats @@ -4140,7 +4140,7 @@ check_engine_stats northd recompute nocompute check_engine_stats bfd recompute nocompute check_engine_stats routes recompute nocompute check_engine_stats lflow recompute nocompute -check_engine_stats northd_output norecompute compute +check_engine_stats northd_output recompute compute CHECK_NO_CHANGE_AFTER_RECOMPUTE check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats @@ -4155,7 +4155,7 @@ check_engine_stats northd recompute nocompute check_engine_stats bfd recompute nocompute check_engine_stats route_policies recompute nocompute check_engine_stats lflow recompute nocompute -check_engine_stats northd_output norecompute compute +check_engine_stats northd_output recompute compute CHECK_NO_CHANGE_AFTER_RECOMPUTE check as northd ovn-appctl -t ovn-northd inc-engine/clear-stats @@ -18716,3 +18716,41 @@ AT_CHECK( AT_CLEANUP ]) + +OVN_FOR_EACH_NORTHD_NO_HV([ +AT_SETUP([LS EVPN Route Type2 sync]) +ovn_start ovn-northd + +AS_BOX([Create logical switch.]) +check ovn-nbctl ls-add ls-evpn \ + -- set logical_switch ls-evpn other_config:dynamic-routing-vni=10 \ + -- lsp-add ls-evpn vm1 \ + -- lsp-set-addresses vm1 "00:00:00:00:01:00 10.0.0.2" +check ovn-nbctl --wait=sb sync +check_row_count Advertised_Mac_Binding 0 +ls_evpn_uuid=$(fetch_column Datapath_Binding _uuid external_ids:name=ls-evpn) + +check ovn-nbctl --wait=sb set logical_switch ls-evpn other_config:dynamic-routing-redistribute=ip +wait_row_count Advertised_Mac_Binding 1 ip=10.0.0.2/32 datapath=$ls_evpn_uuid mac='00\:00\:00\:00\:01\:00' + +check ovn-nbctl lr-add lr \ + -- lrp-add lr lrp 00:00:00:00:01:01 10.0.0.1/24 \ + -- lsp-add ls-evpn ls-evpn-lrp \ + -- lsp-set-type ls-evpn-lrp router \ + -- lsp-set-addresses ls-evpn-lrp 00:00:00:00:01:01 \ + -- lsp-set-options ls-evpn-lrp router-port=lrp +check ovn-nbctl --wait=sb sync + +wait_row_count Advertised_Mac_Binding 1 ip=10.0.0.2/32 datapath=$ls_evpn_uuid mac='00\:00\:00\:00\:01\:00' +wait_row_count Advertised_Mac_Binding 1 ip=10.0.0.1/32 datapath=$ls_evpn_uuid mac='00\:00\:00\:00\:01\:01' +check_row_count Advertised_Mac_Binding 2 + +check ovn-nbctl --wait=sb lsp-del vm1 +wait_row_count Advertised_Mac_Binding 1 ip=10.0.0.1/32 datapath=$ls_evpn_uuid mac='00\:00\:00\:00\:01\:01' +check_row_count Advertised_Mac_Binding 1 + +check ovn-nbctl --wait=sb clear logical_switch ls-evpn other_config +wait_row_count Advertised_Mac_Binding 0 + +AT_CLEANUP +]) -- 2.51.0 _______________________________________________ dev mailing list [email protected] https://mail.openvswitch.org/mailman/listinfo/ovs-dev
