diff options
42 files changed, 793 insertions, 66 deletions
diff --git a/.clang-format b/.clang-format index 1b18323348..c51e0b3cec 100644 --- a/.clang-format +++ b/.clang-format @@ -11,6 +11,7 @@ AllowShortFunctionsOnASingleLine: false IndentCaseLabels: false AlignEscapedNewlinesLeft: false AlignTrailingComments: true +AlignConsecutiveMacros: AcrossComments AllowAllParametersOfDeclarationOnNextLine: false AlignAfterOpenBracket: true SpaceAfterCStyleCast: false diff --git a/bgpd/bgp_mac.c b/bgpd/bgp_mac.c index 980f351303..6272bdb884 100644 --- a/bgpd/bgp_mac.c +++ b/bgpd/bgp_mac.c @@ -360,7 +360,7 @@ bool bgp_mac_exist(const struct ethaddr *mac) return true; } -/* This API checks EVPN type-2 prefix and comapares +/* This API checks EVPN type-2 prefix and compares * mac against any of local assigned (SVIs) MAC * address. */ @@ -375,8 +375,6 @@ bool bgp_mac_entry_exists(const struct prefix *p) return false; return bgp_mac_exist(&p->u.prefix_evpn.macip_addr.mac); - - return true; } static void bgp_mac_show_mac_entry(struct hash_bucket *bucket, void *arg) diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 10fc3ecda4..7db110be93 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -872,6 +872,46 @@ static const struct route_map_rule_cmd route_match_ip_next_hop_type_free }; +/* `match source-protocol` */ +static enum route_map_cmd_result_t +route_match_source_protocol(void *rule, const struct prefix *prefix, + void *object) +{ + struct bgp_path_info *path = object; + int *protocol = rule; + + if (!path) + return RMAP_NOMATCH; + + if (path->type == *protocol) + return RMAP_MATCH; + + return RMAP_NOMATCH; +} + +static void *route_match_source_protocol_compile(const char *arg) +{ + int *protocol; + + protocol = XMALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(*protocol)); + *protocol = proto_name2num(arg); + + return protocol; +} + +static void route_match_source_protocol_free(void *rule) +{ + XFREE(MTYPE_ROUTE_MAP_COMPILED, rule); +} + +static const struct route_map_rule_cmd route_match_source_protocol_cmd = { + "source-protocol", + route_match_source_protocol, + route_match_source_protocol_compile, + route_match_source_protocol_free +}; + + /* `match ip route-source prefix-list PREFIX_LIST' */ static enum route_map_cmd_result_t @@ -7177,6 +7217,42 @@ DEFPY_YANG (match_rpki_extcommunity, return nb_cli_apply_changes(vty, NULL); } +DEFPY_YANG (match_source_protocol, + match_source_protocol_cmd, + "match source-protocol " FRR_REDIST_STR_ZEBRA "$proto", + MATCH_STR + "Match protocol via which the route was learnt\n" + FRR_REDIST_HELP_STR_ZEBRA) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-protocol']"; + char xpath_value[XPATH_MAXLEN]; + + nb_cli_enqueue_change(vty, xpath, NB_OP_CREATE, NULL); + snprintf(xpath_value, sizeof(xpath_value), + "%s/rmap-match-condition/frr-bgp-route-map:source-protocol", + xpath); + nb_cli_enqueue_change(vty, xpath_value, NB_OP_MODIFY, proto); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFPY_YANG (no_match_source_protocol, + no_match_source_protocol_cmd, + "no match source-protocol [" FRR_REDIST_STR_ZEBRA "]", + NO_STR + MATCH_STR + "Match protocol via which the route was learnt\n" + FRR_REDIST_HELP_STR_ZEBRA) +{ + const char *xpath = + "./match-condition[condition='frr-bgp-route-map:source-protocol']"; + + nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL); + + return nb_cli_apply_changes(vty, NULL); +} + /* Initialization of route map. */ void bgp_route_map_init(void) { @@ -7252,6 +7328,7 @@ void bgp_route_map_init(void) route_map_install_match(&route_match_ip_address_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_prefix_list_cmd); route_map_install_match(&route_match_ip_next_hop_type_cmd); + route_map_install_match(&route_match_source_protocol_cmd); route_map_install_match(&route_match_ip_route_source_prefix_list_cmd); route_map_install_match(&route_match_aspath_cmd); route_map_install_match(&route_match_community_cmd); @@ -7441,6 +7518,8 @@ void bgp_route_map_init(void) install_element(RMAP_NODE, &set_ipv6_nexthop_peer_cmd); install_element(RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd); install_element(RMAP_NODE, &match_rpki_extcommunity_cmd); + install_element(RMAP_NODE, &match_source_protocol_cmd); + install_element(RMAP_NODE, &no_match_source_protocol_cmd); #ifdef HAVE_SCRIPTING install_element(RMAP_NODE, &match_script_cmd); #endif diff --git a/bgpd/bgp_routemap_nb.c b/bgpd/bgp_routemap_nb.c index 6e8439cc26..282ebe9116 100644 --- a/bgpd/bgp_routemap_nb.c +++ b/bgpd/bgp_routemap_nb.c @@ -74,6 +74,13 @@ const struct frr_yang_module_info frr_bgp_route_map_info = { .destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_destroy, } }, + { + .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol", + .cbs = { + .modify = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify, + .destroy = lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy, + } + }, { .xpath = "/frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:peer-ipv4-address", .cbs = { diff --git a/bgpd/bgp_routemap_nb.h b/bgpd/bgp_routemap_nb.h index bcd1e837e8..7066fdb419 100644 --- a/bgpd/bgp_routemap_nb.h +++ b/bgpd/bgp_routemap_nb.h @@ -30,6 +30,10 @@ int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_m struct nb_cb_modify_args *args); int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_destroy( struct nb_cb_destroy_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify( + struct nb_cb_modify_args *args); +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy( + struct nb_cb_destroy_args *args); int lib_route_map_entry_match_condition_rmap_match_condition_probability_modify(struct nb_cb_modify_args *args); int lib_route_map_entry_match_condition_rmap_match_condition_probability_destroy(struct nb_cb_destroy_args *args); int lib_route_map_entry_match_condition_rmap_match_condition_source_vrf_modify(struct nb_cb_modify_args *args); diff --git a/bgpd/bgp_routemap_nb_config.c b/bgpd/bgp_routemap_nb_config.c index 938a5ec31b..02564b0004 100644 --- a/bgpd/bgp_routemap_nb_config.c +++ b/bgpd/bgp_routemap_nb_config.c @@ -369,6 +369,60 @@ lib_route_map_entry_match_condition_rmap_match_condition_rpki_destroy( /* * XPath: + * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:source-protocol + */ +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_modify( + struct nb_cb_modify_args *args) +{ + struct routemap_hook_context *rhc; + enum rmap_compile_rets ret; + const char *proto; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + /* Add configuration. */ + rhc = nb_running_get_entry(args->dnode, NULL, true); + proto = yang_dnode_get_string(args->dnode, NULL); + + /* Set destroy information. */ + rhc->rhc_mhook = bgp_route_match_delete; + rhc->rhc_rule = "source-protocol"; + rhc->rhc_event = RMAP_EVENT_MATCH_DELETED; + + ret = bgp_route_match_add(rhc->rhc_rmi, "source-protocol", + proto, RMAP_EVENT_MATCH_ADDED, + args->errmsg, args->errmsg_len); + + if (ret != RMAP_COMPILE_SUCCESS) { + rhc->rhc_mhook = NULL; + return NB_ERR_INCONSISTENCY; + } + } + + return NB_OK; +} + +int lib_route_map_entry_match_condition_rmap_match_condition_source_protocol_destroy( + struct nb_cb_destroy_args *args) +{ + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + return lib_route_map_entry_match_destroy(args); + } + + return NB_OK; +} + +/* + * XPath: * /frr-route-map:lib/route-map/entry/match-condition/rmap-match-condition/frr-bgp-route-map:rpki-extcommunity */ int lib_route_map_entry_match_condition_rmap_match_condition_rpki_extcommunity_modify( diff --git a/doc/developer/workflow.rst b/doc/developer/workflow.rst index f11fff5dee..28cf5d0ab1 100644 --- a/doc/developer/workflow.rst +++ b/doc/developer/workflow.rst @@ -537,7 +537,8 @@ Programming Languages, Tools and Libraries ========================================== The core of FRR is written in C (gcc or clang supported) and makes -use of GNU compiler extensions. A few non-essential scripts are +use of GNU compiler extensions. Additionally, the CLI generation +tool, `clippy`, requires Python. A few other non-essential scripts are implemented in Perl and Python. FRR requires the following tools to build distribution packages: automake, autoconf, texinfo, libtool and gawk and various libraries (i.e. libpam and libjson-c). diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 394a6987fc..f045ca239e 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -2032,6 +2032,13 @@ Capability Negotiation Disabled by default. +.. clicmd:: neighbor PEER aigp + + Send and receive AIGP attribute for this neighbor. This is valid only for + eBGP neighbors. + + Disabled by default. iBGP neighbors have this option enabled implicitly. + .. _bgp-as-path-access-lists: AS Path Access Lists diff --git a/doc/user/ripd.rst b/doc/user/ripd.rst index 67323e61f3..f9c7724302 100644 --- a/doc/user/ripd.rst +++ b/doc/user/ripd.rst @@ -159,6 +159,11 @@ RIP Configuration If `poisoned-reverse` is also set, the router sends the poisoned routes with highest metric back to the sending router. +.. clicmd:: allow-ecmp [1-MULTIPATH_NUM] + + Control how many ECMP paths RIP can inject for the same prefix. If specified + without a number, a maximum is taken (compiled with ``--enable-multipath``). + .. _rip-version-control: RIP Version Control diff --git a/doc/user/routemap.rst b/doc/user/routemap.rst index b7f5336564..bd19ae88e2 100644 --- a/doc/user/routemap.rst +++ b/doc/user/routemap.rst @@ -212,7 +212,7 @@ Route Map Match Command .. clicmd:: match source-protocol PROTOCOL_NAME - This is a ZEBRA specific match command. Matches the + This is a ZEBRA and BGP specific match command. Matches the originating protocol specified. .. clicmd:: match source-instance NUMBER diff --git a/lib/routemap.h b/lib/routemap.h index 9b2e18b4a7..7277744dc5 100644 --- a/lib/routemap.h +++ b/lib/routemap.h @@ -259,6 +259,8 @@ DECLARE_QOBJ_TYPE(route_map); (strmatch(C, "frr-zebra-route-map:ipv4-next-hop-prefix-length")) #define IS_MATCH_SRC_PROTO(C) \ (strmatch(C, "frr-zebra-route-map:source-protocol")) +#define IS_MATCH_BGP_SRC_PROTO(C) \ + (strmatch(C, "frr-bgp-route-map:source-protocol")) #define IS_MATCH_SRC_INSTANCE(C) \ (strmatch(C, "frr-zebra-route-map:source-instance")) /* BGP route-map match conditions */ diff --git a/lib/routemap_cli.c b/lib/routemap_cli.c index 419086c4c6..0ccc78e838 100644 --- a/lib/routemap_cli.c +++ b/lib/routemap_cli.c @@ -599,11 +599,14 @@ void route_map_condition_show(struct vty *vty, const struct lyd_node *dnode, yang_dnode_get_string( dnode, "./rmap-match-condition/frr-zebra-route-map:ipv4-prefix-length")); - } else if (IS_MATCH_SRC_PROTO(condition)) { + } else if (IS_MATCH_SRC_PROTO(condition) || + IS_MATCH_BGP_SRC_PROTO(condition)) { vty_out(vty, " match source-protocol %s\n", yang_dnode_get_string( dnode, - "./rmap-match-condition/frr-zebra-route-map:source-protocol")); + IS_MATCH_SRC_PROTO(condition) + ? "./rmap-match-condition/frr-zebra-route-map:source-protocol" + : "./rmap-match-condition/frr-bgp-route-map:source-protocol")); } else if (IS_MATCH_SRC_INSTANCE(condition)) { vty_out(vty, " match source-instance %s\n", yang_dnode_get_string( diff --git a/ospfd/ospf_abr.c b/ospfd/ospf_abr.c index ded520889f..28d526870b 100644 --- a/ospfd/ospf_abr.c +++ b/ospfd/ospf_abr.c @@ -66,9 +66,11 @@ static void ospf_area_range_add(struct ospf_area *area, apply_mask_ipv4(&p); rn = route_node_get(ranges, (struct prefix *)&p); - if (rn->info) + if (rn->info) { route_unlock_node(rn); - else + ospf_area_range_free(rn->info); + rn->info = range; + } else rn->info = range; } diff --git a/ospfd/ospf_apiserver.c b/ospfd/ospf_apiserver.c index 1aacc341aa..34e59c545b 100644 --- a/ospfd/ospf_apiserver.c +++ b/ospfd/ospf_apiserver.c @@ -1764,6 +1764,12 @@ int ospf_apiserver_originate1(struct ospf_lsa *lsa, struct ospf_lsa *old) * session. Dump it, but increment past it's seqnum. */ assert(!ospf_opaque_is_owned(old)); + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug( + "LSA[Type%d:%pI4]: OSPF API Server Originate LSA Old Seq: 0x%x Age: %d", + old->data->type, &old->data->id, + ntohl(old->data->ls_seqnum), + ntohl(old->data->ls_age)); if (IS_LSA_MAX_SEQ(old)) { flog_warn(EC_OSPF_LSA_INSTALL_FAILURE, "%s: old LSA at maxseq", __func__); @@ -1772,6 +1778,11 @@ int ospf_apiserver_originate1(struct ospf_lsa *lsa, struct ospf_lsa *old) lsa->data->ls_seqnum = lsa_seqnum_increment(old); ospf_discard_from_db(ospf, old->lsdb, old); } + if (IS_DEBUG_OSPF_CLIENT_API) + zlog_debug( + "LSA[Type%d:%pI4]: OSPF API Server Originate LSA New Seq: 0x%x Age: %d", + lsa->data->type, &lsa->data->id, + ntohl(lsa->data->ls_seqnum), ntohl(lsa->data->ls_age)); /* Install this LSA into LSDB. */ if (ospf_lsa_install(ospf, lsa->oi, lsa) == NULL) { @@ -1846,6 +1857,11 @@ struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa) ospf = ospf_lookup_by_vrf_id(VRF_DEFAULT); assert(ospf); + if (IS_DEBUG_OSPF(lsa, LSA_GENERATE)) { + zlog_debug("LSA[Type%d:%pI4]: OSPF API Server LSA Refresher", + lsa->data->type, &lsa->data->id); + } + apiserv = lookup_apiserver_by_lsa(lsa); if (!apiserv) { zlog_warn("%s: LSA[%s]: No apiserver?", __func__, @@ -1855,14 +1871,14 @@ struct ospf_lsa *ospf_apiserver_lsa_refresher(struct ospf_lsa *lsa) goto out; } - if (IS_LSA_MAXAGE(lsa)) { - ospf_opaque_lsa_flush_schedule(lsa); - goto out; - } - /* Check if updated version of LSA instance has already prepared. */ new = ospf_lsdb_lookup(&apiserv->reserve, lsa); if (!new) { + if (IS_LSA_MAXAGE(lsa)) { + ospf_opaque_lsa_flush_schedule(lsa); + goto out; + } + /* This is a periodic refresh, driven by core OSPF mechanism. */ new = ospf_apiserver_opaque_lsa_new(lsa->area, lsa->oi, lsa->data); diff --git a/ospfd/ospf_interface.c b/ospfd/ospf_interface.c index 5742ece1f7..f18aa399a4 100644 --- a/ospfd/ospf_interface.c +++ b/ospfd/ospf_interface.c @@ -101,6 +101,9 @@ int ospf_if_get_output_cost(struct ospf_interface *oi) cost = 1; else if (cost > 65535) cost = 65535; + + if (if_is_loopback(oi->ifp)) + cost = 0; } return cost; diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c index 452a3ba374..67f1faf8a9 100644 --- a/ospfd/ospf_lsa.c +++ b/ospfd/ospf_lsa.c @@ -608,7 +608,8 @@ static int lsa_link_loopback_set(struct stream **s, struct ospf_interface *oi) mask.s_addr = 0xffffffff; id.s_addr = oi->address->u.prefix4.s_addr; - return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, 0); + return link_info_set(s, id, mask, LSA_LINK_TYPE_STUB, 0, + oi->output_cost); } /* Describe Virtual Link. */ diff --git a/ospfd/ospf_opaque.c b/ospfd/ospf_opaque.c index c2b40af1c4..6894c6a009 100644 --- a/ospfd/ospf_opaque.c +++ b/ospfd/ospf_opaque.c @@ -2120,14 +2120,21 @@ void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, lsa->data->type, &lsa->data->id); /* - * Since these LSA entries are not yet installed into corresponding - * LSDB, just flush them without calling ospf_ls_maxage() afterward. + * Install the stale LSA into the Link State Database, add it to the + * MaxAge list, and flush it from the OSPF routing domain. For other + * LSA types, the installation is done in the refresh function. It is + * done inline here since the opaque refresh function is dynamically + * registered when opaque LSAs are originated (which is not the case + * for stale LSAs). */ lsa->data->ls_age = htons(OSPF_LSA_MAXAGE); + ospf_lsa_install( + top, (lsa->data->type == OSPF_OPAQUE_LINK_LSA) ? nbr->oi : NULL, + lsa); + ospf_lsa_maxage(top, lsa); + switch (lsa->data->type) { case OSPF_OPAQUE_LINK_LSA: - ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); - break; case OSPF_OPAQUE_AREA_LSA: ospf_flood_through_area(nbr->oi->area, NULL /*inbr*/, lsa); break; @@ -2139,7 +2146,6 @@ void ospf_opaque_self_originated_lsa_received(struct ospf_neighbor *nbr, __func__, lsa->data->type); return; } - ospf_lsa_discard(lsa); /* List "lsas" will be deleted by caller. */ } /*------------------------------------------------------------------------* diff --git a/ospfd/ospf_packet.c b/ospfd/ospf_packet.c index 552acfd6d3..fe94c34e4e 100644 --- a/ospfd/ospf_packet.c +++ b/ospfd/ospf_packet.c @@ -2031,7 +2031,7 @@ static void ospf_ls_upd(struct ospf *ospf, struct ip *iph, if (current == NULL) { if (IS_DEBUG_OSPF_EVENT) zlog_debug( - "LSA[%s]: Previously originated Opaque-LSA,not found in the LSDB.", + "LSA[%s]: Previously originated Opaque-LSA, not found in the LSDB.", dump_lsa_key(lsa)); SET_FLAG(lsa->flags, OSPF_LSA_SELF); diff --git a/ospfd/ospf_ti_lfa.c b/ospfd/ospf_ti_lfa.c index da9428aba6..07fc503947 100644 --- a/ospfd/ospf_ti_lfa.c +++ b/ospfd/ospf_ti_lfa.c @@ -1078,6 +1078,7 @@ void ospf_ti_lfa_free_p_spaces(struct ospf_area *area) q_spaces_fini(p_space->q_spaces); XFREE(MTYPE_OSPF_Q_SPACE, p_space->q_spaces); + XFREE(MTYPE_OSPF_P_SPACE, p_space); } p_spaces_fini(area->p_spaces); diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c index ac9fc4b1a8..6f45bb5d9e 100644 --- a/ripd/rip_cli.c +++ b/ripd/rip_cli.c @@ -85,14 +85,33 @@ void cli_show_router_rip(struct vty *vty, const struct lyd_node *dnode, /* * XPath: /frr-ripd:ripd/instance/allow-ecmp */ -DEFPY_YANG (rip_allow_ecmp, +DEFUN_YANG (rip_allow_ecmp, rip_allow_ecmp_cmd, - "[no] allow-ecmp", + "allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", + "Allow Equal Cost MultiPath\n" + "Number of paths\n") +{ + int idx_number = 1; + char mpaths[3] = {}; + uint32_t paths = MULTIPATH_NUM; + + if (argv[idx_number]) + paths = strtol(argv[idx_number]->arg, NULL, 10); + snprintf(mpaths, sizeof(mpaths), "%u", paths); + + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, mpaths); + + return nb_cli_apply_changes(vty, NULL); +} + +DEFUN_YANG (no_rip_allow_ecmp, + no_rip_allow_ecmp_cmd, + "no allow-ecmp [" CMD_RANGE_STR(1, MULTIPATH_NUM) "]", NO_STR - "Allow Equal Cost MultiPath\n") + "Allow Equal Cost MultiPath\n" + "Number of paths\n") { - nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, - no ? "false" : "true"); + nb_cli_enqueue_change(vty, "./allow-ecmp", NB_OP_MODIFY, 0); return nb_cli_apply_changes(vty, NULL); } @@ -100,10 +119,14 @@ DEFPY_YANG (rip_allow_ecmp, void cli_show_rip_allow_ecmp(struct vty *vty, const struct lyd_node *dnode, bool show_defaults) { - if (!yang_dnode_get_bool(dnode, NULL)) - vty_out(vty, " no"); + uint8_t paths; + + paths = yang_dnode_get_uint8(dnode, NULL); - vty_out(vty, " allow-ecmp\n"); + if (!paths) + vty_out(vty, " no allow-ecmp\n"); + else + vty_out(vty, " allow-ecmp %d\n", paths); } /* @@ -1156,6 +1179,7 @@ void rip_cli_init(void) install_element(RIP_NODE, &rip_no_distribute_list_cmd); install_element(RIP_NODE, &rip_allow_ecmp_cmd); + install_element(RIP_NODE, &no_rip_allow_ecmp_cmd); install_element(RIP_NODE, &rip_default_information_originate_cmd); install_element(RIP_NODE, &rip_default_metric_cmd); install_element(RIP_NODE, &no_rip_default_metric_cmd); diff --git a/ripd/rip_nb_config.c b/ripd/rip_nb_config.c index 5c7bd0fb86..1117ec9ff7 100644 --- a/ripd/rip_nb_config.c +++ b/ripd/rip_nb_config.c @@ -99,9 +99,13 @@ int ripd_instance_allow_ecmp_modify(struct nb_cb_modify_args *args) return NB_OK; rip = nb_running_get_entry(args->dnode, NULL, true); - rip->ecmp = yang_dnode_get_bool(args->dnode, NULL); - if (!rip->ecmp) + rip->ecmp = yang_dnode_get_uint8(args->dnode, NULL); + if (!rip->ecmp) { rip_ecmp_disable(rip); + return NB_OK; + } + + rip_ecmp_change(rip); return NB_OK; } diff --git a/ripd/rip_zebra.c b/ripd/rip_zebra.c index 698dcb982d..834c7d2d65 100644 --- a/ripd/rip_zebra.c +++ b/ripd/rip_zebra.c @@ -20,6 +20,7 @@ /* All information about zebra. */ struct zclient *zclient = NULL; +uint32_t zebra_ecmp_count = MULTIPATH_NUM; /* Send ECMP routes to zebra. */ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp, @@ -30,7 +31,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp, struct zapi_nexthop *api_nh; struct listnode *listnode = NULL; struct rip_info *rinfo = NULL; - int count = 0; + uint32_t count = 0; memset(&api, 0, sizeof(api)); api.vrf_id = rip->vrf->vrf_id; @@ -39,7 +40,7 @@ static void rip_zebra_ipv4_send(struct rip *rip, struct route_node *rp, SET_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP); for (ALL_LIST_ELEMENTS_RO(list, listnode, rinfo)) { - if (count >= MULTIPATH_NUM) + if (count >= zebra_ecmp_count) break; api_nh = &api.nexthops[count]; api_nh->vrf_id = rip->vrf->vrf_id; @@ -227,6 +228,11 @@ zclient_handler *const rip_handlers[] = { [ZEBRA_REDISTRIBUTE_ROUTE_DEL] = rip_zebra_read_route, }; +static void rip_zebra_capabilities(struct zclient_capabilities *cap) +{ + zebra_ecmp_count = MIN(cap->ecmp, zebra_ecmp_count); +} + void rip_zclient_init(struct event_loop *master) { /* Set default value to the zebra client structure. */ @@ -234,6 +240,7 @@ void rip_zclient_init(struct event_loop *master) array_size(rip_handlers)); zclient_init(zclient, ZEBRA_ROUTE_RIP, 0, &ripd_privs); zclient->zebra_connected = rip_zebra_connected; + zclient->zebra_capabilities = rip_zebra_capabilities; } void rip_zclient_stop(void) diff --git a/ripd/ripd.c b/ripd/ripd.c index 339b033fb1..04a8cad560 100644 --- a/ripd/ripd.c +++ b/ripd/ripd.c @@ -156,7 +156,10 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new) { struct route_node *rp = rinfo_new->rp; struct rip_info *rinfo = NULL; + struct rip_info *rinfo_exist = NULL; struct list *list = NULL; + struct listnode *node = NULL; + struct listnode *nnode = NULL; if (rp->info == NULL) rp->info = list_new(); @@ -167,6 +170,33 @@ struct rip_info *rip_ecmp_add(struct rip *rip, struct rip_info *rinfo_new) if (listcount(list) && !rip->ecmp) return NULL; + /* Add or replace an existing ECMP path with lower neighbor IP */ + if (listcount(list) && listcount(list) >= rip->ecmp) { + struct rip_info *from_highest = NULL; + + /* Find the rip_info struct that has the highest nexthop IP */ + for (ALL_LIST_ELEMENTS(list, node, nnode, rinfo_exist)) + if (!from_highest || + (from_highest && + IPV4_ADDR_CMP(&rinfo_exist->from, + &from_highest->from) > 0)) { + from_highest = rinfo_exist; + } + + /* If we have a route in ECMP group, delete the old + * one that has a higher next-hop address. Lower IP is + * preferred. + */ + if (rip->ecmp > 1 && from_highest && + IPV4_ADDR_CMP(&from_highest->from, &rinfo_new->from) > 0) { + rip_ecmp_delete(rip, from_highest); + goto add_or_replace; + } + + return NULL; + } + +add_or_replace: rinfo = rip_info_new(); memcpy(rinfo, rinfo_new, sizeof(struct rip_info)); listnode_add(list, rinfo); @@ -2632,6 +2662,36 @@ struct rip *rip_lookup_by_vrf_name(const char *vrf_name) return RB_FIND(rip_instance_head, &rip_instances, &rip); } +/* Update ECMP routes to zebra when `allow-ecmp` changed. */ +void rip_ecmp_change(struct rip *rip) +{ + struct route_node *rp; + struct rip_info *rinfo; + struct list *list; + struct listnode *node, *nextnode; + + for (rp = route_top(rip->table); rp; rp = route_next(rp)) { + list = rp->info; + if (list && listcount(list) > 1) { + while (listcount(list) > rip->ecmp) { + struct rip_info *from_highest = NULL; + + for (ALL_LIST_ELEMENTS(list, node, nextnode, + rinfo)) { + if (!from_highest || + (from_highest && + IPV4_ADDR_CMP( + &rinfo->from, + &from_highest->from) > 0)) + from_highest = rinfo; + } + + rip_ecmp_delete(rip, from_highest); + } + } + } +} + /* Create new RIP instance and set it to global variable. */ struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket) { @@ -2641,7 +2701,7 @@ struct rip *rip_create(const char *vrf_name, struct vrf *vrf, int socket) rip->vrf_name = XSTRDUP(MTYPE_RIP_VRF_NAME, vrf_name); /* Set initial value. */ - rip->ecmp = yang_get_default_bool("%s/allow-ecmp", RIP_INSTANCE); + rip->ecmp = yang_get_default_uint8("%s/allow-ecmp", RIP_INSTANCE); rip->default_metric = yang_get_default_uint8("%s/default-metric", RIP_INSTANCE); rip->distance = diff --git a/ripd/ripd.h b/ripd/ripd.h index bba3c28069..2db1e5a6b0 100644 --- a/ripd/ripd.h +++ b/ripd/ripd.h @@ -141,7 +141,7 @@ struct rip { struct route_table *distance_table; /* RIP ECMP flag */ - bool ecmp; + uint8_t ecmp; /* Are we in passive-interface default mode? */ bool passive_default; @@ -537,4 +537,6 @@ extern struct event_loop *master; DECLARE_HOOK(rip_ifaddr_add, (struct connected * ifc), (ifc)); DECLARE_HOOK(rip_ifaddr_del, (struct connected * ifc), (ifc)); +extern void rip_ecmp_change(struct rip *rip); + #endif /* _ZEBRA_RIP_H */ diff --git a/tests/topotests/bgp_route_map_match_source_protocol/__init__.py b/tests/topotests/bgp_route_map_match_source_protocol/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/__init__.py diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf new file mode 100644 index 0000000000..7c3efeea4b --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r1/frr.conf @@ -0,0 +1,32 @@ +! +interface lo + ip address 172.16.255.1/32 +! +interface r1-eth0 + ip address 192.168.1.1/24 +! +interface r1-eth1 + ip address 192.168.2.1/24 +! +ip route 10.10.10.10/32 192.168.2.2 +! +router bgp 65001 + no bgp ebgp-requires-policy + neighbor 192.168.1.2 remote-as external + neighbor 192.168.1.2 timers 1 3 + neighbor 192.168.1.2 timers connect 1 + neighbor 192.168.2.2 remote-as external + neighbor 192.168.2.2 timers 1 3 + neighbor 192.168.2.2 timers connect 1 + address-family ipv4 + redistribute connected + redistribute static + neighbor 192.168.1.2 route-map r2 out + neighbor 192.168.2.2 route-map r3 out + exit-address-family +! +route-map r2 permit 10 + match source-protocol static +route-map r3 permit 10 + match source-protocol connected +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf new file mode 100644 index 0000000000..7213975cc0 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r2/frr.conf @@ -0,0 +1,10 @@ +! +interface r2-eth0 + ip address 192.168.1.2/24 +! +router bgp 65002 + no bgp ebgp-requires-policy + neighbor 192.168.1.1 remote-as external + neighbor 192.168.1.1 timers 1 3 + neighbor 192.168.1.1 timers connect 1 +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf new file mode 100644 index 0000000000..4a1d830b0a --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/r3/frr.conf @@ -0,0 +1,10 @@ +! +interface r3-eth0 + ip address 192.168.2.2/24 +! +router bgp 65003 + no bgp ebgp-requires-policy + neighbor 192.168.2.1 remote-as external + neighbor 192.168.2.1 timers 1 3 + neighbor 192.168.2.1 timers connect 1 +! diff --git a/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py new file mode 100644 index 0000000000..2828796405 --- /dev/null +++ b/tests/topotests/bgp_route_map_match_source_protocol/test_bgp_route_map_match_source_protocol.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + +# +# Copyright (c) 2023 by +# Donatas Abraitis <donatas@opensourcerouting.org> +# + +""" +Test if r1 can announce only static routes to r2, and only connected +routes to r3 using `match source-protocol` with route-maps. +""" + +import os +import sys +import json +import pytest +import functools + +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) + +# pylint: disable=C0413 +from lib import topotest +from lib.topogen import Topogen, TopoRouter, get_topogen + +pytestmark = [pytest.mark.bgpd] + + +def build_topo(tgen): + for routern in range(1, 4): + tgen.add_router("r{}".format(routern)) + + switch = tgen.add_switch("s1") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r3"]) + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + router_list = tgen.routers() + + for i, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname))) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def test_bgp_route_map_match_source_protocol(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + def _bgp_check_advertised_routes_r2(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.1.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "10.10.10.10/32": { + "valid": True, + } + }, + "totalPrefixCounter": 1, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes_r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to filter routes by source-protocol for r2" + + def _bgp_check_advertised_routes_r3(): + output = json.loads( + tgen.gears["r1"].vtysh_cmd( + "show bgp ipv4 unicast neighbors 192.168.2.2 advertised-routes json" + ) + ) + expected = { + "advertisedRoutes": { + "192.168.1.0/24": { + "valid": True, + }, + "192.168.2.0/24": { + "valid": True, + }, + "172.16.255.1/32": { + "valid": True, + }, + }, + "totalPrefixCounter": 3, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_check_advertised_routes_r3) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=1) + assert result is None, "Failed to filter routes by source-protocol for r3" + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/lib/micronet_compat.py b/tests/topotests/lib/micronet_compat.py index c5c2adc545..d648a120ab 100644 --- a/tests/topotests/lib/micronet_compat.py +++ b/tests/topotests/lib/micronet_compat.py @@ -273,7 +273,7 @@ ff02::2\tip6-allrouters shellopt = self.cfgopt.get_option_list("--shell") if "all" in shellopt or "." in shellopt: - self.run_in_window("bash") + self.run_in_window("bash", title="munet") # This is expected by newer munet CLI code self.config_dirname = "" diff --git a/tests/topotests/lib/ospf.py b/tests/topotests/lib/ospf.py index dad87440bc..ffe81fbd99 100644 --- a/tests/topotests/lib/ospf.py +++ b/tests/topotests/lib/ospf.py @@ -302,7 +302,6 @@ def __create_ospf_global(tgen, input_dict, router, build, load_config, ospf): # ospf gr information gr_data = ospf_data.setdefault("graceful-restart", {}) if gr_data: - if "opaque" in gr_data and gr_data["opaque"]: cmd = "capability opaque" if gr_data.setdefault("delete", False): @@ -710,6 +709,7 @@ def verify_ospf_neighbor( else: data_ip = topo["routers"][ospf_nbr]["links"] data_rid = topo["routers"][ospf_nbr]["ospf"]["router_id"] + logger.info("ospf neighbor %s: router-id: %s", router, data_rid) if ospf_nbr in data_ip: nbr_details = nbr_data[ospf_nbr] elif lan: @@ -728,8 +728,10 @@ def verify_ospf_neighbor( try: nh_state = show_ospf_json[nbr_rid][0]["nbrState"].split("/")[0] except KeyError: - errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format( - router, nbr_rid, ospf_nbr + errormsg = ( + "[DUT: {}] missing OSPF neighbor {} with router-id {}".format( + router, ospf_nbr, nbr_rid + ) ) return errormsg @@ -843,7 +845,6 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False) return errormsg for ospf_nbr, nbr_data in ospf_nbr_list.items(): - try: data_ip = data_rid = topo["routers"][ospf_nbr]["ospf6"]["router_id"] except KeyError: @@ -914,7 +915,6 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False) return errormsg continue else: - for router, rnode in tgen.routers().items(): if "ospf6" not in topo["routers"][router]: continue @@ -945,7 +945,7 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False) data_ip = data_rid = topo["routers"][nbr_data["nbr"]]["ospf6"][ "router_id" ] - + logger.info("ospf neighbor %s: router-id: %s", ospf_nbr, data_rid) if ospf_nbr in data_ip: nbr_details = nbr_data[ospf_nbr] elif lan: @@ -968,8 +968,10 @@ def verify_ospf6_neighbor(tgen, topo=None, dut=None, input_dict=None, lan=False) nh_state = get_index_val.get(neighbor_ip)["state"] intf_state = get_index_val.get(neighbor_ip)["ifState"] except TypeError: - errormsg = "[DUT: {}] OSPF peer {} missing,from " "{} ".format( - router, nbr_rid, ospf_nbr + errormsg = ( + "[DUT: {}] missing OSPF neighbor {} with router-id {}".format( + router, ospf_nbr, nbr_rid + ) ) return errormsg @@ -1761,7 +1763,6 @@ def verify_ospf6_rib( continue if st_rt in ospf_rib_json: - st_found = True found_routes.append(st_rt) diff --git a/tests/topotests/munet/base.py b/tests/topotests/munet/base.py index eb4b088442..c6ae70e09b 100644 --- a/tests/topotests/munet/base.py +++ b/tests/topotests/munet/base.py @@ -1241,9 +1241,13 @@ class Commander: # pylint: disable=R0904 # XXX not appropriate for ssh cmd = ["sudo", "-Eu", os.environ["SUDO_USER"]] + cmd - if not isinstance(nscmd, str): - nscmd = shlex.join(nscmd) - cmd.append(nscmd) + if title: + cmd.append("-t") + cmd.append(title) + + if isinstance(nscmd, str): + nscmd = shlex.split(nscmd) + cmd.extend(nscmd) elif "DISPLAY" in os.environ: cmd = [get_exec_path_host("xterm")] if "SUDO_USER" in os.environ: diff --git a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py index 9d7a15833c..cf7d95b65a 100644 --- a/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py +++ b/tests/topotests/ospf_basic_functionality/test_ospf_asbr_summary_topo1.py @@ -84,8 +84,8 @@ SUMMARY = {"ipv4": ["11.0.0.0/8", "12.0.0.0/8", "11.0.0.0/24"]} """ TOPOOLOGY = Please view in a fixed-width font such as Courier. - +---+ A0 +---+ - +R1 +------------+R2 | + +---+ A0 +---+ + |R1 +------------+R2 | +-+-+- +--++ | -- -- | | -- A0 -- | @@ -94,8 +94,8 @@ TOPOOLOGY = | -- -- | | -- -- | +-+-+- +-+-+ - +R0 +-------------+R3 | - +---+ A0 +---+ + |R0 +-------------+R3 | + +---+ A0 +---+ TESTCASES = 1. OSPF summarisation functionality. @@ -977,7 +977,7 @@ def test_ospf_type5_summary_tc42_p0(request): ip = topo["routers"]["r0"]["links"]["r3"]["ipv4"] - ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network) + ip_net = str(ipaddress.ip_interface("{}".format(ip)).network) ospf_summ_r1 = { "r0": { "ospf": {"summary-address": [{"prefix": ip_net.split("/")[0], "mask": "8"}]} @@ -1519,7 +1519,7 @@ def test_ospf_type5_summary_tc45_p0(request): step("Repeat steps 1 to 10 of summarisation in non Back bone area.") reset_config_on_routers(tgen) - step("Change the area id on the interface on R0") + step("Change the area id on the interface on R0 to R1 from 0.0.0.0 to 0.0.0.1") input_dict = { "r0": { "links": { @@ -1549,7 +1549,7 @@ def test_ospf_type5_summary_tc45_p0(request): result = create_interfaces_cfg(tgen, input_dict) assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) - step("Change the area id on the interface ") + step("Change the area id on the interface on R1 to R0 from 0.0.0.0 to 0.0.0.1") input_dict = { "r1": { "links": { @@ -1579,6 +1579,10 @@ def test_ospf_type5_summary_tc45_p0(request): result = create_interfaces_cfg(tgen, input_dict) assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + # clear neighbor state on both routers to avoid stale state + tgen.net["r0"].cmd("clear ip ospf neighbor") + tgen.net["r1"].cmd("clear ip ospf neighbor") + ospf_covergence = verify_ospf_neighbor(tgen, topo) assert ospf_covergence is True, "setup_module :Failed \n Error {}".format( ospf_covergence diff --git a/tests/topotests/ospfapi/test_ospf_clientapi.py b/tests/topotests/ospfapi/test_ospf_clientapi.py index b01c96226e..2e8bea2651 100644 --- a/tests/topotests/ospfapi/test_ospf_clientapi.py +++ b/tests/topotests/ospfapi/test_ospf_clientapi.py @@ -20,7 +20,15 @@ from datetime import datetime, timedelta import pytest -from lib.common_config import retry, run_frr_cmd, step +from lib.common_config import ( + retry, + run_frr_cmd, + step, + kill_router_daemons, + start_router_daemons, + shutdown_bringup_interface, +) + from lib.micronet import Timeout, comm_error from lib.topogen import Topogen, TopoRouter from lib.topotest import interface_set_status, json_cmp @@ -936,6 +944,191 @@ def test_ospf_opaque_delete_data3(tgen): _test_opaque_add_del(tgen, apibin) +def _test_opaque_add_restart_add(tgen, apibin): + "Test adding an opaque LSA and then restarting ospfd" + + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] + + p = None + pread = None + # Log to our stdin, stderr + pout = open(os.path.join(r1.net.logdir, "r1/add-del.log"), "a+") + try: + step("reachable: check for add notification") + pread = r2.popen( + ["/usr/bin/timeout", "120", apibin, "-v", "--logtag=READER", "wait,120"], + encoding=None, # don't buffer + stdin=subprocess.DEVNULL, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + p = r1.popen( + [ + apibin, + "-v", + "add,10,1.2.3.4,231,1", + "add,10,1.2.3.4,231,1,feedaceebeef", + "wait, 5", + "add,10,1.2.3.4,231,1,feedaceedeadbeef", + "wait, 5", + "add,10,1.2.3.4,231,1,feedaceebaddbeef", + "wait, 5", + ] + ) + add_input_dict = { + "areas": { + "1.2.3.4": { + "areaLocalOpaqueLsa": [ + { + "lsId": "231.0.0.1", + "advertisedRouter": "1.0.0.0", + "sequenceNumber": "80000004", + "checksum": "3128", + }, + ], + "areaLocalOpaqueLsaCount": 1, + }, + }, + } + step("Check for add LSAs") + json_cmd = "show ip ospf da json" + assert verify_ospf_database(tgen, r1, add_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_input_dict, json_cmd) is None + + step("Shutdown the interface on r1 to isolate it for r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", False) + + time.sleep(2) + step("Reset the client") + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + step("Kill ospfd on R1") + kill_router_daemons(tgen, "r1", ["ospfd"]) + time.sleep(2) + + step("Bring ospfd on R1 back up") + start_router_daemons(tgen, "r1", ["ospfd"]) + + p = r1.popen( + [ + apibin, + "-v", + "add,10,1.2.3.4,231,1", + "add,10,1.2.3.4,231,1,feedaceecafebeef", + "wait, 5", + ] + ) + + step("Bring the interface on r1 back up for connection to r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", True) + + step("Verify area opaque LSA refresh") + json_cmd = "show ip ospf da opaque-area json" + add_detail_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000005", + "checksum": "a87e", + "length": 28, + "opaqueDataLength": 8, + }, + ], + }, + }, + } + assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None + + step("Shutdown the interface on r1 to isolate it for r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", False) + + time.sleep(2) + step("Reset the client") + p.send_signal(signal.SIGINT) + time.sleep(2) + p.wait() + p = None + + step("Kill ospfd on R1") + kill_router_daemons(tgen, "r1", ["ospfd"]) + time.sleep(2) + + step("Bring ospfd on R1 back up") + start_router_daemons(tgen, "r1", ["ospfd"]) + + step("Bring the interface on r1 back up for connection to r2") + shutdown_bringup_interface(tgen, "r1", "r1-eth0", True) + + step("Verify area opaque LSA Purging") + json_cmd = "show ip ospf da opaque-area json" + add_detail_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [ + { + "lsaAge": 3600, + "linkStateId": "231.0.0.1", + "advertisingRouter": "1.0.0.0", + "lsaSeqNumber": "80000005", + "checksum": "a87e", + "length": 28, + "opaqueDataLength": 8, + }, + ], + }, + }, + } + assert verify_ospf_database(tgen, r1, add_detail_input_dict, json_cmd) is None + assert verify_ospf_database(tgen, r2, add_detail_input_dict, json_cmd) is None + step("Verify Area Opaque LSA removal after timeout (60 seconds)") + time.sleep(60) + json_cmd = "show ip ospf da opaque-area json" + timeout_detail_input_dict = { + "areaLocalOpaqueLsa": { + "areas": { + "1.2.3.4": [], + }, + }, + } + assert ( + verify_ospf_database(tgen, r1, timeout_detail_input_dict, json_cmd) is None + ) + assert ( + verify_ospf_database(tgen, r2, timeout_detail_input_dict, json_cmd) is None + ) + + except Exception: + if p: + p.terminate() + if p.wait(): + comm_error(p) + p = None + raise + finally: + if pread: + pread.terminate() + pread.wait() + if p: + p.terminate() + p.wait() + + +@pytest.mark.parametrize("tgen", [2], indirect=True) +def test_ospf_opaque_restart(tgen): + apibin = os.path.join(CLIENTDIR, "ospfclient.py") + rc, o, e = tgen.gears["r2"].net.cmd_status([apibin, "--help"]) + logging.debug("%s --help: rc: %s stdout: '%s' stderr: '%s'", apibin, rc, o, e) + _test_opaque_add_restart_add(tgen, apibin) + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py index b0e56e619a..8cc0ed6090 100644 --- a/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py +++ b/tests/topotests/ospfv3_basic_functionality/test_ospfv3_asbr_summary_topo1.py @@ -565,7 +565,7 @@ def test_ospfv3_type5_summary_tc42_p0(request): ip = topo["routers"]["r0"]["links"]["r3"]["ipv6"] - ip_net = str(ipaddress.ip_interface(u"{}".format(ip)).network) + ip_net = str(ipaddress.ip_interface("{}".format(ip)).network) ospf_summ_r1 = { "r0": { "ospf6": { @@ -1428,6 +1428,10 @@ def ospfv3_type5_summary_tc45_p0(request): result = create_interfaces_cfg(tgen, input_dict) assert result is True, "Testcase {} :Failed \n Error: {}".format(tc_name, result) + # restart interface state machine on both routers to avoid stale state + tgen.net["r0"].cmd("clear ipv6 ospf6 interface") + tgen.net["r1"].cmd("clear ipv6 ospf6 interface") + ospf_covergence = verify_ospf6_neighbor(tgen, topo) assert ospf_covergence is True, "Testcase {} :Failed \n Error: {}".format( tc_name, ospf_covergence diff --git a/tests/topotests/rip_allow_ecmp/r4/frr.conf b/tests/topotests/rip_allow_ecmp/r4/frr.conf new file mode 100644 index 0000000000..995c2beca9 --- /dev/null +++ b/tests/topotests/rip_allow_ecmp/r4/frr.conf @@ -0,0 +1,13 @@ +! +int lo + ip address 10.10.10.1/32 +! +int r4-eth0 + ip address 192.168.1.4/24 +! +router rip + network 192.168.1.0/24 + network 10.10.10.1/32 + timers basic 5 15 10 +exit + diff --git a/tests/topotests/rip_allow_ecmp/r5/frr.conf b/tests/topotests/rip_allow_ecmp/r5/frr.conf new file mode 100644 index 0000000000..57a06ec0e3 --- /dev/null +++ b/tests/topotests/rip_allow_ecmp/r5/frr.conf @@ -0,0 +1,13 @@ +! +int lo + ip address 10.10.10.1/32 +! +int r5-eth0 + ip address 192.168.1.5/24 +! +router rip + network 192.168.1.0/24 + network 10.10.10.1/32 + timers basic 5 15 10 +exit + diff --git a/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py index acc0aea9e8..7d958fd496 100644 --- a/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py +++ b/tests/topotests/rip_allow_ecmp/test_rip_allow_ecmp.py @@ -21,12 +21,13 @@ sys.path.append(os.path.join(CWD, "../")) # pylint: disable=C0413 from lib import topotest from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step pytestmark = [pytest.mark.ripd] def setup_module(mod): - topodef = {"s1": ("r1", "r2", "r3")} + topodef = {"s1": ("r1", "r2", "r3", "r4", "r5")} tgen = Topogen(topodef, mod.__name__) tgen.start_topology() @@ -102,11 +103,13 @@ def test_rip_allow_ecmp(): _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip rip`" - def _show_routes(): + def _show_routes(nh_num): output = json.loads(r1.vtysh_cmd("show ip route json")) expected = { "10.10.10.1/32": [ { + "internalNextHopNum": nh_num, + "internalNextHopActiveNum": nh_num, "nexthops": [ { "ip": "192.168.1.2", @@ -116,15 +119,36 @@ def test_rip_allow_ecmp(): "ip": "192.168.1.3", "active": True, }, - ] + ], } ] } return topotest.json_cmp(output, expected) - test_func = functools.partial(_show_routes) + test_func = functools.partial(_show_routes, 4) _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) - assert result is None, "Can't see 10.10.10.1/32 as multipath in `show ip route`" + assert result is None, "Can't see 10.10.10.1/32 as multipath (4) in `show ip route`" + + step( + "Configure allow-ecmp 2, ECMP group routes SHOULD have next-hops with the lowest IPs" + ) + r1.vtysh_cmd( + """ + configure terminal + router rip + allow-ecmp 2 + """ + ) + + test_func = functools.partial(_show_rip_routes) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert ( + result is None + ), "Can't see 10.10.10.1/32 as ECMP with the lowest next-hop IPs" + + test_func = functools.partial(_show_routes, 2) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=1) + assert result is None, "Can't see 10.10.10.1/32 as multipath (2) in `show ip route`" if __name__ == "__main__": diff --git a/yang/example/ripd.json b/yang/example/ripd.json index 00040622e5..799f46a6dc 100644 --- a/yang/example/ripd.json +++ b/yang/example/ripd.json @@ -23,7 +23,7 @@ "instance": [ { "vrf": "default", - "allow-ecmp": "true", + "allow-ecmp": 1, "distance": { "source": [ { diff --git a/yang/example/ripd.xml b/yang/example/ripd.xml index 2feddde2d8..dad83619ce 100644 --- a/yang/example/ripd.xml +++ b/yang/example/ripd.xml @@ -19,7 +19,7 @@ <ripd xmlns="http://frrouting.org/yang/ripd"> <instance> <vrf>default</vrf> - <allow-ecmp>true</allow-ecmp> + <allow-ecmp>1</allow-ecmp> <static-route>10.0.1.0/24</static-route> <distance> <source> diff --git a/yang/frr-bgp-route-map.yang b/yang/frr-bgp-route-map.yang index 23e5b0227c..b557cabb22 100644 --- a/yang/frr-bgp-route-map.yang +++ b/yang/frr-bgp-route-map.yang @@ -23,6 +23,10 @@ module frr-bgp-route-map { prefix rt-types; } + import frr-route-types { + prefix frr-route-types; + } + organization "Free Range Routing"; contact @@ -168,6 +172,12 @@ module frr-bgp-route-map { "Match IPv6 next hop address"; } + identity source-protocol { + base frr-route-map:rmap-match-type; + description + "Match protocol via which the route was learnt"; + } + identity distance { base frr-route-map:rmap-set-type; description @@ -759,6 +769,13 @@ module frr-bgp-route-map { "IPv6 address"; } } + + case source-protocol { + when "derived-from-or-self(../frr-route-map:condition, 'frr-bgp-route-map:source-protocol')"; + leaf source-protocol { + type frr-route-types:frr-route-types; + } + } } augment "/frr-route-map:lib/frr-route-map:route-map/frr-route-map:entry/frr-route-map:set-action/frr-route-map:rmap-set-action/frr-route-map:set-action" { diff --git a/yang/frr-ripd.yang b/yang/frr-ripd.yang index a4bf50d958..5f85a4cabc 100644 --- a/yang/frr-ripd.yang +++ b/yang/frr-ripd.yang @@ -119,8 +119,8 @@ module frr-ripd { "VRF name."; } leaf allow-ecmp { - type boolean; - default "false"; + type uint8; + default 0; description "Allow equal-cost multi-path."; } |
