diff options
131 files changed, 3509 insertions, 385 deletions
diff --git a/bgpd/bgp_packet.c b/bgpd/bgp_packet.c index 37de42d0e3..54cd1250cb 100644 --- a/bgpd/bgp_packet.c +++ b/bgpd/bgp_packet.c @@ -2842,6 +2842,14 @@ static int bgp_route_refresh_receive(struct peer_connection *connection, prefix_bgp_orf_remove_all(afi, name); peer->orf_plist[afi][safi] = prefix_bgp_orf_lookup(afi, name); + + paf = peer_af_find(peer, afi, safi); + if (paf && paf->subgroup) { + updgrp = PAF_UPDGRP(paf); + updgrp_peer = UPDGRP_PEER(updgrp); + updgrp_peer->orf_plist[afi][safi] = + peer->orf_plist[afi][safi]; + } break; } diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 479a7dfed1..3bbbdee161 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -3211,21 +3211,6 @@ void bgp_best_selection(struct bgp *bgp, struct bgp_dest *dest, bgp_path_info_unset_flag(dest, look_thru, BGP_PATH_DMED_CHECK); - if (CHECK_FLAG(bgp->flags, BGP_FLAG_DETERMINISTIC_MED) && - (!CHECK_FLAG(look_thru->flags, - BGP_PATH_DMED_SELECTED))) { - bgp_path_info_unset_flag(dest, look_thru, - BGP_PATH_DMED_CHECK); - if (debug) - zlog_debug("%s: %pBD(%s) pi %s dmed", - __func__, dest, - bgp->name_pretty, - look_thru->peer->host); - - worse = look_thru; - continue; - } - reason = dest->reason; any_comparisons = true; if (bgp_path_info_cmp(bgp, first, look_thru, &paths_eq, diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index eab059abea..cf1ea78acf 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -3412,12 +3412,28 @@ static struct bgp *bgp_create(as_t *as, const char *name, afi_t afi; safi_t safi; - if (hidden) { + if (hidden) bgp = bgp_old; - goto peer_init; - } + else + bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp)); + + bgp->as = *as; + + if (bgp->as_pretty) + XFREE(MTYPE_BGP_NAME, bgp->as_pretty); + if (as_pretty) + bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_pretty); + else + bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, asn_asn2asplain(*as)); - bgp = XCALLOC(MTYPE_BGP, sizeof(struct bgp)); + if (asnotation != ASNOTATION_UNDEFINED) { + bgp->asnotation = asnotation; + SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION); + } else + asn_str2asn_notation(bgp->as_pretty, NULL, &bgp->asnotation); + + if (hidden) + goto peer_init; if (BGP_DEBUG(zebra, ZEBRA)) { if (inst_type == BGP_INSTANCE_TYPE_DEFAULT) @@ -3461,18 +3477,6 @@ static struct bgp *bgp_create(as_t *as, const char *name, bgp->peer = list_new(); peer_init: - bgp->as = *as; - if (as_pretty) - bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, as_pretty); - else - bgp->as_pretty = XSTRDUP(MTYPE_BGP_NAME, asn_asn2asplain(*as)); - - if (asnotation != ASNOTATION_UNDEFINED) { - bgp->asnotation = asnotation; - SET_FLAG(bgp->config, BGP_CONFIG_ASNOTATION); - } else - asn_str2asn_notation(bgp->as_pretty, NULL, &bgp->asnotation); - bgp->peer->cmp = (int (*)(void *, void *))peer_cmp; bgp->peerhash = hash_create(peer_hash_key_make, peer_hash_same, "BGP Peer Hash"); @@ -3569,7 +3573,7 @@ peer_init: /* printable name we can use in debug messages */ if (inst_type == BGP_INSTANCE_TYPE_DEFAULT && !hidden) { bgp->name_pretty = XSTRDUP(MTYPE_BGP_NAME, "VRF default"); - } else { + } else if (!hidden) { const char *n; int len; @@ -3772,7 +3776,7 @@ int bgp_lookup_by_as_name_type(struct bgp **bgp_val, as_t *as, const char *as_pr /* Handle AS number change */ if (bgp->as != *as) { if (hidden || CHECK_FLAG(bgp->vrf_flags, BGP_VRF_AUTO)) { - if (force_config == false && hidden) { + if (hidden) { bgp_create(as, name, inst_type, as_pretty, asnotation, bgp, hidden); @@ -4265,12 +4269,11 @@ int bgp_delete(struct bgp *bgp) bgp_set_evpn(bgp_get_default()); } - if (bgp->process_queue) - work_queue_free_and_null(&bgp->process_queue); - - if (!IS_BGP_INSTANCE_HIDDEN(bgp)) + if (!IS_BGP_INSTANCE_HIDDEN(bgp)) { + if (bgp->process_queue) + work_queue_free_and_null(&bgp->process_queue); bgp_unlock(bgp); /* initial reference */ - else { + } else { for (afi = AFI_IP; afi < AFI_MAX; afi++) { enum vpn_policy_direction dir; diff --git a/doc/developer/mgmtd-dev.rst b/doc/developer/mgmtd-dev.rst index 6cbd617f8c..f113d1b521 100644 --- a/doc/developer/mgmtd-dev.rst +++ b/doc/developer/mgmtd-dev.rst @@ -39,17 +39,18 @@ Conversion Status Fully Converted To MGMTD """""""""""""""""""""""" +- lib/affinitymap - lib/distribute - lib/filter +- lib/if - lib/if_rmap +- lib/keychain - lib/routemap -- lib/affinitymap -- lib/if - lib/vrf - ripd - ripngd - staticd -- zebra (* - partial) +- zebra Converted To Northbound """"""""""""""""""""""" @@ -69,7 +70,6 @@ Unconverted - bgpd - ldpd - lib/event -- lib/keychain - lib/log_vty - lib/nexthop_group - lib/zlog_5424_cli diff --git a/doc/manpages/frr-fabricd.rst b/doc/manpages/frr-fabricd.rst index c14c07661e..7e6d253887 100644 --- a/doc/manpages/frr-fabricd.rst +++ b/doc/manpages/frr-fabricd.rst @@ -21,6 +21,10 @@ OPTIONS available for the |DAEMON| command: .. include:: common-options.rst +.. option:: --dummy_as_loopback + + Treat dummy interfaces as loopback interfaces. + FILES ===== diff --git a/doc/user/basic.rst b/doc/user/basic.rst index b2d47a38eb..f7d7232b17 100644 --- a/doc/user/basic.rst +++ b/doc/user/basic.rst @@ -331,12 +331,9 @@ Basic Config Commands .. clicmd:: allow-reserved-ranges - Allow using IPv4 reserved (Class E) IP ranges for daemons. E.g.: setting - IPv4 addresses for interfaces or allowing reserved ranges in BGP next-hops. - - If you need multiple FRR instances (or FRR + any other daemon) running in a - single router and peering via 127.0.0.0/8, it's also possible to use this - knob if turned on. + Allow peering via loopback addresses (127.0.0.0/8). This is necessary in case + of multiple FRR instances (or FRR + any other daemon) peering via loopback + interfaces running on the same router. Default: off. diff --git a/doc/user/fabricd.rst b/doc/user/fabricd.rst index 48d264f30e..23de6731e2 100644 --- a/doc/user/fabricd.rst +++ b/doc/user/fabricd.rst @@ -15,11 +15,18 @@ FRR implements OpenFabric in a daemon called *fabricd* Configuring fabricd =================== -There are no *fabricd* specific options. Common options can be specified -(:ref:`common-invocation-options`) to *fabricd*. *fabricd* needs to acquire -interface information from *zebra* in order to function. Therefore *zebra* must -be running before invoking *fabricd*. Also, if *zebra* is restarted then *fabricd* -must be too. +*fabricd* accepts all common invocations (:ref:`common-invocation-options`) and +the following specific options. + +.. program:: fabricd + +.. option:: --dummy_as_loopback + + Treat dummy interfaces as loopback interfaces. + +*fabricd* needs to acquire interface information from *zebra* in order to +function. Therefore *zebra* must be running before invoking *fabricd*. Also, if +*zebra* is restarted then *fabricd* must be too. Like other daemons, *fabricd* configuration is done in an OpenFabric specific configuration file :file:`fabricd.conf`. diff --git a/doc/user/pim.rst b/doc/user/pim.rst index 9e4c7bb94a..843bc31d23 100644 --- a/doc/user/pim.rst +++ b/doc/user/pim.rst @@ -391,6 +391,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end. reports on the interface. Refer to the next `ip igmp` command for IGMP management. +.. clicmd:: ip pim allowed-neighbors prefix-list PREFIX_LIST + + Only establish sessions with PIM neighbors allowed by the prefix-list. + .. clicmd:: ip pim use-source A.B.C.D If you have multiple addresses configured on a particular interface @@ -424,6 +428,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end. interfaces on this interface. Join-groups on other interfaces will also be proxied. The default version is v3. +.. clicmd:: ip igmp immediate-leave + + Immediately leaves an IGMP group when receiving a IGMPv2 Leave packet. + .. clicmd:: ip igmp query-interval (1-65535) Set the IGMP query interval that PIM will use. diff --git a/doc/user/pimv6.rst b/doc/user/pimv6.rst index bd5430f51e..8126881bd9 100644 --- a/doc/user/pimv6.rst +++ b/doc/user/pimv6.rst @@ -214,6 +214,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end. reports on the interface. Refer to the next ``ipv6 mld`` command for MLD management. +.. clicmd:: ipv6 pim allowed-neighbors prefix-list PREFIX_LIST + + Only establish sessions with PIM neighbors allowed by the prefix-list. + .. clicmd:: ipv6 pim use-source X:X::X:X If you have multiple addresses configured on a particular interface @@ -245,6 +249,10 @@ is in a vrf, enter the interface command with the vrf keyword at the end. Join multicast group or source-group on an interface. +.. clicmd:: ipv6 mld immediate-leave + + Immediately leaves a MLD group when receiving a MLDv1 Done packet. + .. clicmd:: ipv6 mld query-interval (1-65535) Set the MLD query interval that PIM will use. diff --git a/doc/user/ripd.rst b/doc/user/ripd.rst index ea13dc92df..575eedf952 100644 --- a/doc/user/ripd.rst +++ b/doc/user/ripd.rst @@ -149,12 +149,12 @@ RIP Configuration The default is to be passive on all interfaces. -.. clicmd:: ip split-horizon [poisoned-reverse] +.. clicmd:: ip rip split-horizon [poisoned-reverse] - Control split-horizon on the interface. Default is `ip split-horizon`. If + Control split-horizon on the interface. Default is `ip rip split-horizon`. If you don't perform split-horizon on the interface, please specify `no ip - split-horizon`. + rip split-horizon`. If `poisoned-reverse` is also set, the router sends the poisoned routes with highest metric back to the sending router. diff --git a/doc/user/static.rst b/doc/user/static.rst index 0ce6e2107e..c1d11cf0b0 100644 --- a/doc/user/static.rst +++ b/doc/user/static.rst @@ -207,17 +207,18 @@ SRv6 Static SIDs Commands Move from srv6 node to static-sids node. In this static-sids node, user can configure static SRv6 SIDs. -.. clicmd:: sid X:X::X:X/M locator NAME behavior <uN|uDT4|uDT6|uDT46> [vrf VRF] +.. clicmd:: sid X:X::X:X/M locator NAME behavior <uN|uA|uDT4|uDT6|uDT46> [vrf VRF] [interface IFNAME [nexthop X:X::X:X]] Specify the locator sid manually. Configuring a local sid in a purely static mode by specifying the sid value would generate a unique SID. This feature will support the configuration of static SRv6 decapsulation on the system. - It supports four parameter options, corresponding to the following functions: - uN, uDT4, uDT6, uDT46 + It supports the following behaviors: uN, uA, uDT4, uDT6, uDT46. When configuring the local sid, if the action is set to 'uN', no vrf should be set. - While for any other action, it is necessary to specify a specific vrf. + For uDT4, uDT6 and uDT46, it is necessary to specify a specific vrf. + The uA behavior requires the outgoing interface and optionally the IPv6 address of the Layer 3 adjacency + to which the packet should be forwarded. :: @@ -228,6 +229,7 @@ SRv6 Static SIDs Commands router(config-srv6-sids)# sid fcbb:bbbb:1:fe01::/64 locator LOC1 behavior uDT6 vrf Vrf1 router(config-srv6-sids)# sid fcbb:bbbb:1:fe02::/64 locator LOC1 behavior uDT4 vrf Vrf1 router(config-srv6-sids)# sid fcbb:bbbb:1:fe03::/64 locator LOC1 behavior uDT46 vrf Vrf2 + router(config-srv6-sids)# sid fcbb:bbbb:1:fe04::/64 locator LOC1 behavior uA interface eth0 nexthop 2001::2 router(config-srv6-locator)# show run ... @@ -237,5 +239,6 @@ SRv6 Static SIDs Commands sid fcbb:bbbb:1:fe01::/64 locator LOC1 behavior uDT6 vrf Vrf1 sid fcbb:bbbb:1:fe02::/64 locator LOC1 behavior uDT4 vrf Vrf1 sid fcbb:bbbb:1:fe03::/64 locator LOC1 behavior uDT46 vrf Vrf2 + sid fcbb:bbbb:1:fe04::/64 locator LOC1 behavior uA interface eth0 nexthop 2001::2 ! ...
\ No newline at end of file diff --git a/isisd/isis_circuit.c b/isisd/isis_circuit.c index 5b62d3c518..eed2c52552 100644 --- a/isisd/isis_circuit.c +++ b/isisd/isis_circuit.c @@ -491,16 +491,17 @@ void isis_circuit_if_add(struct isis_circuit *circuit, struct interface *ifp) { struct connected *conn; - if (if_is_broadcast(ifp)) { + if (if_is_loopback(ifp) || (isis_option_check(ISIS_OPT_DUMMY_AS_LOOPBACK) && + CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_DUMMY))) { + circuit->circ_type = CIRCUIT_T_LOOPBACK; + circuit->is_passive = 1; + } else if (if_is_broadcast(ifp)) { if (fabricd || circuit->circ_type_config == CIRCUIT_T_P2P) circuit->circ_type = CIRCUIT_T_P2P; else circuit->circ_type = CIRCUIT_T_BROADCAST; } else if (if_is_pointopoint(ifp)) { circuit->circ_type = CIRCUIT_T_P2P; - } else if (if_is_loopback(ifp)) { - circuit->circ_type = CIRCUIT_T_LOOPBACK; - circuit->is_passive = 1; } else { /* It's normal in case of loopback etc. */ if (IS_DEBUG_EVENTS) diff --git a/isisd/isis_main.c b/isisd/isis_main.c index b7ed8f7605..0d9b3df39c 100644 --- a/isisd/isis_main.c +++ b/isisd/isis_main.c @@ -80,9 +80,12 @@ struct zebra_privs_t isisd_privs = { .cap_num_p = array_size(_caps_p), .cap_num_i = 0}; +#define OPTION_DUMMY_AS_LOOPBACK 2000 + /* isisd options */ static const struct option longopts[] = { {"int_num", required_argument, NULL, 'I'}, + {"dummy_as_loopback", no_argument, NULL, OPTION_DUMMY_AS_LOOPBACK}, {0}}; /* Master of threads. */ @@ -269,15 +272,16 @@ int main(int argc, char **argv, char **envp) { int opt; int instance = 1; + bool dummy_as_loopback = false; #ifdef FABRICD frr_preinit(&fabricd_di, argc, argv); #else frr_preinit(&isisd_di, argc, argv); #endif - frr_opt_add( - "I:", longopts, - " -I, --int_num Set instance number (label-manager)\n"); + frr_opt_add("I:", longopts, + " -I, --int_num Set instance number (label-manager).\n" + " --dummy_as_loopback Treat dummy interfaces like loopback interfaces.\n"); /* Command line argument treatment. */ while (1) { @@ -295,6 +299,9 @@ int main(int argc, char **argv, char **envp) zlog_err("Instance %i out of range (1..%u)", instance, (unsigned short)-1); break; + case OPTION_DUMMY_AS_LOOPBACK: + dummy_as_loopback = true; + break; default: frr_help_exit(1); } @@ -311,6 +318,9 @@ int main(int argc, char **argv, char **envp) /* thread master */ isis_master_init(frr_init()); master = im->master; + if (dummy_as_loopback) + isis_option_set(ISIS_OPT_DUMMY_AS_LOOPBACK); + /* * initializations */ diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c index b985ad1f7d..c2670143f2 100644 --- a/isisd/isis_zebra.c +++ b/isisd/isis_zebra.c @@ -999,6 +999,14 @@ void isis_zebra_srv6_sid_install(struct isis_area *area, case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: default: zlog_err( "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", @@ -1056,6 +1064,14 @@ void isis_zebra_srv6_sid_uninstall(struct isis_area *area, case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: default: zlog_err( "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", @@ -1121,6 +1137,14 @@ void isis_zebra_srv6_adj_sid_install(struct srv6_adjacency *sra) case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: default: zlog_err( "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", @@ -1167,6 +1191,14 @@ void isis_zebra_srv6_adj_sid_uninstall(struct srv6_adjacency *sra) case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: case SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: default: zlog_err( "ISIS-SRv6 (%s): unsupported SRv6 endpoint behavior %u", diff --git a/isisd/isisd.c b/isisd/isisd.c index fed6d3c6dc..a0faf31221 100644 --- a/isisd/isisd.c +++ b/isisd/isisd.c @@ -116,6 +116,25 @@ int show_isis_neighbor_common(struct vty *, struct json_object *json, int clear_isis_neighbor_common(struct vty *, const char *id, const char *vrf_name, bool all_vrf); + +/* ISIS global flag manipulation. */ +int isis_option_set(int flag) +{ + switch (flag) { + case ISIS_OPT_DUMMY_AS_LOOPBACK: + SET_FLAG(im->options, flag); + break; + default: + return -1; + } + return 0; +} + +int isis_option_check(int flag) +{ + return CHECK_FLAG(im->options, flag); +} + /* Link ISIS instance to VRF. */ void isis_vrf_link(struct isis *isis, struct vrf *vrf) { diff --git a/isisd/isisd.h b/isisd/isisd.h index 1ae39f0ae9..ae39502023 100644 --- a/isisd/isisd.h +++ b/isisd/isisd.h @@ -74,7 +74,9 @@ struct isis_master { struct list *isis; /* ISIS thread master. */ struct event_loop *master; + /* Various global options */ uint8_t options; +#define ISIS_OPT_DUMMY_AS_LOOPBACK (1 << 0) }; #define F_ISIS_UNIT_TEST 0x01 @@ -269,6 +271,8 @@ DECLARE_HOOK(isis_area_overload_bit_update, (struct isis_area * area), (area)); void isis_terminate(void); void isis_master_init(struct event_loop *master); void isis_master_terminate(void); +int isis_option_set(int flag); +int isis_option_check(int flag); void isis_vrf_link(struct isis *isis, struct vrf *vrf); void isis_vrf_unlink(struct isis *isis, struct vrf *vrf); struct isis *isis_lookup_by_vrfid(vrf_id_t vrf_id); @@ -242,6 +242,7 @@ struct interface { #define ZEBRA_INTERFACE_SUB (1 << 1) #define ZEBRA_INTERFACE_LINKDETECTION (1 << 2) #define ZEBRA_INTERFACE_VRF_LOOPBACK (1 << 3) +#define ZEBRA_INTERFACE_DUMMY (1 << 4) /* Interface flags. */ uint64_t flags; diff --git a/lib/keychain_nb.c b/lib/keychain_nb.c index 57967b30a5..7c3df1c857 100644 --- a/lib/keychain_nb.c +++ b/lib/keychain_nb.c @@ -587,9 +587,9 @@ static int key_chains_key_chain_key_crypto_algorithm_destroy( if (args->event != NB_EV_APPLY) return NB_OK; - name = yang_dnode_get_string(args->dnode, "../../../name"); + name = yang_dnode_get_string(args->dnode, "../../name"); keychain = keychain_lookup(name); - index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../../key-id"); + index = (uint32_t)yang_dnode_get_uint64(args->dnode, "../key-id"); key = key_lookup(keychain, index); key->hash_algo = KEYCHAIN_ALGO_NULL; keychain_touch(keychain); diff --git a/lib/northbound.c b/lib/northbound.c index 60794b8728..f860b83c45 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -127,6 +127,8 @@ static int nb_node_new_cb(const struct lysc_node *snode, void *arg) if (module && module->ignore_cfg_cbs) SET_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS); + if (module && module->get_tree_locked) + SET_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE); return YANG_ITER_CONTINUE; } @@ -256,6 +258,7 @@ static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node) { unsigned int error = 0; + bool state_optional = CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE); if (CHECK_FLAG(nb_node->flags, F_NB_NODE_IGNORE_CFG_CBS)) return error; @@ -273,15 +276,15 @@ static unsigned int nb_node_validate_cbs(const struct nb_node *nb_node) error += nb_node_validate_cb(nb_node, NB_CB_APPLY_FINISH, !!nb_node->cbs.apply_finish, true); error += nb_node_validate_cb(nb_node, NB_CB_GET_ELEM, - (nb_node->cbs.get_elem || nb_node->cbs.get), false); + (nb_node->cbs.get_elem || nb_node->cbs.get), state_optional); error += nb_node_validate_cb(nb_node, NB_CB_GET_NEXT, (nb_node->cbs.get_next || (nb_node->snode->nodetype == LYS_LEAFLIST && nb_node->cbs.get)), - false); - error += nb_node_validate_cb(nb_node, NB_CB_GET_KEYS, - !!nb_node->cbs.get_keys, false); - error += nb_node_validate_cb(nb_node, NB_CB_LOOKUP_ENTRY, - !!nb_node->cbs.lookup_entry, false); + state_optional); + error += nb_node_validate_cb(nb_node, NB_CB_GET_KEYS, !!nb_node->cbs.get_keys, + state_optional); + error += nb_node_validate_cb(nb_node, NB_CB_LOOKUP_ENTRY, !!nb_node->cbs.lookup_entry, + state_optional); error += nb_node_validate_cb(nb_node, NB_CB_RPC, !!nb_node->cbs.rpc, false); error += nb_node_validate_cb(nb_node, NB_CB_NOTIFY, @@ -516,20 +519,33 @@ void nb_config_diff_created(const struct lyd_node *dnode, uint32_t *seq, static void nb_config_diff_deleted(const struct lyd_node *dnode, uint32_t *seq, struct nb_config_cbs *changes) { + struct nb_node *nb_node = dnode->schema->priv; + struct lyd_node *child; + bool recursed = false; + /* Ignore unimplemented nodes. */ - if (!dnode->schema->priv) + if (!nb_node) return; + /* + * If the CB structure indicates it (recurse flag set), call the destroy + * callbacks for the children of a containment node. + */ + if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER | LYS_LIST) && + CHECK_FLAG(nb_node->cbs.flags, F_NB_CB_DESTROY_RECURSE)) { + recursed = true; + LY_LIST_FOR (lyd_child(dnode), child) { + nb_config_diff_deleted(child, seq, changes); + } + } + if (nb_cb_operation_is_valid(NB_CB_DESTROY, dnode->schema)) nb_config_diff_add_change(changes, NB_CB_DESTROY, seq, dnode); - else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER)) { - struct lyd_node *child; - + else if (CHECK_FLAG(dnode->schema->nodetype, LYS_CONTAINER) && !recursed) { /* - * Non-presence containers need special handling since they - * don't have "destroy" callbacks. In this case, what we need to - * do is to call the "destroy" callbacks of their child nodes - * when applicable (i.e. optional nodes). + * If we didn't already above, call destroy on the children of + * this container (it's an NP container) as NP containers have + * no destroy CB themselves. */ LY_LIST_FOR (lyd_child(dnode), child) { nb_config_diff_deleted(child, seq, changes); @@ -2717,7 +2733,7 @@ void nb_init(struct event_loop *tm, const struct frr_yang_module_info *const modules[], size_t nmodules, bool db_enabled, bool load_library) { - struct yang_module *loaded[nmodules], **loadedp = loaded; + struct yang_module *loaded[nmodules]; /* * Currently using this explicit compile feature in libyang2 leads to @@ -2737,8 +2753,8 @@ void nb_init(struct event_loop *tm, for (size_t i = 0; i < nmodules; i++) { DEBUGD(&nb_dbg_events, "northbound: loading %s.yang", modules[i]->name); - *loadedp++ = yang_module_load(modules[i]->name, - modules[i]->features); + loaded[i] = yang_module_load(modules[i]->name, modules[i]->features); + loaded[i]->frr_info = modules[i]; } if (explicit_compile) diff --git a/lib/northbound.h b/lib/northbound.h index c31f007e70..0468c58de3 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -386,6 +386,11 @@ struct nb_callbacks { int (*destroy)(struct nb_cb_destroy_args *args); /* + * Flags to control the how northbound callbacks are invoked. + */ + uint flags; + + /* * Configuration callback. * * A list entry or leaf-list entry has been moved. Only applicable when @@ -622,6 +627,12 @@ struct nb_callbacks { void (*cli_show_end)(struct vty *vty, const struct lyd_node *dnode); }; +/* + * Flag indicating the northbound should recurse destroy the children of this + * node when it is destroyed. + */ +#define F_NB_CB_DESTROY_RECURSE 0x01 + struct nb_dependency_callbacks { void (*get_dependant_xpath)(const struct lyd_node *dnode, char *xpath); void (*get_dependency_xpath)(const struct lyd_node *dnode, char *xpath); @@ -663,6 +674,8 @@ struct nb_node { #define F_NB_NODE_KEYLESS_LIST 0x02 /* Ignore config callbacks for this node */ #define F_NB_NODE_IGNORE_CFG_CBS 0x04 +/* Ignore state callbacks for this node */ +#define F_NB_NODE_HAS_GET_TREE 0x08 /* * HACK: old gcc versions (< 5.x) have a bug that prevents C99 flexible arrays @@ -690,6 +703,22 @@ struct frr_yang_module_info { */ const char **features; + /* + * If the module keeps its oper-state in a libyang tree + * this function should return that tree (locked if multi-threading). + * If this function is provided then the state callback functions + * (get_elem, get_keys, get_next, lookup_entry) need not be set for a + * module. The unlock_tree function if non-NULL will be called with + * the returned tree and the *user_lock value. + */ + const struct lyd_node *(*get_tree_locked)(const char *xpath, void **user_lock); + + /* + * This function will be called following a call to get_tree_locked() in + * order to unlock the tree if locking was required. + */ + void (*unlock_tree)(const struct lyd_node *tree, void *user_lock); + /* Northbound callbacks. */ const struct { /* Data path of this YANG node. */ @@ -1825,6 +1854,18 @@ extern struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, c extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt, va_list ap); +/** + * nb_notif_add() - Notice that the value at `path` has changed. + * @path - Absolute path in the state tree that has changed (either added or + * updated). + */ +void nb_notif_add(const char *path); + +/** + * nb_notif_delete() - Notice that the value at `path` has been deleted. + * @path - Absolute path in the state tree that has been deleted. + */ +void nb_notif_delete(const char *path); /** * nb_notif_set_filters() - add or replace notification filters @@ -1835,6 +1876,15 @@ extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, */ extern void nb_notif_set_filters(const char **selectors, bool replace); +/** + * nb_notif_enable_multi_thread() - enable use of multiple threads with nb_notif + * + * If the nb_notif_XXX calls will be made from multiple threads then locking is + * required. Call this function to enable that functionality, prior to using the + * nb_notif_XXX API. + */ +extern void nb_notif_enable_multi_thread(void); + extern void nb_notif_init(struct event_loop *loop); extern void nb_notif_terminate(void); diff --git a/lib/northbound_notif.c b/lib/northbound_notif.c index 9caca9f6d7..746b33beb2 100644 --- a/lib/northbound_notif.c +++ b/lib/northbound_notif.c @@ -58,6 +58,9 @@ RB_HEAD(op_changes, op_change); RB_PROTOTYPE(op_changes, op_change, link, op_change_cmp) RB_GENERATE(op_changes, op_change, link, op_change_cmp) +pthread_mutex_t _nb_notif_lock = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t *nb_notif_lock; + struct op_changes nb_notif_adds = RB_INITIALIZER(&nb_notif_adds); struct op_changes nb_notif_dels = RB_INITIALIZER(&nb_notif_dels); struct event_loop *nb_notif_master; @@ -158,12 +161,14 @@ static void op_change_free(struct op_change *note) } /** - * op_changes_group_push() - Save the current set of changes on the queue. + * __op_changes_group_push() - Save the current set of changes on the queue. * * This function will save the current set of changes on the queue and * initialize a new set of changes. + * + * The lock must be held during this call. */ -static void op_changes_group_push(void) +static void __op_changes_group_push(void) { struct op_changes_group *changes; @@ -312,17 +317,34 @@ static void __op_change_add_del(const char *path, struct op_changes *this_head, nb_notif_set_walk_timer(); } -static void nb_notif_add(const char *path) +void nb_notif_add(const char *path) { + if (nb_notif_lock) + pthread_mutex_lock(nb_notif_lock); + __op_change_add_del(path, &nb_notif_adds, &nb_notif_dels); + + if (nb_notif_lock) + pthread_mutex_unlock(nb_notif_lock); } -static void nb_notif_delete(const char *path) +void nb_notif_delete(const char *path) { + if (nb_notif_lock) + pthread_mutex_lock(nb_notif_lock); + __op_change_add_del(path, &nb_notif_dels, &nb_notif_adds); + + if (nb_notif_lock) + pthread_mutex_unlock(nb_notif_lock); } + +/* ---------------------------------------------- */ +/* User functions to update and delete oper state */ +/* ---------------------------------------------- */ + struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value) { struct lyd_node *dnode; @@ -459,13 +481,21 @@ static struct op_changes_group *op_changes_group_next(void) { struct op_changes_group *group; + if (nb_notif_lock) + pthread_mutex_lock(nb_notif_lock); + group = op_changes_queue_pop(&op_changes_queue); if (!group) { - op_changes_group_push(); + __op_changes_group_push(); group = op_changes_queue_pop(&op_changes_queue); } + + if (nb_notif_lock) + pthread_mutex_unlock(nb_notif_lock); + if (!group) return NULL; + group->cur_changes = &group->dels; group->cur_change = RB_MIN(op_changes, group->cur_changes); if (!group->cur_change) { @@ -674,6 +704,11 @@ void nb_notif_set_filters(const char **selectors, bool replace) darr_free(selectors); } +void nb_notif_enable_multi_thread(void) +{ + nb_notif_lock = &_nb_notif_lock; +} + void nb_notif_init(struct event_loop *tm) { nb_notif_master = tm; diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c index 6336db502a..ad495b6f9c 100644 --- a/lib/northbound_oper.c +++ b/lib/northbound_oper.c @@ -48,6 +48,9 @@ DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos"); /* ---------- */ PREDECL_LIST(nb_op_walks); +typedef const struct lyd_node *(*get_tree_locked_cb)(const char *xpath, void **user_tree_lock); +typedef void (*unlock_tree_cb)(const struct lyd_node *tree, void *user_tree_lock); + /* * This is our information about a node on the branch we are looking at */ @@ -81,6 +84,7 @@ struct nb_op_node_info { * @walk_start_level: @walk_root_level + 1. * @query_base_level: the level the query string stops at and full walks * commence below that. + * @user_tree: the user's existing state tree to copy state from or NULL. */ struct nb_op_yield_state { /* Walking state */ @@ -96,6 +100,11 @@ struct nb_op_yield_state { int query_base_level; bool query_list_entry; /* XXX query was for a specific list entry */ + /* For now we support a single use of this. */ + const struct lyd_node *user_tree; + void *user_tree_lock; + unlock_tree_cb user_tree_unlock; + /* Yielding state */ bool query_did_entry; /* currently processing the entry */ bool should_batch; @@ -125,6 +134,11 @@ static struct nb_op_walks_head nb_op_walks; static enum nb_error nb_op_yield(struct nb_op_yield_state *ys); static struct lyd_node *ys_root_node(struct nb_op_yield_state *ys); +static const void *nb_op_list_get_next(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const struct nb_op_node_info *pni, const void *list_entry); +static const void *nb_op_list_lookup_entry(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const struct nb_op_node_info *pni, struct lyd_node *node, + const struct yang_list_keys *keys); /* -------------------- */ /* Function Definitions */ @@ -140,6 +154,11 @@ nb_op_create_yield_state(const char *xpath, struct yang_translator *translator, ys = XCALLOC(MTYPE_NB_YIELD_STATE, sizeof(*ys)); ys->xpath = darr_strdup_cap(xpath, (size_t)XPATH_MAXLEN); + /* remove trailing '/'s */ + while (darr_len(ys->xpath) > 1 && ys->xpath[darr_len(ys->xpath) - 2] == '/') { + darr_setlen(ys->xpath, darr_len(ys->xpath) - 1); + *darr_last(ys->xpath) = 0; + } ys->xpath_orig = darr_strdup(xpath); ys->translator = translator; ys->flags = flags; @@ -158,6 +177,8 @@ static inline void nb_op_free_yield_state(struct nb_op_yield_state *ys, bool nofree_tree) { if (ys) { + if (ys->user_tree && ys->user_tree_unlock) + ys->user_tree_unlock(ys->user_tree, ys->user_tree_lock); EVENT_OFF(ys->walk_ev); nb_op_walks_del(&nb_op_walks, ys); /* if we have a branch then free up it's libyang tree */ @@ -295,9 +316,8 @@ static bool __move_back_to_next(struct nb_op_yield_state *ys, int i) static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) { - struct nb_op_node_info *ni; + struct nb_op_node_info *pni, *ni; struct nb_node *nn; - const void *parent_entry; const void *list_entry; uint i; @@ -320,6 +340,7 @@ static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) * restored. */ darr_foreach_i (ys->node_infos, i) { + pni = i > 0 ? &ys->node_infos[i - 1] : NULL; ni = &ys->node_infos[i]; nn = ni->schema->priv; @@ -330,9 +351,7 @@ static void nb_op_resume_data_tree(struct nb_op_yield_state *ys) ni == darr_last(ys->node_infos)); /* Verify the entry is still present */ - parent_entry = (i == 0 ? NULL : ni[-1].list_entry); - list_entry = nb_callback_lookup_entry(nn, parent_entry, - &ni->keys); + list_entry = nb_op_list_lookup_entry(ys, nn, pni, NULL, &ni->keys); if (!list_entry || list_entry != ni->list_entry) { /* May be NULL or a different pointer * move back to first of @@ -404,6 +423,7 @@ static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in, char **xpath_out static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, uint index) { + struct nb_op_node_info *pni = index == 0 ? NULL : &ys->node_infos[index - 1]; struct nb_op_node_info *ni = &ys->node_infos[index]; struct lyd_node *inner = ni->inner; struct nb_node *nn = ni->schema->priv; @@ -417,8 +437,7 @@ static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, /* Assert that we are walking the rightmost branch */ assert(!inner->parent || inner == inner->parent->child->prev); - if (CHECK_FLAG(inner->schema->nodetype, - LYS_CASE | LYS_CHOICE | LYS_CONTAINER)) { + if (CHECK_FLAG(inner->schema->nodetype, LYS_CONTAINER)) { /* containers have only zero or one child on a branch of a tree */ inner = ((struct lyd_node_inner *)inner)->child; assert(!inner || inner->prev == inner); @@ -448,17 +467,12 @@ static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, */ /* ni->list_entry starts as the parent entry of this node */ - ni->list_entry = nb_callback_get_next(nn, ni->list_entry, NULL); + ni->list_entry = nb_op_list_get_next(ys, nn, pni, NULL); for (i = 1; i < ni->position && ni->list_entry; i++) - ni->list_entry = nb_callback_get_next(nn, ni->list_entry, ni->list_entry); + ni->list_entry = nb_op_list_get_next(ys, nn, pni, ni->list_entry); - if (i != ni->position || !ni->list_entry) { - flog_warn(EC_LIB_NB_OPERATIONAL_DATA, - "%s: entry at position %d doesn't exist in: %s", __func__, - ni->position, ys->xpath); + if (i != ni->position || !ni->list_entry) return NB_ERR_NOT_FOUND; - } - } else { nb_op_get_keys((struct lyd_node_inner *)inner, &ni->keys); /* A list entry cannot be present in a tree w/o it's keys */ @@ -468,8 +482,10 @@ static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, * Get this nodes opaque list_entry object */ + /* We need a lookup entry unless this is a keyless list */ - if (!nn->cbs.lookup_entry && ni->keys.num) { + if (!nn->cbs.lookup_entry && ni->keys.num && + !CHECK_FLAG(nn->flags, F_NB_NODE_HAS_GET_TREE)) { flog_warn(EC_LIB_NB_OPERATIONAL_DATA, "%s: data path doesn't support iteration over operational data: %s", __func__, ys->xpath); @@ -477,7 +493,7 @@ static enum nb_error nb_op_ys_finalize_node_info(struct nb_op_yield_state *ys, } /* ni->list_entry starts as the parent entry of this node */ - ni->list_entry = nb_callback_lookup_entry(nn, ni->list_entry, &ni->keys); + ni->list_entry = nb_op_list_lookup_entry(ys, nn, pni, NULL, &ni->keys); if (ni->list_entry == NULL) { flog_warn(EC_LIB_NB_OPERATIONAL_DATA, "%s: list entry lookup failed", __func__); @@ -568,7 +584,8 @@ static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys) inner = node; prevlen = 0; xplen = strlen(xpath); - darr_free(xpath); + darr_free(ys->xpath); + ys->xpath = xpath; for (i = len; i > 0; i--, inner = &inner->parent->node) { ni = &ys->node_infos[i - 1]; ni->inner = inner; @@ -630,6 +647,222 @@ static enum nb_error nb_op_ys_init_node_infos(struct nb_op_yield_state *ys) /* End of init code */ /* ================ */ +static const char *__module_name(const struct nb_node *nb_node) +{ + return nb_node->snode->module->name; +} + +static get_tree_locked_cb __get_get_tree_funcs(const char *module_name, + unlock_tree_cb *unlock_func_pp) +{ + struct yang_module *module = yang_module_find(module_name); + + if (!module || !module->frr_info->get_tree_locked) + return NULL; + + *unlock_func_pp = module->frr_info->unlock_tree; + return module->frr_info->get_tree_locked; +} + +static const struct lyd_node *__get_tree(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, const char *xpath) +{ + get_tree_locked_cb get_tree_cb; + + if (ys->user_tree) + return ys->user_tree; + + get_tree_cb = __get_get_tree_funcs(__module_name(nb_node), &ys->user_tree_unlock); + assert(get_tree_cb); + + ys->user_tree = get_tree_cb(xpath, &ys->user_tree_lock); + return ys->user_tree; +} + +/** + * nb_op_libyang_cb_get() - get a leaf value from user supplied libyang tree. + */ +static enum nb_error nb_op_libyang_cb_get(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, struct lyd_node *parent, + const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + const struct lyd_node *tree = __get_tree(ys, nb_node, xpath); + struct lyd_node *node; + LY_ERR err; + + err = lyd_find_path(tree, xpath, false, &node); + /* We are getting LY_EINCOMPLETE for missing `type empty` nodes */ + if (err == LY_ENOTFOUND || err == LY_EINCOMPLETE) + return NB_OK; + else if (err != LY_SUCCESS) + return NB_ERR; + if (lyd_dup_single_to_ctx(node, snode->module->ctx, (struct lyd_node_inner *)parent, 0, + &node)) + return NB_ERR; + return NB_OK; +} + +static enum nb_error nb_op_libyang_cb_get_leaflist(struct nb_op_yield_state *ys, + const struct nb_node *nb_node, + struct lyd_node *parent, const char *xpath) +{ + const struct lysc_node *snode = nb_node->snode; + const struct lyd_node *tree = __get_tree(ys, nb_node, xpath); + struct ly_set *set = NULL; + LY_ERR err; + int ret = NB_OK; + uint i; + + err = lyd_find_xpath(tree, xpath, &set); + /* We are getting LY_EINCOMPLETE for missing `type empty` nodes */ + if (err == LY_ENOTFOUND || err == LY_EINCOMPLETE) + return NB_OK; + else if (err != LY_SUCCESS) + return NB_ERR; + + for (i = 0; i < set->count; i++) { + if (lyd_dup_single_to_ctx(set->dnodes[i], snode->module->ctx, + (struct lyd_node_inner *)parent, 0, NULL)) { + ret = NB_ERR; + break; + } + } + ly_set_free(set, NULL); + return ret; +} + +static const struct lyd_node *__get_node_other_tree(const struct lyd_node *tree, + const struct lyd_node *parent_node, + const struct lysc_node *schema, + const struct yang_list_keys *keys) +{ + char xpath[XPATH_MAXLEN]; + struct lyd_node *node; + int schema_len = strlen(schema->name); + struct ly_set *set = NULL; + int len; + + if (!parent_node) { + /* we need a full path to the schema node */ + if (!lysc_path(schema, LYSC_PATH_DATA, xpath, sizeof(xpath))) + return NULL; + len = strlen(xpath); + } else { + if (!lyd_path(parent_node, LYD_PATH_STD, xpath, sizeof(xpath))) + return NULL; + len = strlen(xpath); + /* do we have room for slash and the node basename? */ + if (len + 1 + schema_len + 1 > XPATH_MAXLEN) + return NULL; + xpath[len++] = '/'; + strlcpy(&xpath[len], schema->name, sizeof(xpath) - len); + len += schema_len; + } + if (keys) + yang_get_key_preds(&xpath[len], schema, keys, sizeof(xpath) - len); + + if (lyd_find_xpath(tree, xpath, &set)) + return NULL; + if (set->count < 1) + return NULL; + node = set->dnodes[0]; + ly_set_free(set, NULL); + return node; +} + +static const void *nb_op_list_lookup_entry(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const struct nb_op_node_info *pni, struct lyd_node *node, + const struct yang_list_keys *keys) +{ + struct yang_list_keys _keys; + const struct lyd_node *tree; + const struct lyd_node *parent_node; + + /* Use user callback */ + if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE)) { + if (node) + return nb_callback_lookup_node_entry(node, pni ? pni->list_entry : NULL); + + assert(keys); + return nb_callback_lookup_entry(nb_node, pni ? pni->list_entry : NULL, keys); + } + + if (!keys) { + assert(node); + if (yang_get_node_keys(node, &_keys)) { + flog_warn(EC_LIB_LIBYANG, + "%s: can't get keys for lookup from existing data node %s", + __func__, node->schema->name); + return NULL; + } + keys = &_keys; + } + tree = __get_tree(ys, nb_node, NULL); + parent_node = pni ? pni->inner : NULL; + return __get_node_other_tree(tree, parent_node, nb_node->snode, keys); +} + +static const void *__get_next(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const struct nb_op_node_info *pni, const void *list_entry) +{ + const struct lysc_node *snode = nb_node->snode; + const struct lyd_node *tree = __get_tree(ys, nb_node, NULL); + const struct lyd_node *parent_node = pni ? pni->inner : NULL; + const struct lyd_node *node = list_entry; + + if (!node) + return __get_node_other_tree(tree, parent_node, snode, NULL); + + node = node->next; + LY_LIST_FOR (node, node) { + if (node->schema == snode) + break; + } + return node; +} + +static const void *nb_op_list_get_next(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const struct nb_op_node_info *pni, const void *list_entry) +{ + if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE)) + return nb_callback_get_next(nb_node, pni ? pni->list_entry : NULL, list_entry); + return __get_next(ys, nb_node, pni, list_entry); +} + +static enum nb_error nb_op_list_get_keys(struct nb_op_yield_state *ys, struct nb_node *nb_node, + const void *list_entry, struct yang_list_keys *keys) +{ + const struct lyd_node_inner *list_node = list_entry; + const struct lyd_node *child; + uint count = 0; + + /* Use user callback */ + if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE)) + return nb_callback_get_keys(nb_node, list_entry, keys); + + assert(list_node->schema->nodetype == LYS_LIST); + + /* + * NOTE: libyang current stores the keys as the first children of a list + * node we count on that here. + */ + + LY_LIST_FOR (lyd_child(&list_node->node), child) { + if (!lysc_is_key(child->schema)) + break; + if (count == LIST_MAXKEYS) { + zlog_err("Too many keys for list_node: %s", list_node->schema->name); + break; + } + strlcpy(keys->key[count++], lyd_get_value(child), sizeof(keys->key[0])); + } + keys->num = count; + + return 0; +} + + /** * nb_op_add_leaf() - Add leaf data to the get tree results * @ys - the yield state for this tree walk. @@ -655,8 +888,13 @@ static enum nb_error nb_op_iter_leaf(struct nb_op_yield_state *ys, if (lysc_is_key(snode)) return NB_OK; + /* See if we use data tree directly */ + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE)) + return nb_op_libyang_cb_get(ys, nb_node, ni->inner, xpath); + /* Check for new simple get */ if (nb_node->cbs.get) + /* XXX: need to run through translator */ return nb_node->cbs.get(nb_node, ni->list_entry, ni->inner); data = nb_callback_get_elem(nb_node, xpath, ni->list_entry); @@ -694,8 +932,13 @@ static enum nb_error nb_op_iter_leaflist(struct nb_op_yield_state *ys, /* Check for new simple get */ if (nb_node->cbs.get) + /* XXX: need to run through translator */ return nb_node->cbs.get(nb_node, ni->list_entry, ni->inner); + if (CHECK_FLAG(nb_node->flags, F_NB_NODE_HAS_GET_TREE)) + /* XXX: need to run through translator */ + return nb_op_libyang_cb_get_leaflist(ys, nb_node, ni->inner, xpath); + do { struct yang_data *data; @@ -897,8 +1140,7 @@ static const struct lysc_node *nb_op_sib_first(struct nb_op_yield_state *ys, * base of the user query, return the next schema node from the query * string (schema_path). */ - if (last != NULL && - !CHECK_FLAG(last->schema->nodetype, LYS_CASE | LYS_CHOICE)) + if (last != NULL) assert(last->schema == parent); if (darr_lasti(ys->node_infos) < ys->query_base_level) return ys->schema_path[darr_lasti(ys->node_infos) + 1]; @@ -975,7 +1217,8 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) if (!walk_stem_tip) return NB_ERR_NOT_FOUND; - if (ys->schema_path[0]->nodetype == LYS_CHOICE) { + if (ys->schema_path[0]->parent && + CHECK_FLAG(ys->schema_path[0]->parent->nodetype, LYS_CHOICE|LYS_CASE)) { flog_err(EC_LIB_NB_OPERATIONAL_DATA, "%s: unable to walk root level choice node from module: %s", __func__, ys->schema_path[0]->module->name); @@ -1082,8 +1325,12 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) LYS_LEAF | LYS_LEAFLIST | LYS_CONTAINER)) xpath_child = nb_op_get_child_path(ys->xpath, sib, xpath_child); - else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE)) + else if (CHECK_FLAG(sib->nodetype, LYS_CASE | LYS_CHOICE)) { darr_in_strdup(xpath_child, ys->xpath); + len = darr_last(ys->node_infos)->xpath_len; + darr_setlen(xpath_child, len + 1); + xpath_child[len] = 0; + } nn = sib->priv; @@ -1304,9 +1551,8 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) * -------------------- */ if (list_start) { - list_entry = - nb_callback_lookup_node_entry( - node, parent_list_entry); + list_entry = nb_op_list_lookup_entry(ys, nn, pni, node, + NULL); /* * If the node we created from a * specific predicate entry is not @@ -1339,10 +1585,7 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) * (list_entry != NULL) the list iteration. */ /* Obtain [next] list entry. */ - list_entry = - nb_callback_get_next(nn, - parent_list_entry, - list_entry); + list_entry = nb_op_list_get_next(ys, nn, pni, list_entry); } /* @@ -1468,8 +1711,7 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) /* Need to get keys. */ if (!CHECK_FLAG(nn->flags, F_NB_NODE_KEYLESS_LIST)) { - ret = nb_callback_get_keys(nn, list_entry, - &ni->keys); + ret = nb_op_list_get_keys(ys, nn, list_entry, &ni->keys); if (ret) { darr_pop(ys->node_infos); ret = NB_ERR_RESOURCE; @@ -1481,8 +1723,9 @@ static enum nb_error __walk(struct nb_op_yield_state *ys, bool is_resume) */ len = darr_strlen(ys->xpath); if (ni->keys.num) { - yang_get_key_preds(ys->xpath + len, sib, - &ni->keys, + darr_ensure_avail(ys->xpath, + yang_get_key_pred_strlen(sib, &ni->keys) + 1); + yang_get_key_preds(ys->xpath + len, sib, &ni->keys, darr_cap(ys->xpath) - len); } else { /* add a position predicate (1s based?) */ @@ -1703,22 +1946,22 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, * NOTE: appears to be a bug in nb_node linkage where parent can be NULL, * or I'm misunderstanding the code, in any case we use the libyang * linkage to walk which works fine. - * - * XXX: we don't actually support choice/case yet, they are container - * types in the libyang schema, but won't be in data so our length - * checking gets messed up. */ - for (sn = nblast->snode, count = 0; sn; count++, sn = sn->parent) + for (sn = nblast->snode, count = 0; sn; sn = sn->parent) { if (sn != nblast->snode) assert(CHECK_FLAG(sn->nodetype, - LYS_CONTAINER | LYS_LIST | - LYS_CHOICE | LYS_CASE)); + LYS_CONTAINER | LYS_LIST | LYS_CHOICE | LYS_CASE)); + if (!CHECK_FLAG(sn->nodetype, LYS_CHOICE | LYS_CASE)) + count++; + } /* create our arrays */ darr_append_n(ys->schema_path, count); darr_append_n(ys->query_tokens, count); darr_append_nz(ys->non_specific_predicate, count); - for (sn = nblast->snode; sn; sn = sn->parent) - ys->schema_path[--count] = sn; + for (sn = nblast->snode; sn; sn = sn->parent) { + if (!CHECK_FLAG(sn->nodetype, LYS_CHOICE | LYS_CASE)) + ys->schema_path[--count] = sn; + } /* * Now tokenize the query string and get pointers to each token @@ -1737,50 +1980,42 @@ static enum nb_error nb_op_ys_init_schema_path(struct nb_op_yield_state *ys, int nlen = strlen(name); int mnlen = 0; - /* - * Technically the query_token for choice/case should probably be pointing at - * the child (leaf) rather than the parent (container), however, - * we only use these for processing list nodes so KISS. - */ - if (CHECK_FLAG(ys->schema_path[i]->nodetype, - LYS_CASE | LYS_CHOICE)) { - ys->query_tokens[i] = ys->query_tokens[i - 1]; - continue; - } - + s2 = s; while (true) { - s2 = strstr(s, name); + /* skip past any module name prefix */ + s2 = strstr(s2, name); if (!s2) goto error; - if (s2[-1] == ':') { + if (s2 > s && s2[-1] == ':') { mnlen = strlen(modname) + 1; - if (ys->query_tokstr > s2 - mnlen || - strncmp(s2 - mnlen, modname, mnlen - 1)) - goto error; + if (s2 - s < mnlen || strncmp(s2 - mnlen, modname, mnlen - 1)) { + /* No match of module prefix, advance and try again */ + s2 += strlen(name); + continue; + } s2 -= mnlen; nlen += mnlen; } - s = s2; - if ((i == 0 || s[-1] == '/') && - (s[nlen] == 0 || s[nlen] == '[' || s[nlen] == '/')) + if ((i == 0 || s2[-1] == '/') && + (s2[nlen] == 0 || s2[nlen] == '[' || s2[nlen] == '/')) { + s = s2; break; - /* - * Advance past the incorrect match, must have been - * part of previous predicate. - */ - s += nlen; + } + /* No exact match at end, advance and try again */ + s2 += strlen(name); } /* NUL terminate previous token and save this one */ - if (i > 0) + if (i > 0) { + assert(s[-1] == '/'); s[-1] = 0; + } ys->query_tokens[i] = s; s += nlen; } - /* NOTE: need to subtract choice/case nodes when these are supported */ ys->query_base_level = darr_lasti(ys->schema_path); return NB_OK; diff --git a/lib/prefix.c b/lib/prefix.c index 2485c3e61b..feaf3e5f1c 100644 --- a/lib/prefix.c +++ b/lib/prefix.c @@ -1439,10 +1439,13 @@ bool ipv4_unicast_valid(const struct in_addr *addr) { in_addr_t ip = ntohl(addr->s_addr); + if (IPV4_CLASS_E(ip)) + return true; + if (IPV4_CLASS_D(ip)) return false; - if (IPV4_NET0(ip) || IPV4_NET127(ip) || IPV4_CLASS_E(ip)) { + if (IPV4_NET0(ip) || IPV4_NET127(ip)) { if (cmd_allow_reserved_ranges_get()) return true; else diff --git a/lib/srv6.h b/lib/srv6.h index 011705504e..467f02a3c9 100644 --- a/lib/srv6.h +++ b/lib/srv6.h @@ -176,12 +176,20 @@ struct srv6_locator_chunk { enum srv6_endpoint_behavior_codepoint { SRV6_ENDPOINT_BEHAVIOR_RESERVED = 0x0000, SRV6_ENDPOINT_BEHAVIOR_END = 0x0001, + SRV6_ENDPOINT_BEHAVIOR_END_PSP = 0x0002, SRV6_ENDPOINT_BEHAVIOR_END_X = 0x0005, + SRV6_ENDPOINT_BEHAVIOR_END_X_PSP = 0x0006, SRV6_ENDPOINT_BEHAVIOR_END_DT6 = 0x0012, SRV6_ENDPOINT_BEHAVIOR_END_DT4 = 0x0013, SRV6_ENDPOINT_BEHAVIOR_END_DT46 = 0x0014, + SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD = 0x001D, + SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD = 0x0021, SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID = 0x002B, - SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID = 0x002C, + SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID = 0x0034, + SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP = 0x002C, + SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD = 0x0030, + SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP = 0x0035, + SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD = 0x0039, SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID = 0x003E, SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID = 0x003F, SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID = 0x0040, @@ -199,8 +207,16 @@ srv6_endpoint_behavior_codepoint2str(enum srv6_endpoint_behavior_codepoint behav return "Reserved"; case SRV6_ENDPOINT_BEHAVIOR_END: return "End"; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + return "End PSP"; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + return "End PSP/USD"; case SRV6_ENDPOINT_BEHAVIOR_END_X: return "End.X"; + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + return "End.X PSP"; + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + return "End.X PSP/USD"; case SRV6_ENDPOINT_BEHAVIOR_END_DT6: return "End.DT6"; case SRV6_ENDPOINT_BEHAVIOR_END_DT4: @@ -209,8 +225,16 @@ srv6_endpoint_behavior_codepoint2str(enum srv6_endpoint_behavior_codepoint behav return "End.DT46"; case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: return "uN"; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + return "uN PSP"; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + return "uN PSP/USD"; case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: return "uA"; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + return "uA PSP"; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: + return "uA PSP/USD"; case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: return "uDT6"; case SRV6_ENDPOINT_BEHAVIOR_END_DT4_USID: @@ -297,6 +321,7 @@ struct srv6_sid_ctx { struct in_addr nh4; struct in6_addr nh6; vrf_id_t vrf_id; + ifindex_t ifindex; }; static inline const char *seg6_mode2str(enum seg6_mode_t mode) diff --git a/lib/yang.c b/lib/yang.c index dd48d8861b..a57b247634 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -1357,9 +1357,21 @@ uint32_t yang_get_list_elements_count(const struct lyd_node *node) } while (node); return count; } +int yang_get_key_pred_strlen(const struct lysc_node *snode, const struct yang_list_keys *keys) +{ + const struct lysc_node_leaf *skey; + size_t len = 0; + size_t i = 0; + + LY_FOR_KEYS (snode, skey) { + /* [%s='%s'] */ + len += 5 + strlen(skey->name) + strlen(keys->key[i]); + i++; + } + return len; +} -int yang_get_key_preds(char *s, const struct lysc_node *snode, - struct yang_list_keys *keys, ssize_t space) +int yang_get_key_preds(char *s, const struct lysc_node *snode, const struct yang_list_keys *keys, ssize_t space) { const struct lysc_node_leaf *skey; ssize_t len2, len = 0; diff --git a/lib/yang.h b/lib/yang.h index 748f089037..3877a421c5 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -20,6 +20,8 @@ extern "C" { #endif +struct frr_yang_module_info; + /* Maximum XPath length. */ #define XPATH_MAXLEN 1024 @@ -45,6 +47,7 @@ struct yang_module { RB_ENTRY(yang_module) entry; const char *name; const struct lys_module *info; + const struct frr_yang_module_info *frr_info; #ifdef HAVE_SYSREPO sr_subscription_ctx_t *sr_subscription; struct event *sr_thread; @@ -879,7 +882,11 @@ bool yang_is_last_level_dnode(const struct lyd_node *dnode); /* Create a YANG predicate string based on the keys */ extern int yang_get_key_preds(char *s, const struct lysc_node *snode, - struct yang_list_keys *keys, ssize_t space); + const struct yang_list_keys *keys, ssize_t space); + +/* Get the length of the predicate string based on the keys */ +extern int yang_get_key_pred_strlen(const struct lysc_node *snode, + const struct yang_list_keys *keys); /* Get YANG keys from an existing dnode */ extern int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys); diff --git a/ospf6d/ospf6d.h b/ospf6d/ospf6d.h index c927ee7566..8ae7052840 100644 --- a/ospf6d/ospf6d.h +++ b/ospf6d/ospf6d.h @@ -18,22 +18,6 @@ extern struct event_loop *master; /* OSPF config processing timer thread */ extern struct event *t_ospf6_cfg; -/* Historical for KAME. */ -#ifndef IPV6_JOIN_GROUP -#ifdef IPV6_ADD_MEMBERSHIP -#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP -#endif /* IPV6_ADD_MEMBERSHIP. */ -#ifdef IPV6_JOIN_MEMBERSHIP -#define IPV6_JOIN_GROUP IPV6_JOIN_MEMBERSHIP -#endif /* IPV6_JOIN_MEMBERSHIP. */ -#endif /* ! IPV6_JOIN_GROUP*/ - -#ifndef IPV6_LEAVE_GROUP -#ifdef IPV6_DROP_MEMBERSHIP -#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP -#endif /* IPV6_DROP_MEMBERSHIP */ -#endif /* ! IPV6_LEAVE_GROUP */ - #define MSG_OK 0 #define MSG_NG 1 diff --git a/ospfd/ospf_lsa.c b/ospfd/ospf_lsa.c index 1d5c0be5c4..15068ec820 100644 --- a/ospfd/ospf_lsa.c +++ b/ospfd/ospf_lsa.c @@ -87,16 +87,6 @@ bool ospf_check_dna_lsa(const struct ospf_lsa *lsa) : false); } -struct timeval int2tv(int a) -{ - struct timeval ret; - - ret.tv_sec = a; - ret.tv_usec = 0; - - return ret; -} - struct timeval msec2tv(int a) { struct timeval ret; diff --git a/ospfd/ospf_lsa.h b/ospfd/ospf_lsa.h index f58bbde07a..309d506ea6 100644 --- a/ospfd/ospf_lsa.h +++ b/ospfd/ospf_lsa.h @@ -224,7 +224,6 @@ enum lsid_status { LSID_AVAILABLE = 0, LSID_CHANGE, LSID_NOT_AVAILABLE }; /* Prototypes. */ /* XXX: Eek, time functions, similar are in lib/thread.c */ -extern struct timeval int2tv(int); extern struct timeval msec2tv(int a); extern int tv2msec(struct timeval tv); diff --git a/pimd/pim6_cmd.c b/pimd/pim6_cmd.c index 40bd7caf7d..8297911828 100644 --- a/pimd/pim6_cmd.c +++ b/pimd/pim6_cmd.c @@ -1649,6 +1649,19 @@ ALIAS_YANG(interface_ipv6_mld_limits, "Limit number of MLDv2 sources to track\n" "Limit number of MLD group memberships to track\n") +DEFPY_YANG(interface_ipv6_mld_immediate_leave, + interface_ipv6_mld_immediate_leave_cmd, + "[no] ipv6 mld immediate-leave", + NO_STR + IPV6_STR + IFACE_MLD_STR + "Immediately drop group memberships on receiving Leave (MLDv1 only)\n") +{ + nb_cli_enqueue_change(vty, "./immediate-leave", NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL); +} + DEFPY (interface_ipv6_mld_query_interval, interface_ipv6_mld_query_interval_cmd, "ipv6 mld query-interval (1-65535)$q_interval", @@ -1790,6 +1803,34 @@ DEFPY (interface_no_ipv6_mld_last_member_query_interval, return gm_process_no_last_member_query_interval_cmd(vty); } +DEFPY_YANG(interface_ipv6_pim_neighbor_prefix_list, + interface_ipv6_pim_neighbor_prefix_list_cmd, + "[no] ipv6 pim allowed-neighbors prefix-list PREFIXLIST6_NAME$prefix_list", + NO_STR + IP_STR + PIM_STR + "Restrict allowed PIM neighbors\n" + "Use prefix-list to filter neighbors\n" + "Name of a prefix-list\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./neighbor-filter-prefix-list", NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./neighbor-filter-prefix-list", NB_OP_MODIFY, + prefix_list); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL); +} + +ALIAS(interface_ipv6_pim_neighbor_prefix_list, + interface_no_ipv6_pim_neighbor_prefix_list_cmd, + "no ipv6 pim allowed-neighbors [prefix-list]", + NO_STR + IP_STR + PIM_STR + "Restrict allowed PIM neighbors\n" + "Use prefix-list to filter neighbors\n") + DEFPY (show_ipv6_pim_rp, show_ipv6_pim_rp_cmd, "show ipv6 pim [vrf NAME] rp-info [X:X::X:X/M$group] [json$json]", @@ -2944,6 +2985,7 @@ void pim_cmd_init(void) install_element(INTERFACE_NODE, &interface_ipv6_mld_static_group_cmd); install_element(INTERFACE_NODE, &interface_ipv6_mld_version_cmd); install_element(INTERFACE_NODE, &interface_no_ipv6_mld_version_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_mld_immediate_leave_cmd); install_element(INTERFACE_NODE, &interface_ipv6_mld_query_interval_cmd); install_element(INTERFACE_NODE, &interface_no_ipv6_mld_query_interval_cmd); @@ -2959,6 +3001,8 @@ void pim_cmd_init(void) &interface_ipv6_mld_last_member_query_interval_cmd); install_element(INTERFACE_NODE, &interface_no_ipv6_mld_last_member_query_interval_cmd); + install_element(INTERFACE_NODE, &interface_ipv6_pim_neighbor_prefix_list_cmd); + install_element(INTERFACE_NODE, &interface_no_ipv6_pim_neighbor_prefix_list_cmd); install_element(VIEW_NODE, &show_ipv6_pim_rp_cmd); install_element(VIEW_NODE, &show_ipv6_pim_rp_vrf_all_cmd); diff --git a/pimd/pim6_mld.c b/pimd/pim6_mld.c index d7e0314d3b..2546166d0a 100644 --- a/pimd/pim6_mld.c +++ b/pimd/pim6_mld.c @@ -448,18 +448,23 @@ static void gm_sg_update(struct gm_sg *sg, bool has_expired) desired == GM_SG_NOPRUNE_EXPIRING) { struct gm_query_timers timers; - timers.qrv = gm_ifp->cur_qrv; - timers.max_resp_ms = gm_ifp->cur_max_resp; - timers.qqic_ms = gm_ifp->cur_query_intv_trig; - timers.fuzz = gm_ifp->cfg_timing_fuzz; + if (!pim_ifp->gmp_immediate_leave) { + timers.qrv = gm_ifp->cur_qrv; + timers.max_resp_ms = gm_ifp->cur_max_resp; + timers.qqic_ms = gm_ifp->cur_query_intv_trig; + timers.fuzz = gm_ifp->cfg_timing_fuzz; + + gm_expiry_calc(&timers); + } else + memset(&timers.expire_wait, 0, sizeof(timers.expire_wait)); - gm_expiry_calc(&timers); gm_sg_timer_start(gm_ifp, sg, timers.expire_wait); EVENT_OFF(sg->t_sg_query); sg->query_sbit = false; /* Trigger the specific queries only for querier. */ - if (IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest)) { + if (!pim_ifp->gmp_immediate_leave && + IPV6_ADDR_SAME(&gm_ifp->querier, &pim_ifp->ll_lowest)) { sg->n_query = gm_ifp->cur_lmqc; gm_trigger_specific(sg); } @@ -1102,11 +1107,24 @@ static void gm_handle_v1_leave(struct gm_if *gm_ifp, if (grp) { old_grp = gm_packet_sg_find(grp, GM_SUB_POS, subscriber); if (old_grp) { + const struct pim_interface *pim_ifp = gm_ifp->ifp->info; + struct gm_packet_sg *item; + gm_packet_sg_drop(old_grp); - gm_sg_update(grp, false); -/* TODO "need S,G PRUNE => NO_INFO transition here" */ + /* + * If immediate leave drop others subscribers and proceed + * to expire the MLD join. + */ + if (pim_ifp->gmp_immediate_leave) { + frr_each_safe (gm_packet_sg_subs, grp->subs_positive, item) { + gm_packet_sg_drop(item); + } + gm_sg_update(grp, true); + } else + gm_sg_update(grp, false); + /* TODO "need S,G PRUNE => NO_INFO transition here" */ } } diff --git a/pimd/pim_autorp.c b/pimd/pim_autorp.c index dc077dbbd6..d3f3517efd 100644 --- a/pimd/pim_autorp.c +++ b/pimd/pim_autorp.c @@ -967,6 +967,7 @@ err: static bool pim_autorp_socket_enable(struct pim_autorp *autorp) { int fd; + struct interface *ifp; /* Return early if socket is already enabled */ if (autorp->sock != -1) @@ -980,6 +981,13 @@ static bool pim_autorp_socket_enable(struct pim_autorp *autorp) return false; } + if (vrf_bind(autorp->pim->vrf->vrf_id, fd, NULL)) { + zlog_warn("Could not bind autorp socket to vrf fd=%d: vrf_id=%d: errno=%d: %s", + fd, autorp->pim->vrf->vrf_id, errno, safe_strerror(errno)); + close(fd); + return false; + } + if (!pim_autorp_setup(fd)) { zlog_warn("Could not setup autorp socket fd=%d: errno=%d: %s", fd, errno, safe_strerror(errno)); @@ -990,6 +998,11 @@ static bool pim_autorp_socket_enable(struct pim_autorp *autorp) autorp->sock = fd; + /* Join autorp groups on all pim enabled interfaces in the VRF */ + FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { + pim_autorp_add_ifp(ifp); + } + if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP socket enabled (fd=%u)", __func__, fd); @@ -1002,6 +1015,7 @@ static bool pim_autorp_socket_disable(struct pim_autorp *autorp) if (autorp->sock == -1) return true; + /* No need to leave the autorp groups explicitly, they are left when the socket is closed */ if (close(autorp->sock)) { zlog_warn("Failure closing autorp socket: fd=%d errno=%d: %s", autorp->sock, errno, safe_strerror(errno)); @@ -1428,10 +1442,10 @@ void pim_autorp_add_ifp(struct interface *ifp) { /* Add a new interface for autorp * When autorp is enabled, we must join the autorp groups on all - * pim/multicast interfaces. When autorp first starts, if finds all - * current multicast interfaces and joins on them. If a new interface - * comes up or is configured for multicast after autorp is running, then - * this method will add it for autorp-> + * pim/multicast interfaces. When autorp becomes enabled, it finds all + * current pim enabled interfaces and joins the autorp groups on them. + * Any new interfaces added after autorp is enabled will use this function + * to join the autorp groups * This is called even when adding a new pim interface that is not yet * active, so make sure the check, it'll call in again once the interface is up. */ @@ -1441,7 +1455,8 @@ void pim_autorp_add_ifp(struct interface *ifp) pim_ifp = ifp->info; if (CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE) && pim_ifp && pim_ifp->pim_enable) { pim = pim_ifp->pim; - if (pim && pim->autorp && pim->autorp->do_discovery) { + if (pim && pim->autorp && + (pim->autorp->do_discovery || pim->autorp->send_rp_discovery)) { if (PIM_DEBUG_AUTORP) zlog_debug("%s: Adding interface %s to AutoRP, joining AutoRP groups", __func__, ifp->name); @@ -1477,44 +1492,37 @@ void pim_autorp_rm_ifp(struct interface *ifp) void pim_autorp_start_discovery(struct pim_instance *pim) { - struct interface *ifp; struct pim_autorp *autorp = pim->autorp; + if (autorp->do_discovery) + return; + + autorp->do_discovery = true; + /* Make sure the socket is open and ready */ if (!pim_autorp_socket_enable(autorp)) { zlog_err("%s: AutoRP failed to open socket", __func__); return; } - if (!autorp->do_discovery) { - autorp->do_discovery = true; - autorp_read_on(autorp); - - FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { - pim_autorp_add_ifp(ifp); - } + autorp_read_on(autorp); - if (PIM_DEBUG_AUTORP) - zlog_debug("%s: AutoRP Discovery started", __func__); - } + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Discovery started", __func__); } void pim_autorp_stop_discovery(struct pim_instance *pim) { - struct interface *ifp; struct pim_autorp *autorp = pim->autorp; - if (autorp->do_discovery) { - FOR_ALL_INTERFACES (autorp->pim->vrf, ifp) { - pim_autorp_rm_ifp(ifp); - } + if (!autorp->do_discovery) + return; - autorp->do_discovery = false; - autorp_read_off(autorp); + autorp->do_discovery = false; + autorp_read_off(autorp); - if (PIM_DEBUG_AUTORP) - zlog_debug("%s: AutoRP Discovery stopped", __func__); - } + if (PIM_DEBUG_AUTORP) + zlog_debug("%s: AutoRP Discovery stopped", __func__); /* Close the socket if we need to */ if (pim_autorp_should_close(autorp) && !pim_autorp_socket_disable(autorp)) @@ -1549,7 +1557,10 @@ void pim_autorp_init(struct pim_instance *pim) if (PIM_DEBUG_AUTORP) zlog_debug("%s: AutoRP Initialized", __func__); +} +void pim_autorp_enable(struct pim_instance *pim) +{ /* Start AutoRP discovery by default on startup */ pim_autorp_start_discovery(pim); } diff --git a/pimd/pim_autorp.h b/pimd/pim_autorp.h index e4c6530109..88aebe5b7d 100644 --- a/pimd/pim_autorp.h +++ b/pimd/pim_autorp.h @@ -173,6 +173,7 @@ void pim_autorp_rm_ifp(struct interface *ifp); void pim_autorp_start_discovery(struct pim_instance *pim); void pim_autorp_stop_discovery(struct pim_instance *pim); void pim_autorp_init(struct pim_instance *pim); +void pim_autorp_enable(struct pim_instance *pim); void pim_autorp_finish(struct pim_instance *pim); int pim_autorp_config_write(struct pim_instance *pim, struct vty *vty); void pim_autorp_show_autorp(struct vty *vty, struct pim_instance *pim, const char *component, diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index fa9c6f9537..f838c401e3 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -5693,6 +5693,19 @@ ALIAS_YANG(interface_ip_igmp_limits, "Limit number of IGMPv3 sources to track\n" "Limit number of IGMP group memberships to track\n") +DEFPY_YANG(interface_ip_igmp_immediate_leave, + interface_ip_igmp_immediate_leave_cmd, + "[no] ip igmp immediate-leave", + NO_STR + IP_STR + IFACE_IGMP_STR + "Immediately drop group memberships on receiving Leave (IGMPv2 only)\n") +{ + nb_cli_enqueue_change(vty, "./immediate-leave", NB_OP_MODIFY, no ? "false" : "true"); + + return nb_cli_apply_changes(vty, FRR_GMP_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL); +} + DEFUN (interface_ip_pim_drprio, interface_ip_pim_drprio_cmd, "ip pim drpriority (0-4294967295)", @@ -6022,6 +6035,34 @@ DEFPY (interface_ip_igmp_proxy, } +DEFPY_YANG(interface_ip_pim_neighbor_prefix_list, + interface_ip_pim_neighbor_prefix_list_cmd, + "[no] ip pim allowed-neighbors prefix-list WORD", + NO_STR + IP_STR + "pim multicast routing\n" + "Restrict allowed PIM neighbors\n" + "Use prefix-list to filter neighbors\n" + "Name of a prefix-list\n") +{ + if (no) + nb_cli_enqueue_change(vty, "./neighbor-filter-prefix-list", NB_OP_DESTROY, NULL); + else + nb_cli_enqueue_change(vty, "./neighbor-filter-prefix-list", NB_OP_MODIFY, + prefix_list); + + return nb_cli_apply_changes(vty, FRR_PIM_INTERFACE_XPATH, FRR_PIM_AF_XPATH_VAL); +} + +ALIAS (interface_ip_pim_neighbor_prefix_list, + interface_no_ip_pim_neighbor_prefix_list_cmd, + "no ip pim allowed-neighbors [prefix-list]", + NO_STR + IP_STR + "pim multicast routing\n" + "Restrict allowed PIM neighbors\n" + "Use prefix-list to filter neighbors\n") + DEFUN (debug_igmp, debug_igmp_cmd, "debug igmp", @@ -9140,6 +9181,7 @@ void pim_cmd_init(void) install_element(INTERFACE_NODE, &interface_ip_igmp_proxy_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_limits_cmd); install_element(INTERFACE_NODE, &no_interface_ip_igmp_limits_cmd); + install_element(INTERFACE_NODE, &interface_ip_igmp_immediate_leave_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_activeactive_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_ssm_cmd); install_element(INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd); @@ -9155,6 +9197,8 @@ void pim_cmd_init(void) install_element(INTERFACE_NODE, &interface_no_ip_pim_boundary_oil_cmd); install_element(INTERFACE_NODE, &interface_ip_pim_boundary_acl_cmd); install_element(INTERFACE_NODE, &interface_ip_igmp_query_generate_cmd); + install_element(INTERFACE_NODE, &interface_ip_pim_neighbor_prefix_list_cmd); + install_element(INTERFACE_NODE, &interface_no_ip_pim_neighbor_prefix_list_cmd); // Static mroutes NEB install_element(INTERFACE_NODE, &interface_ip_mroute_cmd); diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index 8ec51ddc39..e0b157b8f6 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -218,6 +218,7 @@ void pim_if_delete(struct interface *ifp) if (pim_ifp->bfd_config.profile) XFREE(MTYPE_TMP, pim_ifp->bfd_config.profile); + XFREE(MTYPE_PIM_PLIST_NAME, pim_ifp->nbr_plist); XFREE(MTYPE_PIM_INTERFACE, pim_ifp); ifp->info = NULL; @@ -1900,9 +1901,7 @@ static int pim_ifp_up(struct interface *ifp) } #if PIM_IPV == 4 - if (pim->autorp && pim->autorp->do_discovery && pim_ifp && - pim_ifp->pim_enable) - pim_autorp_add_ifp(ifp); + pim_autorp_add_ifp(ifp); #endif pim_cand_addrs_changed(); @@ -2019,8 +2018,7 @@ void pim_pim_interface_delete(struct interface *ifp) return; #if PIM_IPV == 4 - if (pim_ifp->pim_enable) - pim_autorp_rm_ifp(ifp); + pim_autorp_rm_ifp(ifp); #endif pim_ifp->pim_enable = false; diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h index 0a7993fd27..b0befcfcba 100644 --- a/pimd/pim_iface.h +++ b/pimd/pim_iface.h @@ -107,6 +107,9 @@ struct pim_interface { uint32_t gm_source_limit, gm_group_limit; + /* IGMPv2 only/MLDv1 only immediate leave */ + bool gmp_immediate_leave; + int pim_sock_fd; /* PIM socket file descriptor */ struct event *t_pim_sock_read; /* thread for reading PIM socket */ int64_t pim_sock_creation; /* timestamp of PIM socket creation */ @@ -118,6 +121,7 @@ struct pim_interface { uint32_t pim_generation_id; uint16_t pim_propagation_delay_msec; /* config */ uint16_t pim_override_interval_msec; /* config */ + char *nbr_plist; struct list *pim_neighbor_list; /* list of struct pim_neighbor */ struct list *upstream_switch_list; struct pim_ifchannel_rb ifchannel_rb; diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c index 7cb168dc5d..2de2b32688 100644 --- a/pimd/pim_igmpv3.c +++ b/pimd/pim_igmpv3.c @@ -728,9 +728,25 @@ static void toin_incl(struct gm_group *group, int num_sources, static void toin_excl(struct gm_group *group, int num_sources, struct in_addr *sources) { + struct listnode *src_node, *src_next; + struct pim_interface *pim_ifp = group->interface->info; int num_sources_tosend; int i; + if (group->igmp_version == 2 && pim_ifp->gmp_immediate_leave) { + struct gm_source *src; + + if (PIM_DEBUG_GM_TRACE) + zlog_debug("IGMP(v2) Immediate-leave group %pI4 on %s", &group->group_addr, + group->interface->name); + + igmp_group_timer_on(group, 0, group->interface->name); + + for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_next, src)) + igmp_source_delete(src); + return; + } + /* Set SEND flag for X (sources with timer > 0) */ num_sources_tosend = source_mark_send_flag_by_timer(group); @@ -1496,7 +1512,9 @@ void igmp_group_timer_lower_to_lmqt(struct gm_group *group) pim_ifp = ifp->info; ifname = ifp->name; - lmqi_dsec = pim_ifp->gm_specific_query_max_response_time_dsec; + lmqi_dsec = pim_ifp->gmp_immediate_leave + ? 0 + : pim_ifp->gm_specific_query_max_response_time_dsec; lmqc = pim_ifp->gm_last_member_query_count; lmqt_msec = PIM_IGMP_LMQT_MSEC( lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ @@ -1531,7 +1549,9 @@ void igmp_source_timer_lower_to_lmqt(struct gm_source *source) pim_ifp = ifp->info; ifname = ifp->name; - lmqi_dsec = pim_ifp->gm_specific_query_max_response_time_dsec; + lmqi_dsec = pim_ifp->gmp_immediate_leave + ? 0 + : pim_ifp->gm_specific_query_max_response_time_dsec; lmqc = pim_ifp->gm_last_member_query_count; lmqt_msec = PIM_IGMP_LMQT_MSEC( lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */ diff --git a/pimd/pim_instance.c b/pimd/pim_instance.c index 3945c5923d..f64b02e44d 100644 --- a/pimd/pim_instance.c +++ b/pimd/pim_instance.c @@ -181,8 +181,15 @@ static int pim_vrf_enable(struct vrf *vrf) zlog_debug("%s: for %s %u", __func__, vrf->name, vrf->vrf_id); + if (vrf_bind(vrf->vrf_id, pim->reg_sock, NULL) < 0) + zlog_warn("Failed to bind register socket to VRF %s", vrf->name); + pim_mroute_socket_enable(pim); +#if PIM_IPV == 4 + pim_autorp_enable(pim); +#endif + FOR_ALL_INTERFACES (vrf, ifp) { if (!ifp->info) continue; diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c index 6c13e1324f..30daa3a929 100644 --- a/pimd/pim_mroute.c +++ b/pimd/pim_mroute.c @@ -876,17 +876,11 @@ int pim_mroute_socket_enable(struct pim_instance *pim) pim->vrf->name); #endif -#ifdef SO_BINDTODEVICE - if (pim->vrf->vrf_id != VRF_DEFAULT - && setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, - pim->vrf->name, strlen(pim->vrf->name))) { - zlog_warn("Could not setsockopt SO_BINDTODEVICE: %s", - safe_strerror(errno)); + if (vrf_bind(pim->vrf->vrf_id, fd, NULL)) { + zlog_warn("Could not bind to vrf: %s", safe_strerror(errno)); close(fd); return -3; } -#endif - } pim->mroute_socket = fd; diff --git a/pimd/pim_nb.c b/pimd/pim_nb.c index 62c5d531d9..9a2fc5f3cd 100644 --- a/pimd/pim_nb.c +++ b/pimd/pim_nb.c @@ -315,6 +315,13 @@ const struct frr_yang_module_info frr_pim_info = { } }, { + .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/neighbor-filter-prefix-list", + .cbs = { + .modify = lib_interface_pim_address_family_nbr_plist_modify, + .destroy = lib_interface_pim_address_family_nbr_plist_destroy, + } + }, + { .xpath = "/frr-interface:lib/interface/frr-pim:pim/address-family/bfd", .cbs = { .create = lib_interface_pim_address_family_bfd_create, @@ -743,7 +750,13 @@ const struct frr_yang_module_info frr_gmp_info = { .modify = lib_interface_gmp_address_family_proxy_modify, } }, -{ + { + .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/immediate-leave", + .cbs = { + .modify = lib_interface_gmp_immediate_leave_modify, + } + }, + { .xpath = "/frr-interface:lib/interface/frr-gmp:gmp/address-family/static-group", .cbs = { .create = lib_interface_gmp_address_family_static_group_create, diff --git a/pimd/pim_nb.h b/pimd/pim_nb.h index 1656313fc2..e9faf875b0 100644 --- a/pimd/pim_nb.h +++ b/pimd/pim_nb.h @@ -110,6 +110,8 @@ int routing_control_plane_protocols_control_plane_protocol_pim_address_family_mc struct nb_cb_modify_args *args); int lib_interface_pim_address_family_dr_priority_modify( struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_nbr_plist_modify(struct nb_cb_modify_args *args); +int lib_interface_pim_address_family_nbr_plist_destroy(struct nb_cb_destroy_args *args); int lib_interface_pim_address_family_create(struct nb_cb_create_args *args); int lib_interface_pim_address_family_destroy(struct nb_cb_destroy_args *args); int lib_interface_pim_address_family_pim_enable_modify( @@ -289,6 +291,7 @@ int lib_interface_gmp_address_family_static_group_destroy( struct nb_cb_destroy_args *args); int lib_interface_gm_max_sources_modify(struct nb_cb_modify_args *args); int lib_interface_gm_max_groups_modify(struct nb_cb_modify_args *args); +int lib_interface_gmp_immediate_leave_modify(struct nb_cb_modify_args *args); /* * Callback registered with routing_nb lib to validate only diff --git a/pimd/pim_nb_config.c b/pimd/pim_nb_config.c index c926696610..1be5e9cb88 100644 --- a/pimd/pim_nb_config.c +++ b/pimd/pim_nb_config.c @@ -2163,6 +2163,55 @@ int lib_interface_pim_address_family_hello_holdtime_destroy( return NB_OK; } + +/* + * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/neighbor-filter-prefix-list + */ +int lib_interface_pim_address_family_nbr_plist_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + const char *plist; + + plist = yang_dnode_get_string(args->dnode, NULL); + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + + XFREE(MTYPE_PIM_PLIST_NAME, pim_ifp->nbr_plist); + pim_ifp->nbr_plist = XSTRDUP(MTYPE_PIM_PLIST_NAME, plist); + break; + } + + return NB_OK; +} + +int lib_interface_pim_address_family_nbr_plist_destroy(struct nb_cb_destroy_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + XFREE(MTYPE_PIM_PLIST_NAME, pim_ifp->nbr_plist); + break; + } + + return NB_OK; +} + /* * XPath: /frr-interface:lib/interface/frr-pim:pim/address-family/bfd */ @@ -4493,6 +4542,29 @@ int lib_interface_gmp_address_family_robustness_variable_modify( } /* + * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/immediate-leave + */ +int lib_interface_gmp_immediate_leave_modify(struct nb_cb_modify_args *args) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + + switch (args->event) { + case NB_EV_VALIDATE: + case NB_EV_PREPARE: + case NB_EV_ABORT: + break; + case NB_EV_APPLY: + ifp = nb_running_get_entry(args->dnode, NULL, true); + pim_ifp = ifp->info; + pim_ifp->gmp_immediate_leave = yang_dnode_get_bool(args->dnode, NULL); + break; + } + + return NB_OK; +} + +/* * XPath: /frr-interface:lib/interface/frr-gmp:gmp/address-family/proxy */ int lib_interface_gmp_address_family_proxy_modify(struct nb_cb_modify_args *args) diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c index a41bbacea7..fb78e39022 100644 --- a/pimd/pim_pim.c +++ b/pimd/pim_pim.c @@ -149,6 +149,9 @@ int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, uint32_t pim_msg_len = 0; uint16_t pim_checksum; /* received checksum */ uint16_t checksum; /* computed checksum */ + struct pim_interface *pim_ifp = ifp->info; + struct prefix src_prefix; + struct prefix_list *nbr_plist = NULL; struct pim_neighbor *neigh; struct pim_msg_header *header; bool no_fwd; @@ -205,6 +208,41 @@ int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len, return -1; } + switch (header->type) { + case PIM_MSG_TYPE_HELLO: + case PIM_MSG_TYPE_JOIN_PRUNE: + case PIM_MSG_TYPE_ASSERT: + if (pim_ifp == NULL || pim_ifp->nbr_plist == NULL) + break; + + nbr_plist = prefix_list_lookup(PIM_AFI, pim_ifp->nbr_plist); + +#if PIM_IPV == 4 + src_prefix.family = AF_INET; + src_prefix.prefixlen = IPV4_MAX_BITLEN; + src_prefix.u.prefix4 = sg.src; +#else + src_prefix.family = AF_INET6; + src_prefix.prefixlen = IPV6_MAX_BITLEN; + src_prefix.u.prefix6 = sg.src; +#endif + + if (nbr_plist && + prefix_list_apply_ext(nbr_plist, NULL, &src_prefix, true) == PREFIX_PERMIT) + break; + +#if PIM_IPV == 4 + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("neighbor filter rejects packet %pI4 -> %pI4 on %s", + &ip_hdr->ip_src, &ip_hdr->ip_dst, ifp->name); +#else + if (PIM_DEBUG_PIM_PACKETS) + zlog_debug("neighbor filter rejects packet %pI6 -> %pI6 on %s", &sg.src, + &sg.grp, ifp->name); +#endif + return -1; + } + /* save received checksum */ pim_checksum = header->checksum; diff --git a/pimd/pim_register.c b/pimd/pim_register.c index f776a59b7f..29e658ef16 100644 --- a/pimd/pim_register.c +++ b/pimd/pim_register.c @@ -186,8 +186,9 @@ int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size) */ for (ALL_LIST_ELEMENTS_RO(up->sources, up_node, child)) { if (PIM_DEBUG_PIM_REG) - zlog_debug("Executing Reg stop for %s", - child->sg_str); + zlog_debug( + "Executing Reg stop for upstream child %s", + child->sg_str); pim_reg_stop_upstream(pim, child); } @@ -208,8 +209,9 @@ int pim_register_stop_recv(struct interface *ifp, uint8_t *buf, int buf_size) frr_each (rb_pim_upstream, &pim->upstream_head, up) { if (pim_addr_cmp(up->sg.grp, sg.grp) == 0) { if (PIM_DEBUG_PIM_REG) - zlog_debug("Executing Reg stop for %s", - up->sg_str); + zlog_debug( + "Executing Reg stop for upstream %s", + up->sg_str); pim_reg_stop_upstream(pim, up); } } @@ -682,9 +684,12 @@ int pim_register_recv(struct interface *ifp, pim_addr dest_addr, } } - if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) - || ((SwitchToSptDesiredOnRp(pim, &sg)) - && pim_upstream_inherited_olist(pim, upstream) == 0)) { + if ((upstream->sptbit == PIM_UPSTREAM_SPTBIT_TRUE) || + (PIM_UPSTREAM_FLAG_TEST_FHR(upstream->flags) && i_am_rp) || + ((SwitchToSptDesiredOnRp(pim, &sg)) && + pim_upstream_inherited_olist(pim, upstream) == 0)) { + zlog_debug("sending pim register stop message : %s ", + upstream->sg_str); pim_register_stop_send(ifp, &sg, dest_addr, src_addr); sentRegisterStop = 1; } else { diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c index 01e1321b25..e4603ff946 100644 --- a/pimd/pim_upstream.c +++ b/pimd/pim_upstream.c @@ -643,6 +643,12 @@ void pim_upstream_update_use_rpt(struct pim_upstream *up, if (pim_addr_is_any(up->sg.src)) return; + /* Ignore RP mapping when the upsteam state + * is NOT Joined on a FHR + */ + if (up->join_state == PIM_UPSTREAM_NOTJOINED && PIM_UPSTREAM_FLAG_TEST_FHR(up->flags)) + return; + old_use_rpt = !!PIM_UPSTREAM_FLAG_TEST_USE_RPT(up->flags); /* We will use the SPT (IIF=RPF_interface(S) if - diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index 64750a22f6..e37703be2b 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -471,6 +471,18 @@ int pim_config_write(struct vty *vty, int writes, struct interface *ifp, ++writes; } + /* IF ip/ipv6 igmp/mld immediate-leave */ + if (pim_ifp->gmp_immediate_leave) { + vty_out(vty, " " PIM_AF_NAME " " GM_AF_DBG " immediate-leave\n"); + ++writes; + } + + if (pim_ifp->nbr_plist) { + vty_out(vty, " " PIM_AF_NAME " pim allowed-neighbors prefix-list %s\n", + pim_ifp->nbr_plist); + ++writes; + } + /* IF ip pim drpriority */ if (pim_ifp->pim_dr_priority != PIM_DEFAULT_DR_PRIORITY) { vty_out(vty, " " PIM_AF_NAME " pim drpriority %u\n", diff --git a/ripd/rip_cli.c b/ripd/rip_cli.c index 5712a0b825..0bafc6f342 100644 --- a/ripd/rip_cli.c +++ b/ripd/rip_cli.c @@ -681,9 +681,9 @@ DEFPY_YANG (ip_rip_split_horizon, { const char *value; - if (no) + if (no && poisoned_reverse == NULL) value = "disabled"; - else if (poisoned_reverse) + else if (poisoned_reverse && no == NULL) value = "poison-reverse"; else value = "simple"; diff --git a/ripngd/ripng_interface.c b/ripngd/ripng_interface.c index 9ef9f89005..2b5d745bf4 100644 --- a/ripngd/ripng_interface.c +++ b/ripngd/ripng_interface.c @@ -26,14 +26,6 @@ #include "ripngd/ripngd.h" #include "ripngd/ripng_debug.h" -/* If RFC2133 definition is used. */ -#ifndef IPV6_JOIN_GROUP -#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP -#endif -#ifndef IPV6_LEAVE_GROUP -#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP -#endif - DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_IF, "ripng interface"); /* Static utility function. */ diff --git a/staticd/static_nb.c b/staticd/static_nb.c index ef363bfe7e..60dc3dc788 100644 --- a/staticd/static_nb.c +++ b/staticd/static_nb.c @@ -157,6 +157,27 @@ const struct frr_yang_module_info frr_staticd_info = { } }, { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/paths", + .cbs = { + .create = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_create, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/paths/interface", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_destroy, + } + }, + { + .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/paths/next-hop", + .cbs = { + .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_modify, + .destroy = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_destroy, + } + }, + { .xpath = "/frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/static-sids/sid/locator-name", .cbs = { .modify = routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify, diff --git a/staticd/static_nb.h b/staticd/static_nb.h index aa11f34021..282c9dcf11 100644 --- a/staticd/static_nb.h +++ b/staticd/static_nb.h @@ -96,6 +96,18 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routi struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_vrf_name_destroy( struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_create( + struct nb_cb_create_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_destroy( + struct nb_cb_destroy_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_modify( + struct nb_cb_modify_args *args); +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_destroy( + struct nb_cb_destroy_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify( struct nb_cb_modify_args *args); int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_destroy( @@ -183,6 +195,10 @@ int routing_control_plane_protocols_name_validate( #define FRR_STATIC_SRV6_SID_LOCATOR_NAME_XPATH "/locator-name" +#define FRR_STATIC_SRV6_SID_INTERFACE_XPATH "/paths[path-index=%u]/interface" + +#define FRR_STATIC_SRV6_SID_NEXTHOP_XPATH "/paths[path-index=%u]/next-hop" + #ifdef __cplusplus } #endif diff --git a/staticd/static_nb_config.c b/staticd/static_nb_config.c index e2ab1f2ffe..71df15fa61 100644 --- a/staticd/static_nb_config.c +++ b/staticd/static_nb_config.c @@ -1231,6 +1231,113 @@ int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routi /* * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/paths + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_create( + struct nb_cb_create_args *args) +{ + /* Actual setting is done in apply_finish */ + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_destroy( + struct nb_cb_destroy_args *args) +{ + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/paths/interface + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_modify( + struct nb_cb_modify_args *args) +{ + struct static_srv6_sid *sid; + const char *ifname; + + if (args->event != NB_EV_APPLY) + return NB_OK; + + sid = nb_running_get_entry(args->dnode, NULL, true); + + /* Release and uninstall existing SID, if any, before requesting the new one */ + if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) { + static_zebra_release_srv6_sid(sid); + UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID); + } + + if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) { + static_zebra_srv6_sid_uninstall(sid); + UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA); + } + + ifname = yang_dnode_get_string(args->dnode, "../interface"); + snprintf(sid->attributes.ifname, sizeof(sid->attributes.ifname), "%s", ifname); + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_interface_destroy( + struct nb_cb_destroy_args *args) +{ + return NB_OK; +} + +/* + * XPath: + * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/paths/next-hop + */ +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_modify( + struct nb_cb_modify_args *args) +{ + struct static_srv6_sid *sid; + struct ipaddr nexthop; + + switch (args->event) { + case NB_EV_VALIDATE: + zlog_info("validating nexthop %pI6", &nexthop.ipaddr_v6); + yang_dnode_get_ip(&nexthop, args->dnode, "../next-hop"); + if (!IS_IPADDR_V6(&nexthop)) { + snprintf(args->errmsg, args->errmsg_len, + "%% Nexthop must be an IPv6 address"); + return NB_ERR_VALIDATION; + } + break; + case NB_EV_ABORT: + case NB_EV_PREPARE: + break; + case NB_EV_APPLY: + sid = nb_running_get_entry(args->dnode, NULL, true); + + /* Release and uninstall existing SID, if any, before requesting the new one */ + if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) { + static_zebra_release_srv6_sid(sid); + UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID); + } + + if (CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA)) { + static_zebra_srv6_sid_uninstall(sid); + UNSET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_SENT_TO_ZEBRA); + } + + yang_dnode_get_ip(&nexthop, args->dnode, "../next-hop"); + sid->attributes.nh6 = nexthop.ipaddr_v6; + + break; + } + + return NB_OK; +} + +int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_paths_next_hop_destroy( + struct nb_cb_destroy_args *args) +{ + return NB_OK; +} + +/* + * XPath: * /frr-routing:routing/control-plane-protocols/control-plane-protocol/frr-staticd:staticd/segment-routing/srv6/locators/locator/static-sids/sid/vrf-name */ int routing_control_plane_protocols_control_plane_protocol_staticd_segment_routing_srv6_local_sids_sid_locator_name_modify( diff --git a/staticd/static_routes.c b/staticd/static_routes.c index cbe1c3c8c0..82eabd8d56 100644 --- a/staticd/static_routes.c +++ b/staticd/static_routes.c @@ -53,6 +53,11 @@ void zebra_stable_node_cleanup(struct route_table *table, /* Install static path into rib. */ void static_install_path(struct static_path *pn) { + struct static_nexthop *nh; + + frr_each (static_nexthop_list, &pn->nexthop_list, nh) + static_zebra_nht_register(nh, true); + if (static_nexthop_list_count(&pn->nexthop_list)) static_zebra_route_add(pn, true); } diff --git a/staticd/static_vty.c b/staticd/static_vty.c index 895846a1c7..6e9087363d 100644 --- a/staticd/static_vty.c +++ b/staticd/static_vty.c @@ -1199,13 +1199,18 @@ DEFUN_NOSH (static_srv6_sids, static_srv6_sids_cmd, } DEFPY_YANG(srv6_sid, srv6_sid_cmd, - "sid X:X::X:X/M locator NAME$locator_name behavior <uN | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>", + "sid X:X::X:X/M locator NAME$locator_name behavior <uN | uA interface INTERFACE$interface [nexthop X:X::X:X$nh6] | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>", "Configure SRv6 SID\n" "Specify SRv6 SID\n" "Locator name\n" "Specify Locator name\n" "Specify SRv6 SID behavior\n" "Apply the code to a uN SID\n" + "Behavior uA\n" + "Configure the interface\n" + "Interface name\n" + "Configure the nexthop\n" + "IPv6 address of the nexthop\n" "Apply the code to an uDT6 SID\n" "Configure VRF name\n" "Specify VRF name\n" @@ -1223,7 +1228,10 @@ DEFPY_YANG(srv6_sid, srv6_sid_cmd, char xpath_sid[XPATH_MAXLEN]; char xpath_behavior[XPATH_MAXLEN]; char xpath_vrf_name[XPATH_MAXLEN]; + char xpath_ifname[XPATH_MAXLEN]; + char xpath_nexthop[XPATH_MAXLEN]; char xpath_locator_name[XPATH_MAXLEN]; + char ab_xpath[XPATH_MAXLEN]; if (argv_find(argv, argc, "uN", &idx)) { behavior = SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID; @@ -1236,6 +1244,8 @@ DEFPY_YANG(srv6_sid, srv6_sid_cmd, } else if (argv_find(argv, argc, "uDT46", &idx)) { behavior = SRV6_ENDPOINT_BEHAVIOR_END_DT46_USID; vrf_name = argv[idx + 2]->arg; + } else if (argv_find(argv, argc, "uA", &idx)) { + behavior = SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID; } snprintf(xpath_srv6, sizeof(xpath_srv6), FRR_STATIC_SRV6_INFO_KEY_XPATH, @@ -1259,6 +1269,22 @@ DEFPY_YANG(srv6_sid, srv6_sid_cmd, nb_cli_enqueue_change(vty, xpath_vrf_name, NB_OP_MODIFY, vrf_name); } + if (interface) { + snprintf(ab_xpath, sizeof(ab_xpath), FRR_STATIC_SRV6_SID_INTERFACE_XPATH, 0); + strlcpy(xpath_ifname, xpath_sid, sizeof(xpath_ifname)); + strlcat(xpath_ifname, ab_xpath, sizeof(xpath_ifname)); + + nb_cli_enqueue_change(vty, xpath_ifname, NB_OP_MODIFY, interface); + } + + if (nh6_str) { + snprintf(ab_xpath, sizeof(ab_xpath), FRR_STATIC_SRV6_SID_NEXTHOP_XPATH, 0); + strlcpy(xpath_nexthop, xpath_sid, sizeof(xpath_nexthop)); + strlcat(xpath_nexthop, ab_xpath, sizeof(xpath_nexthop)); + + nb_cli_enqueue_change(vty, xpath_nexthop, NB_OP_MODIFY, nh6_str); + } + strlcpy(xpath_locator_name, xpath_sid, sizeof(xpath_locator_name)); strlcat(xpath_locator_name, FRR_STATIC_SRV6_SID_LOCATOR_NAME_XPATH, sizeof(xpath_locator_name)); @@ -1269,7 +1295,7 @@ DEFPY_YANG(srv6_sid, srv6_sid_cmd, } DEFPY_YANG(no_srv6_sid, no_srv6_sid_cmd, - "no sid X:X::X:X/M [locator NAME$locator_name] [behavior <uN | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>]", + "no sid X:X::X:X/M [locator NAME$locator_name] [behavior <uN | uA interface INTERFACE$interface [nexthop X:X::X:X$nh6] | uDT6 vrf VIEWVRFNAME | uDT4 vrf VIEWVRFNAME | uDT46 vrf VIEWVRFNAME>]", NO_STR "Configure SRv6 SID\n" "Specify SRv6 SID\n" @@ -1277,6 +1303,11 @@ DEFPY_YANG(no_srv6_sid, no_srv6_sid_cmd, "Specify Locator name\n" "Specify SRv6 SID behavior\n" "Apply the code to a uN SID\n" + "Behavior uA\n" + "Configure the interface\n" + "Interface name\n" + "Configure the nexthop\n" + "IPv6 address of the nexthop\n" "Apply the code to an uDT6 SID\n" "Configure VRF name\n" "Specify VRF name\n" @@ -1685,6 +1716,7 @@ static void srv6_sid_cli_show(struct vty *vty, const struct lyd_node *sid, bool { enum srv6_endpoint_behavior_codepoint srv6_behavior; struct prefix_ipv6 sid_value; + struct ipaddr nexthop; yang_dnode_get_ipv6p(&sid_value, sid, "sid"); @@ -1696,9 +1728,21 @@ static void srv6_sid_cli_show(struct vty *vty, const struct lyd_node *sid, bool case SRV6_ENDPOINT_BEHAVIOR_END: vty_out(vty, " behavior End"); break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + vty_out(vty, " behavior End PSP"); + break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + vty_out(vty, " behavior End PSP/USD"); + break; case SRV6_ENDPOINT_BEHAVIOR_END_X: vty_out(vty, " behavior End.X"); break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + vty_out(vty, " behavior End.X PSP"); + break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + vty_out(vty, " behavior End.X PSP/USD"); + break; case SRV6_ENDPOINT_BEHAVIOR_END_DT6: vty_out(vty, " behavior End.DT6"); break; @@ -1711,9 +1755,21 @@ static void srv6_sid_cli_show(struct vty *vty, const struct lyd_node *sid, bool case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: vty_out(vty, " behavior uN"); break; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + vty_out(vty, " behavior uN PSP"); + break; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + vty_out(vty, " behavior uN PSP/USD"); + break; case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: vty_out(vty, " behavior uA"); break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + vty_out(vty, " behavior uA PSP"); + break; + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: + vty_out(vty, " behavior uA PSP/USD"); + break; case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: vty_out(vty, " behavior uDT6"); break; @@ -1732,6 +1788,16 @@ static void srv6_sid_cli_show(struct vty *vty, const struct lyd_node *sid, bool if (yang_dnode_exists(sid, "vrf-name")) vty_out(vty, " vrf %s", yang_dnode_get_string(sid, "vrf-name")); + if (yang_dnode_exists(sid, "paths[path-index=0]/interface")) { + vty_out(vty, " interface %s", + yang_dnode_get_string(sid, "paths[path-index=0]/interface")); + + if (yang_dnode_exists(sid, "paths[path-index=0]/next-hop")) { + yang_dnode_get_ip(&nexthop, sid, "paths[path-index=0]/next-hop"); + vty_out(vty, " nexthop %pI6", &nexthop.ipaddr_v6); + } + } + vty_out(vty, "\n"); } diff --git a/staticd/static_zebra.c b/staticd/static_zebra.c index 9a794d4d02..a6521cccc6 100644 --- a/staticd/static_zebra.c +++ b/staticd/static_zebra.c @@ -323,6 +323,10 @@ void static_zebra_nht_register(struct static_nexthop *nh, bool reg) if (!static_zebra_nht_get_prefix(nh, &lookup.nh)) return; + + if (nh->nh_vrf_id == VRF_UNKNOWN) + return; + lookup.nh_vrf_id = nh->nh_vrf_id; lookup.safi = si->safi; @@ -631,9 +635,20 @@ void static_zebra_srv6_sid_install(struct static_srv6_sid *sid) } switch (sid->behavior) { + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: + action = ZEBRA_SEG6_LOCAL_ACTION_END; + SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_PSP); + break; case SRV6_ENDPOINT_BEHAVIOR_END: action = ZEBRA_SEG6_LOCAL_ACTION_END; break; + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: + action = ZEBRA_SEG6_LOCAL_ACTION_END; + SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID); + SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_PSP); + ctx.flv.lcblock_len = sid->locator->block_bits_length; + ctx.flv.lcnode_func_len = sid->locator->node_bits_length; + break; case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: action = ZEBRA_SEG6_LOCAL_ACTION_END; SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID); @@ -691,8 +706,26 @@ void static_zebra_srv6_sid_install(struct static_srv6_sid *sid) return; } break; - case SRV6_ENDPOINT_BEHAVIOR_END_X: case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + action = ZEBRA_SEG6_LOCAL_ACTION_END_X; + ctx.nh6 = sid->attributes.nh6; + ifp = if_lookup_by_name(sid->attributes.ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn("Failed to install SID %pFX: failed to get interface %s", + &sid->addr, sid->attributes.ifname); + return; + } + SET_SRV6_FLV_OP(ctx.flv.flv_ops, ZEBRA_SEG6_LOCAL_FLV_OP_NEXT_CSID); + ctx.flv.lcblock_len = sid->locator->block_bits_length; + ctx.flv.lcnode_func_len = sid->locator->node_bits_length; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: case SRV6_ENDPOINT_BEHAVIOR_RESERVED: zlog_warn("unsupported behavior: %u", sid->behavior); @@ -764,7 +797,9 @@ void static_zebra_srv6_sid_uninstall(struct static_srv6_sid *sid) switch (sid->behavior) { case SRV6_ENDPOINT_BEHAVIOR_END: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: break; case SRV6_ENDPOINT_BEHAVIOR_END_DT6: case SRV6_ENDPOINT_BEHAVIOR_END_DT6_USID: @@ -811,8 +846,22 @@ void static_zebra_srv6_sid_uninstall(struct static_srv6_sid *sid) return; } break; - case SRV6_ENDPOINT_BEHAVIOR_END_X: case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + ctx.nh6 = sid->attributes.nh6; + ifp = if_lookup_by_name(sid->attributes.ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn("Failed to install SID %pFX: failed to get interface %s", + &sid->addr, sid->attributes.ifname); + return; + } + break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: case SRV6_ENDPOINT_BEHAVIOR_RESERVED: zlog_warn("unsupported behavior: %u", sid->behavior); @@ -865,6 +914,7 @@ extern void static_zebra_request_srv6_sid(struct static_srv6_sid *sid) struct srv6_sid_ctx ctx = {}; int ret = 0; struct vrf *vrf; + struct interface *ifp; if (!sid) return; @@ -872,7 +922,9 @@ extern void static_zebra_request_srv6_sid(struct static_srv6_sid *sid) /* convert `srv6_endpoint_behavior_codepoint` to `seg6local_action_t` */ switch (sid->behavior) { case SRV6_ENDPOINT_BEHAVIOR_END: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END; break; case SRV6_ENDPOINT_BEHAVIOR_END_DT6: @@ -914,8 +966,24 @@ extern void static_zebra_request_srv6_sid(struct static_srv6_sid *sid) } break; - case SRV6_ENDPOINT_BEHAVIOR_END_X: case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_X; + ctx.nh6 = sid->attributes.nh6; + ifp = if_lookup_by_name(sid->attributes.ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn("Failed to request SRv6 SID %pFX: interface %s does not exist", + &sid->addr, sid->attributes.ifname); + return; + } + ctx.ifindex = ifp->ifindex; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: case SRV6_ENDPOINT_BEHAVIOR_RESERVED: zlog_warn("unsupported behavior: %u", sid->behavior); @@ -933,6 +1001,7 @@ extern void static_zebra_release_srv6_sid(struct static_srv6_sid *sid) struct srv6_sid_ctx ctx = {}; struct vrf *vrf; int ret = 0; + struct interface *ifp; if (!sid || !CHECK_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID)) return; @@ -940,7 +1009,9 @@ extern void static_zebra_release_srv6_sid(struct static_srv6_sid *sid) /* convert `srv6_endpoint_behavior_codepoint` to `seg6local_action_t` */ switch (sid->behavior) { case SRV6_ENDPOINT_BEHAVIOR_END: + case SRV6_ENDPOINT_BEHAVIOR_END_PSP: case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP: ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END; break; case SRV6_ENDPOINT_BEHAVIOR_END_DT6: @@ -982,8 +1053,24 @@ extern void static_zebra_release_srv6_sid(struct static_srv6_sid *sid) } break; - case SRV6_ENDPOINT_BEHAVIOR_END_X: case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID: + ctx.behavior = ZEBRA_SEG6_LOCAL_ACTION_END_X; + ctx.nh6 = sid->attributes.nh6; + ifp = if_lookup_by_name(sid->attributes.ifname, VRF_DEFAULT); + if (!ifp) { + zlog_warn("Failed to request SRv6 SID %pFX: interface %s does not exist", + &sid->addr, sid->attributes.ifname); + return; + } + ctx.ifindex = ifp->ifindex; + break; + case SRV6_ENDPOINT_BEHAVIOR_END_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_NEXT_CSID_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_PSP_USD: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP: + case SRV6_ENDPOINT_BEHAVIOR_END_X_NEXT_CSID_PSP_USD: case SRV6_ENDPOINT_BEHAVIOR_OPAQUE: case SRV6_ENDPOINT_BEHAVIOR_RESERVED: zlog_warn("unsupported behavior: %u", sid->behavior); @@ -1199,6 +1286,9 @@ static int static_zebra_srv6_sid_notify(ZAPI_CALLBACK_ARGS) return 0; } + if (!IPV6_ADDR_SAME(&ctx.nh6, &in6addr_any)) + sid->attributes.nh6 = ctx.nh6; + SET_FLAG(sid->flags, STATIC_FLAG_SRV6_SID_VALID); /* diff --git a/tests/lib/northbound/test_oper_data.c b/tests/lib/northbound/test_oper_data.c index 0b334c6522..a38325173a 100644 --- a/tests/lib/northbound/test_oper_data.c +++ b/tests/lib/northbound/test_oper_data.c @@ -236,13 +236,9 @@ static int frr_test_module_vrfs_vrf_ping(struct nb_cb_rpc_args *args) return NB_OK; } -/* - * XPath: /frr-test-module:frr-test-module/c1value - */ -static struct yang_data * -frr_test_module_c1value_get_elem(struct nb_cb_get_elem_args *args) +static struct yang_data *__return_null(struct nb_cb_get_elem_args *args) { - return yang_data_new_uint8(args->xpath, 21); + return NULL; } /* @@ -263,6 +259,14 @@ static enum nb_error frr_test_module_c2cont_c2value_get(const struct nb_node *nb return NB_OK; } +/* + * XPath: /frr-test-module:frr-test-module/c3value + */ +static struct yang_data *frr_test_module_c3value_get_elem(struct nb_cb_get_elem_args *args) +{ + return yang_data_new_uint8(args->xpath, 21); +} + /* clang-format off */ const struct frr_yang_module_info frr_test_module_info = { .name = "frr-test-module", @@ -316,13 +320,21 @@ const struct frr_yang_module_info frr_test_module_info = { }, { .xpath = "/frr-test-module:frr-test-module/c1value", - .cbs.get_elem = frr_test_module_c1value_get_elem, + .cbs.get_elem = __return_null, }, { .xpath = "/frr-test-module:frr-test-module/c2cont/c2value", .cbs.get = frr_test_module_c2cont_c2value_get, }, { + .xpath = "/frr-test-module:frr-test-module/c3value", + .cbs.get_elem = frr_test_module_c3value_get_elem, + }, + { + .xpath = "/frr-test-module:frr-test-module/c4cont/c4value", + .cbs.get_elem = __return_null, + }, + { .xpath = NULL, }, } diff --git a/tests/lib/northbound/test_oper_data.in b/tests/lib/northbound/test_oper_data.in index 0053148953..bed83b8d74 100644 --- a/tests/lib/northbound/test_oper_data.in +++ b/tests/lib/northbound/test_oper_data.in @@ -2,4 +2,8 @@ show yang operational-data /frr-test-module:frr-test-module show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[2] show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[3]/interface show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10] +show yang operational-data /frr-test-module:frr-test-module/c3value +show yang operational-data /frr-test-module:frr-test-module/c2cont +show yang operational-data /frr-test-module:frr-test-module/c2cont/ +show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value test rpc diff --git a/tests/lib/northbound/test_oper_data.refout b/tests/lib/northbound/test_oper_data.refout index 2536e0306b..2feadf4b77 100644 --- a/tests/lib/northbound/test_oper_data.refout +++ b/tests/lib/northbound/test_oper_data.refout @@ -125,10 +125,10 @@ test# show yang operational-data /frr-test-module:frr-test-module }
]
},
- "c1value": 21,
"c2cont": {
"c2value": 2868969987
- }
+ },
+ "c3value": 21
}
}
test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[2] @@ -174,6 +174,36 @@ test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name= }
test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10] {}
+test# show yang operational-data /frr-test-module:frr-test-module/c3value +{ + "frr-test-module:frr-test-module": { + "c3value": 21 + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont/ +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} test# test rpc vrf testname data testdata test# diff --git a/tests/lib/northbound/test_oper_exists.c b/tests/lib/northbound/test_oper_exists.c new file mode 100644 index 0000000000..17afcc7fd4 --- /dev/null +++ b/tests/lib/northbound/test_oper_exists.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2018 NetDEF, Inc. + * Renato Westphal + * Copyright (C) 2025 LabN Consulting, L.L.C. + */ + +#include <zebra.h> +#include <sys/stat.h> + +#include "debug.h" +#include "frrevent.h" +#include "vty.h" +#include "command.h" +#include "memory.h" +#include "lib_vty.h" +#include "log.h" +#include "northbound.h" +#include "northbound_cli.h" + +static struct event_loop *master; +static struct lyd_node *data_tree; +static uint data_tree_lock; + +const char *data_json = "\n" + "{\n" + " \"frr-test-module:frr-test-module\": {\n" + " \"vrfs\": {\n" + " \"vrf\": [\n" + " {\n" + " \"name\": \"vrf0\",\n" + " \"interfaces\": {\n" + " \"interface\": [\n" + " \"eth0\",\n" + " \"eth1\",\n" + " \"eth2\",\n" + " \"eth3\"\n" + " ],\n" + " \"interface-new\": [\n" + " \"eth0\",\n" + " \"eth1\",\n" + " \"eth2\",\n" + " \"eth3\"\n" + " ]\n" + " },\n" + " \"routes\": {\n" + " \"route\": [\n" + " {\n" + " \"prefix\": \"10.0.0.0/32\",\n" + " \"next-hop\": \"172.16.0.0\",\n" + " \"interface\": \"eth0\",\n" + " \"metric\": 0,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.1/32\",\n" + " \"next-hop\": \"172.16.0.1\",\n" + " \"interface\": \"eth1\",\n" + " \"metric\": 1\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.2/32\",\n" + " \"next-hop\": \"172.16.0.2\",\n" + " \"interface\": \"eth2\",\n" + " \"metric\": 2,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.3/32\",\n" + " \"next-hop\": \"172.16.0.3\",\n" + " \"interface\": \"eth3\",\n" + " \"metric\": 3\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.4/32\",\n" + " \"next-hop\": \"172.16.0.4\",\n" + " \"interface\": \"eth4\",\n" + " \"metric\": 4,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.5/32\",\n" + " \"next-hop\": \"172.16.0.5\",\n" + " \"interface\": \"eth5\",\n" + " \"metric\": 5\n" + " }\n" + " ]\n" + " }\n" + " },\n" + " {\n" + " \"name\": \"vrf1\",\n" + " \"interfaces\": {\n" + " \"interface\": [\n" + " \"eth0\",\n" + " \"eth1\",\n" + " \"eth2\",\n" + " \"eth3\"\n" + " ],\n" + " \"interface-new\": [\n" + " \"eth0\",\n" + " \"eth1\",\n" + " \"eth2\",\n" + " \"eth3\"\n" + " ]\n" + " },\n" + " \"routes\": {\n" + " \"route\": [\n" + " {\n" + " \"prefix\": \"10.0.0.0/32\",\n" + " \"next-hop\": \"172.16.0.0\",\n" + " \"interface\": \"eth0\",\n" + " \"metric\": 0,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.1/32\",\n" + " \"next-hop\": \"172.16.0.1\",\n" + " \"interface\": \"eth1\",\n" + " \"metric\": 1\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.2/32\",\n" + " \"next-hop\": \"172.16.0.2\",\n" + " \"interface\": \"eth2\",\n" + " \"metric\": 2,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.3/32\",\n" + " \"next-hop\": \"172.16.0.3\",\n" + " \"interface\": \"eth3\",\n" + " \"metric\": 3\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.4/32\",\n" + " \"next-hop\": \"172.16.0.4\",\n" + " \"interface\": \"eth4\",\n" + " \"metric\": 4,\n" + " \"active\": [null]\n" + " },\n" + " {\n" + " \"prefix\": \"10.0.0.5/32\",\n" + " \"next-hop\": \"172.16.0.5\",\n" + " \"interface\": \"eth5\",\n" + " \"metric\": 5\n" + " }\n" + " ]\n" + " }\n" + " }\n" + " ]\n" + " },\n" + " \"c2cont\": {\n" + " \"c2value\": 2868969987\n" + " },\n" + " \"c3value\": 21\n" + " }\n" + "}\n"; + + +static const struct lyd_node *test_oper_get_tree_locked(const char *xpath) +{ + ++data_tree_lock; + return data_tree; +} + +static void test_oper_unlock_tree(const struct lyd_node *tree __attribute__((unused))) +{ + data_tree_lock--; +} + +static int __rpc_return_ok(struct nb_cb_rpc_args *args) +{ + return NB_OK; +} + +/* clang-format off */ +const struct frr_yang_module_info frr_test_module_info = { + .name = "frr-test-module", + .get_tree_locked = test_oper_get_tree_locked, + .unlock_tree = test_oper_unlock_tree, + .nodes = { + { + .xpath = "/frr-test-module:frr-test-module/vrfs/vrf/ping", + .cbs.rpc = __rpc_return_ok, + }, + { + .xpath = NULL, + }, + } +}; +/* clang-format on */ + +static const struct frr_yang_module_info *const modules[] = { + &frr_test_module_info, +}; + +static void vty_do_exit(int isexit) +{ + printf("\nend.\n"); + + lyd_free_all(data_tree); + + cmd_terminate(); + vty_terminate(); + nb_terminate(); + yang_terminate(); + event_master_free(master); + + log_memstats(NULL, true); + if (!isexit) + exit(0); +} + + +static struct lyd_node *load_data(void) +{ + struct ly_in *in = NULL; + struct lyd_node *tree = NULL; + LY_ERR err; + + err = ly_in_new_memory(data_json, &in); + if (!err) + err = lyd_parse_data(ly_native_ctx, NULL, in, LYD_JSON, LYD_PARSE_STRICT, LYD_VALIDATE_OPERATIONAL, &tree); + ly_in_free(in, 0); + if (err) { + fprintf(stderr, "LYERR: %s\n", getcwd(NULL, 0)); + fprintf(stderr, "LYERR: %s\n", ly_last_errmsg()); + exit(1); + } + return tree; +} + +/* main routine. */ +int main(int argc, char **argv) +{ + struct event thread; + + /* Set umask before anything for security */ + umask(0027); + + /* master init. */ + master = event_master_create(NULL); + + // zlog_aux_init("NONE: ", ZLOG_DISABLED); + + /* Library inits. */ + cmd_init(1); + cmd_hostname_set("test"); + vty_init(master, false); + lib_cmd_init(); + debug_init(); + nb_init(master, modules, array_size(modules), false, false); + + /* Create artificial data. */ + data_tree = load_data(); + + /* Read input from .in file. */ + vty_stdio(vty_do_exit); + + /* Fetch next active thread. */ + while (event_fetch(master, &thread)) + event_call(&thread); + + /* Not reached. */ + exit(0); +} diff --git a/tests/lib/northbound/test_oper_exists.in b/tests/lib/northbound/test_oper_exists.in new file mode 100644 index 0000000000..7b83c27a0b --- /dev/null +++ b/tests/lib/northbound/test_oper_exists.in @@ -0,0 +1,8 @@ +show yang operational-data /frr-test-module:frr-test-module +show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[2] +show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[3]/interface +show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10] +show yang operational-data /frr-test-module:frr-test-module/c3value +show yang operational-data /frr-test-module:frr-test-module/c2cont +show yang operational-data /frr-test-module:frr-test-module/c2cont/ +show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value diff --git a/tests/lib/northbound/test_oper_exists.py b/tests/lib/northbound/test_oper_exists.py new file mode 100644 index 0000000000..423414eb85 --- /dev/null +++ b/tests/lib/northbound/test_oper_exists.py @@ -0,0 +1,5 @@ +import frrtest + + +class TestNbOperData(frrtest.TestRefOut): + program = "./test_oper_exists" diff --git a/tests/lib/northbound/test_oper_exists.refout b/tests/lib/northbound/test_oper_exists.refout new file mode 100644 index 0000000000..4060a096fd --- /dev/null +++ b/tests/lib/northbound/test_oper_exists.refout @@ -0,0 +1,208 @@ +test# show yang operational-data /frr-test-module:frr-test-module +{ + "frr-test-module:frr-test-module": { + "vrfs": { + "vrf": [ + { + "name": "vrf0", + "interfaces": { + "interface": [ + "eth0", + "eth1", + "eth2", + "eth3" + ], + "interface-new": [ + "eth0", + "eth1", + "eth2", + "eth3" + ] + }, + "routes": { + "route": [ + { + "prefix": "10.0.0.0/32", + "next-hop": "172.16.0.0", + "interface": "eth0", + "metric": 0, + "active": [null] + }, + { + "prefix": "10.0.0.1/32", + "next-hop": "172.16.0.1", + "interface": "eth1", + "metric": 1 + }, + { + "prefix": "10.0.0.2/32", + "next-hop": "172.16.0.2", + "interface": "eth2", + "metric": 2, + "active": [null] + }, + { + "prefix": "10.0.0.3/32", + "next-hop": "172.16.0.3", + "interface": "eth3", + "metric": 3 + }, + { + "prefix": "10.0.0.4/32", + "next-hop": "172.16.0.4", + "interface": "eth4", + "metric": 4, + "active": [null] + }, + { + "prefix": "10.0.0.5/32", + "next-hop": "172.16.0.5", + "interface": "eth5", + "metric": 5 + } + ] + } + }, + { + "name": "vrf1", + "interfaces": { + "interface": [ + "eth0", + "eth1", + "eth2", + "eth3" + ], + "interface-new": [ + "eth0", + "eth1", + "eth2", + "eth3" + ] + }, + "routes": { + "route": [ + { + "prefix": "10.0.0.0/32", + "next-hop": "172.16.0.0", + "interface": "eth0", + "metric": 0, + "active": [null] + }, + { + "prefix": "10.0.0.1/32", + "next-hop": "172.16.0.1", + "interface": "eth1", + "metric": 1 + }, + { + "prefix": "10.0.0.2/32", + "next-hop": "172.16.0.2", + "interface": "eth2", + "metric": 2, + "active": [null] + }, + { + "prefix": "10.0.0.3/32", + "next-hop": "172.16.0.3", + "interface": "eth3", + "metric": 3 + }, + { + "prefix": "10.0.0.4/32", + "next-hop": "172.16.0.4", + "interface": "eth4", + "metric": 4, + "active": [null] + }, + { + "prefix": "10.0.0.5/32", + "next-hop": "172.16.0.5", + "interface": "eth5", + "metric": 5 + } + ] + } + } + ] + }, + "c2cont": { + "c2value": 2868969987 + }, + "c3value": 21 + } +} +test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[2] +{ + "frr-test-module:frr-test-module": { + "vrfs": { + "vrf": [ + { + "name": "vrf0", + "routes": { + "route": [ + { + "prefix": "10.0.0.1/32", + "next-hop": "172.16.0.1", + "interface": "eth1", + "metric": 1 + } + ] + } + } + ] + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[3]/interface +{ + "frr-test-module:frr-test-module": { + "vrfs": { + "vrf": [ + { + "name": "vrf0", + "routes": { + "route": [ + { + "interface": "eth2" + } + ] + } + } + ] + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/vrfs/vrf[name='vrf0']/routes/route[10] +{} +test# show yang operational-data /frr-test-module:frr-test-module/c3value +{ + "frr-test-module:frr-test-module": { + "c3value": 21 + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont/ +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} +test# show yang operational-data /frr-test-module:frr-test-module/c2cont/c2value +{ + "frr-test-module:frr-test-module": { + "c2cont": { + "c2value": 2868969987 + } + } +} +test# +end. diff --git a/tests/lib/subdir.am b/tests/lib/subdir.am index 1a21684f16..ca74306543 100644 --- a/tests/lib/subdir.am +++ b/tests/lib/subdir.am @@ -131,6 +131,19 @@ EXTRA_DIST += \ # end +check_PROGRAMS += tests/lib/northbound/test_oper_exists +tests_lib_northbound_test_oper_exists_CFLAGS = $(TESTS_CFLAGS) +tests_lib_northbound_test_oper_exists_CPPFLAGS = $(TESTS_CPPFLAGS) +tests_lib_northbound_test_oper_exists_LDADD = $(ALL_TESTS_LDADD) +tests_lib_northbound_test_oper_exists_SOURCES = tests/lib/northbound/test_oper_exists.c +nodist_tests_lib_northbound_test_oper_exists_SOURCES = yang/frr-test-module.yang.c +EXTRA_DIST += \ + tests/lib/northbound/test_oper_exists.in \ + tests/lib/northbound/test_oper_exists.py \ + tests/lib/northbound/test_oper_exists.refout \ + # end + + check_PROGRAMS += tests/lib/test_assert tests_lib_test_assert_CFLAGS = $(TESTS_CFLAGS) tests_lib_test_assert_CPPFLAGS = $(TESTS_CPPFLAGS) diff --git a/tests/topotests/bgp_l3vpn_hidden/__init__.py b/tests/topotests/bgp_l3vpn_hidden/__init__.py new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/__init__.py diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf new file mode 100644 index 0000000000..95d84d5e6b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf @@ -0,0 +1,52 @@ +debug bgp neighbor-events +! +ip prefix-list PLIST-LAN seq 10 permit 172.20.0.0/16 le 24 +! +ipv6 prefix-list PLIST-LAN6 seq 10 permit 2001:db8::/32 le 64 +! +route-map RMAP-LAN permit 10 + match ip address prefix-list PLIST-LAN +exit +! +route-map RMAP-LAN6 permit 10 + match ipv6 address prefix-list PLIST-LAN6 +exit +! +interface eth-lan + ip address 172.20.1.1/24 + ipv6 address 2001:db8:1::ff/64 +exit +! +interface eth-pe1 + ip address 172.16.1.254/24 + ipv6 address 3fff:1::ff/64 +exit +! +router bgp 65501 + bgp router-id 172.16.1.254 + no bgp ebgp-requires-policy + bgp bestpath compare-routerid + neighbor 172.16.1.1 remote-as external + neighbor 172.16.1.1 bfd profile BGP + neighbor 3fff:1::1 remote-as external + neighbor 3fff:1::1 bfd profile BGP + ! + address-family ipv4 unicast + redistribute connected route-map RMAP-LAN + neighbor 172.16.1.1 next-hop-self + no neighbor 3fff:1::1 activate + exit-address-family +! + address-family ipv6 unicast + redistribute connected route-map RMAP-LAN6 + neighbor 3fff:1::1 activate + neighbor 3fff:1::1 next-hop-self + exit-address-family +exit +bfd + profile BGP + transmit-interval 2000 + receive-interval 2000 + exit + ! +exit diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json new file mode 100644 index 0000000000..e9741a2fbd --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json @@ -0,0 +1,24 @@ +{ + "vrfName": "default", + "routerId": "172.16.1.254", + "localAS": 65501, + "routes": { + "172.20.1.0/24": [ + { + "valid": true, + "bestpath": true, + "peerId": "(unspec)", + "path": "", + "nexthops": [ + { + "ip": "0.0.0.0", + "hostname": "ce1", + "used": true + } + ] + } + ] + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json new file mode 120000 index 0000000000..0d02eacc65 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json @@ -0,0 +1 @@ +show_bgp_ipv4_unicast.json
\ No newline at end of file diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json @@ -0,0 +1 @@ +{} diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json new file mode 100644 index 0000000000..1120d30edd --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json @@ -0,0 +1,25 @@ +{ + "vrfName": "default", + "routerId": "172.16.1.254", + "localAS": 65501, + "routes": { + "2001:db8:1::/64": [ + { + "valid": true, + "bestpath": true, + "peerId": "(unspec)", + "path": "", + "nexthops": [ + { + "ip": "::", + "hostname": "ce1", + "scope": "global", + "used": true + } + ] + } + ] + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json new file mode 120000 index 0000000000..94f8f5bba9 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json @@ -0,0 +1 @@ +show_bgp_ipv6_unicast.json
\ No newline at end of file diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json @@ -0,0 +1 @@ +{} diff --git a/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json new file mode 100644 index 0000000000..5a0699ed9b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json @@ -0,0 +1,24 @@ +{ + "default": { + "ipv4Unicast": { + "routerId": "172.16.1.254", + "peers": { + "172.16.1.1": { + "hostname": "pe1", + "state": "Established" + } + }, + "totalPeers": 1 + }, + "ipv6Unicast": { + "routerId": "172.16.1.254", + "peers": { + "3fff:1::1": { + "hostname": "pe1", + "state": "Established" + } + }, + "totalPeers": 1 + } + } +} diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf new file mode 100644 index 0000000000..95a7262b32 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf @@ -0,0 +1,107 @@ +mpls label dynamic-block 1000 1048575 +! +interface lo + ip address 192.168.0.1/32 + ipv6 address 3fff::192:168:0:1/128 +! +interface eth-rr1 + ip address 10.0.1.1/24 +! +interface eth-ce1 + ip address 172.16.1.1/24 + ipv6 address 3fff:1::1/64 +! +router bgp 65000 + bgp router-id 192.168.0.1 + no bgp ebgp-requires-policy + no bgp default ipv4-unicast + neighbor 192.168.0.101 remote-as 65000 + neighbor 192.168.0.101 bfd profile BGP + neighbor 192.168.0.101 update-source 192.168.0.1 + neighbor 3fff::192:168:0:101 remote-as 65000 + neighbor 3fff::192:168:0:101 bfd profile BGP + neighbor 3fff::192:168:0:101 update-source 3fff::192:168:0:1 + +! + address-family ipv4 unicast + no neighbor 192.168.0.101 activate + exit-address-family +! + address-family ipv4 vpn + neighbor 192.168.0.101 activate + neighbor 192.168.0.101 soft-reconfiguration inbound + exit-address-family +! + address-family ipv6 vpn + neighbor 3fff::192:168:0:101 activate + neighbor 3fff::192:168:0:101 soft-reconfiguration inbound + exit-address-family +! +router bgp 65000 vrf RED + bgp router-id 192.168.0.1 + no bgp ebgp-requires-policy + bgp bestpath compare-routerid + neighbor 172.16.1.254 remote-as external + neighbor 172.16.1.254 bfd profile BGP + neighbor 3fff:1::ff remote-as external + neighbor 3fff:1::ff bfd profile BGP + ! + address-family ipv4 unicast + label vpn export 100 + rd vpn export 65000:100 + rt vpn both 65000:100 + export vpn + import vpn + neighbor 172.16.1.254 next-hop-self + no neighbor 3fff:1::ff activate + exit-address-family +! + address-family ipv6 unicast + label vpn export 200 + rd vpn export 65000:100 + rt vpn both 65000:100 + export vpn + import vpn + neighbor 3fff:1::ff activate + neighbor 3fff:1::ff next-hop-self + exit-address-family +exit +! +interface lo + ip router isis 1 + isis hello-interval 2 + ipv6 router isis 1 +! +interface eth-rr1 + ip router isis 1 + isis hello-interval 2 + ipv6 router isis 1 +! +router isis 1 + lsp-gen-interval 2 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0001.00 + metric-style wide + exit +! +mpls ldp + router-id 192.168.0.1 + ! + address-family ipv4 + discovery transport-address 192.168.0.1 + ! + interface eth-rr1 + ! + address-family ipv6 + discovery transport-address 3fff::192:168:0:1 + ! + interface eth-rr1 + ! + ! +! +bfd + profile BGP + transmit-interval 2000 + receive-interval 2000 + exit + ! +exit diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json new file mode 100644 index 0000000000..d21dd89291 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json @@ -0,0 +1,29 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.1", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": { + "172.20.1.0/24": [ + { + "valid": true, + "bestpath": true, + "peerId": "(unspec)", + "path": "65501", + "nhVrfName": "RED", + "nexthops": [ + { + "ip": "172.16.1.254", + "hostname": "pe1", + "used": true + } + ] + } + ] + } + } + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json @@ -0,0 +1 @@ +{} diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json new file mode 100644 index 0000000000..b42656f44f --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json @@ -0,0 +1,29 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.1", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": { + "2001:db8:1::/64": [ + { + "valid": true, + "bestpath": true, + "peerId": "(unspec)", + "path": "65501", + "nhVrfName": "RED", + "nexthops": [ + { + "ip": "3fff:1::ff", + "hostname": "pe1", + "used": true + } + ] + } + ] + } + } + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json @@ -0,0 +1 @@ +{} diff --git a/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json new file mode 100644 index 0000000000..b414d2e4ae --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json @@ -0,0 +1,46 @@ +{ + "default": { + "ipv4Vpn": { + "routerId": "192.168.0.1", + "peers": { + "192.168.0.101": { + "hostname": "rr1", + "state": "Established" + } + }, + "totalPeers": 1 + }, + "ipv6Vpn": { + "routerId": "192.168.0.1", + "peers": { + "3fff::192:168:0:101": { + "hostname": "rr1", + "state": "Established" + } + }, + "totalPeers": 1 + } + }, + "RED": { + "ipv4Unicast": { + "routerId": "192.168.0.1", + "peers": { + "172.16.1.254": { + "hostname": "ce1", + "state": "Established" + } + }, + "totalPeers": 1 + }, + "ipv6Unicast": { + "routerId": "192.168.0.1", + "peers": { + "3fff:1::ff": { + "hostname": "ce1", + "state": "Established" + } + }, + "totalPeers": 1 + } + } +} diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf b/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf new file mode 100644 index 0000000000..b25a7a336b --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf @@ -0,0 +1,79 @@ +mpls label dynamic-block 1000 1048575 +! +interface lo + ip address 192.168.0.101/32 + ipv6 address 3fff::192:168:0:101/128 +! +interface eth-pe1 + ip address 10.0.1.101/24 +! +router bgp 65000 + bgp router-id 192.168.0.101 + bgp cluster-id 192.168.0.101 + bgp log-neighbor-changes + no bgp default ipv4-unicast + neighbor PE peer-group + neighbor PE remote-as 65000 + neighbor PE bfd profile BGP + neighbor PE update-source 192.168.0.101 + neighbor PE6 peer-group + neighbor PE6 remote-as 65000 + neighbor PE6 bfd profile BGP + neighbor PE6 update-source 3fff::192:168:0:101 + neighbor 192.168.0.1 peer-group PE + neighbor 3fff::192:168:0:1 peer-group PE6 +! + address-family ipv4 unicast + no neighbor PE activate + exit-address-family +! + address-family ipv4 vpn + neighbor PE activate + neighbor PE route-reflector-client + neighbor PE soft-reconfiguration inbound + exit-address-family +! + address-family ipv6 vpn + neighbor PE6 activate + neighbor PE6 route-reflector-client + neighbor PE6 soft-reconfiguration inbound + exit-address-family +! +! +interface lo + ip router isis 1 + isis hello-interval 2 + ipv6 router isis 1 +! +interface eth-pe1 + ip router isis 1 + isis hello-interval 2 + ipv6 router isis 1 +! +! +router isis 1 + lsp-gen-interval 2 + net 10.0000.0000.0000.0000.0000.0000.0000.0000.0100.00 + metric-style wide + exit +! +mpls ldp + router-id 192.168.0.101 + ! + address-family ipv4 + discovery transport-address 192.168.0.101 + ! + interface eth-pe1 + ! + address-family ipv6 + discovery transport-address 3fff::192:168:0:101 + ! + interface eth-pe1 +! +bfd + profile BGP + transmit-interval 2000 + receive-interval 2000 + exit + ! +exit diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json new file mode 100644 index 0000000000..3fd451640c --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json @@ -0,0 +1,28 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.101", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": { + "172.20.1.0/24": [ + { + "valid": true, + "bestpath": true, + "peerId": "192.168.0.1", + "path": "65501", + "nexthops": [ + { + "ip": "192.168.0.1", + "hostname": "pe1", + "used": true + } + ] + } + ] + } + } + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json new file mode 100644 index 0000000000..88d1ac5e6e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json @@ -0,0 +1,12 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.101", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": {} + } + }, + "totalRoutes": 0, + "totalPaths": 0 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json new file mode 100644 index 0000000000..c3ecd71c46 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json @@ -0,0 +1,28 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.101", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": { + "2001:db8:1::/64": [ + { + "valid": true, + "bestpath": true, + "peerId": "3fff::192:168:0:1", + "path": "65501", + "nexthops": [ + { + "ip": "3fff::192:168:0:1", + "hostname": "pe1", + "used": true + } + ] + } + ] + } + } + }, + "totalRoutes": 1, + "totalPaths": 1 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json new file mode 100644 index 0000000000..88d1ac5e6e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json @@ -0,0 +1,12 @@ +{ + "vrfName": "default", + "routerId": "192.168.0.101", + "localAS": 65000, + "routes": { + "routeDistinguishers": { + "65000:100": {} + } + }, + "totalRoutes": 0, + "totalPaths": 0 +} diff --git a/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json new file mode 100644 index 0000000000..8d6c15526e --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json @@ -0,0 +1,24 @@ +{ + "default": { + "ipv4Vpn": { + "routerId": "192.168.0.101", + "peers": { + "192.168.0.1": { + "hostname": "pe1", + "state": "Established" + } + }, + "totalPeers": 1 + }, + "ipv6Vpn": { + "routerId": "192.168.0.101", + "peers": { + "3fff::192:168:0:1": { + "hostname": "pe1", + "state": "Established" + } + }, + "totalPeers": 1 + } + } +} diff --git a/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py b/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py new file mode 100644 index 0000000000..04a8482518 --- /dev/null +++ b/tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py @@ -0,0 +1,287 @@ +#!/usr/bin/env python +# SPDX-License-Identifier: ISC + + +""" +Test BGP hidden +See https://github.com/FRRouting/frr/commit/4d0e7a49cf8d4311a485281fa50bbff6ee8ca6cc +""" + +import os +import sys +import re +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.topolog import logger +from lib.topogen import Topogen, TopoRouter, get_topogen +from lib.common_config import step + + +pytestmark = [pytest.mark.bgpd, pytest.mark.bfdd, pytest.mark.isisd, pytest.mark.ldpd] + + +def build_topo(tgen): + """ + +---+ +---+ +---+ + |ce1|---|pe1|---|rr1| + +---+ +---+ +---+""" + + def connect_routers(tgen, left, right): + for rname in [left, right]: + if rname not in tgen.routers().keys(): + tgen.add_router(rname) + + switch = tgen.add_switch("s-{}-{}".format(left, right)) + switch.add_link(tgen.gears[left], nodeif="eth-{}".format(right)) + switch.add_link(tgen.gears[right], nodeif="eth-{}".format(left)) + if "ce" not in right and "ce" not in left: + tgen.gears[left].cmd(f"sysctl net.mpls.conf.eth-{right}.input=1") + tgen.gears[right].cmd(f"sysctl net.mpls.conf.eth-{left}.input=1") + + def connect_switchs(tgen, rname, switch): + if rname not in tgen.routers().keys(): + tgen.add_router(rname) + + switch.add_link(tgen.gears[rname], nodeif="eth-{}".format(switch.name)) + + def connect_lan(tgen, rname): + if rname not in tgen.routers().keys(): + tgen.add_router(rname) + + # Extra LAN interfaces. Not used for communication with hosts, just to + # hold an address we use to inject routes + switch = tgen.add_switch("s-{}".format(rname)) + switch.add_link(tgen.gears[rname], nodeif="eth-lan") + + # directly connected without switch routers + connect_routers(tgen, "rr1", "pe1") + connect_routers(tgen, "pe1", "ce1") + connect_lan(tgen, "ce1") + + +def setup_module(mod): + tgen = Topogen(build_topo, mod.__name__) + tgen.start_topology() + + pe1 = tgen.gears["pe1"] + pe1.cmd( + f""" +ip link add RED type vrf table 100 +ip link set RED up +ip link set eth-ce1 master RED +""" + ) + + router_list = tgen.routers() + + for _, (rname, router) in enumerate(router_list.items(), 1): + router.load_frr_config( + os.path.join(CWD, "{}/frr.conf".format(rname)), + [ + (TopoRouter.RD_ZEBRA, None), + (TopoRouter.RD_MGMTD, None), + (TopoRouter.RD_BFD, None), + (TopoRouter.RD_LDP, None), + (TopoRouter.RD_ISIS, None), + (TopoRouter.RD_BGP, None), + ], + ) + + tgen.start_router() + + +def teardown_module(mod): + tgen = get_topogen() + tgen.stop_topology() + + +def check_bgp_convergence(step=None): + """ + out was generated using + + FRRGIT=/path/git/frr + + vtysh -c 'show bgp vrf all summary json' | jq . | egrep -v 'ersion|idType|connections|peerState|pfx|outq|inq|msg|As|rib|Count|Memory|Uptime|vrf|\"as|failedPeers|displayedPeers|dynamicPeers' | awk '/ "bestPath": {/ {c=3; next} c-- > 0 {next} 1' | sed -E 's|"totalPeers": (.+),|"totalPeers": \1|g;s|"Established",|"Established"|g' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_summary.json + + vtysh -c 'show bgp ipv4 vpn json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv4_vpn_step1.json + vtysh -c 'show bgp ipv6 vpn json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|fe80|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv6_vpn_step1.json + + vtysh -c 'show bgp ipv4 unicast json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv4_unicast.json + vtysh -c 'show bgp ipv6 unicast json' | jq . | egrep -v 'selectionReason|pathFrom|prefix|locPrf|ersion|weight|origin|vrfId|afi|defaultLocPrf|network|fe80|nhVrfId|announceNexthopSelf|metric|multipath|linkLocalOnly|length' | jq . >$FRRGIT/tests/topotests/bgp_l3vpn_hidden/$HOSTNAME/show_bgp_ipv6_unicast.json + """ + tgen = get_topogen() + + logger.info("waiting for bgp convergence") + + step_suffix = f"_step{step}" if step else "" + + if not step: + logger.info("Check BGP summary") + for rname, router in tgen.routers().items(): + reffile = os.path.join(CWD, f"{rname}/show_bgp_summary.json") + expected = json.loads(open(reffile).read()) + cmd = "show bgp vrf all summary json" + test_func = functools.partial( + topotest.router_json_cmp, router, cmd, expected + ) + _, res = topotest.run_and_expect(test_func, None, count=60, wait=1) + assertmsg = f"BGP did not converge. Error on {rname} {cmd}" + assert res is None, assertmsg + + logger.info("Check BGP IPv4/6 unicast/VPN table") + for rname, router in tgen.routers().items(): + for ipv in [4, 6]: + logger.info(f"Check BGP IPv4/6 unicast/VPN table: {rname} IPv{ipv}") + safi = "unicast" if "ce" in rname else "vpn" + reffile = os.path.join( + CWD, f"{rname}/show_bgp_ipv{ipv}_{safi}{step_suffix}.json" + ) + expected = json.loads(open(reffile).read()) + exact = not expected # exact match if json is void (ie. {}) + cmd = f"show bgp ipv{ipv} {safi} json" + test_func = functools.partial( + topotest.router_json_cmp, + router, + cmd, + expected, + exact=exact, + ) + _, res = topotest.run_and_expect(test_func, None, count=120, wait=1) + assertmsg = f"BGP did not converge. Error on {rname} {cmd}" + assert res is None, assertmsg + + +def configure_bgp(vrf=None, router_name="all", activate=False): + tgen = get_topogen() + + vrf_suffix = f" vrf {vrf}" if vrf else "" + as_pattern = re.compile(rf"^router bgp (\d+){vrf_suffix}$") + + for rname, router in tgen.routers().items(): + if router_name != "all" and router_name != rname: + continue + + if "ce" in rname: + continue + + as_number = "" + cmds = [] + router_bgp = False + with open(os.path.join(CWD, f"{rname}/frr.conf"), "r") as f: + for line in f: + line = line.strip() + if "router bgp" in line: + match = as_pattern.match(line) + if match: + as_number = match.group(1) + router_bgp = True + continue + if router_bgp: + # If we already hit "router bgp <as_number>" once, + # and see another "router bgp" line, break. + break + if not router_bgp: + # Only capture lines after we've matched "router bgp" + continue + cmds.append(line) + + cfg = "configure terminal\n" + if activate: + cfg += f"router bgp {as_number}{vrf_suffix}\n" + for cmd in cmds: + cfg += f"{cmd}\n" + else: + cfg += f"no router bgp {as_number}{vrf_suffix}\n" + + router.vtysh_cmd(cfg) + + +def test_bgp_convergence(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + check_bgp_convergence() + + +def test_bgp_l3vpn_hidden_step1(): + """ + Remove pe1 router bgp blocks + The Default BGP instance becomes hidden + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for vrf in ["RED", None]: + configure_bgp(router_name="pe1", vrf=vrf, activate=False) + + check_bgp_convergence(step=1) + + +def test_bgp_l3vpn_hidden_step2(): + """ + Restore pe1 router bgp blocks + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for vrf in [None, "RED"]: + configure_bgp(router_name="pe1", vrf=vrf, activate=True) + + # identical to the intitial step + check_bgp_convergence(step=None) + + +def test_bgp_l3vpn_hidden_step3(): + """ + Remove pe1 router bgp blocks + The Default BGP instance becomes hidden + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for vrf in ["RED", None]: + configure_bgp(router_name="pe1", vrf=vrf, activate=False) + + # identical to the intitial step 1 + check_bgp_convergence(step=1) + + +def test_bgp_l3vpn_hidden_step4(): + """ + Restore pe1 router bgp blocks + Reconfigure VRF block first + """ + + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + for vrf in [None, "RED"]: + configure_bgp(router_name="pe1", vrf=vrf, activate=True) + + # identical to the intitial step + check_bgp_convergence(step=None) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args)) diff --git a/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf index 397f7938d2..8363e2bc99 100644 --- a/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf +++ b/tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf @@ -1,12 +1,5 @@ hostname r1 -router bgp 99 - no bgp ebgp-requires-policy - address-family ipv4 unicast - redistribute connected - import vrf DONNA - ! -! router bgp 99 vrf DONNA no bgp ebgp-requires-policy address-family ipv4 unicast @@ -31,3 +24,10 @@ router bgp 99 vrf ZITA network 172.16.101.0/24 ! ! +router bgp 99 + no bgp ebgp-requires-policy + address-family ipv4 unicast + redistribute connected + import vrf DONNA + ! +! diff --git a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py index 3bc36862bf..7a407761d3 100644 --- a/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py +++ b/tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py @@ -64,6 +64,16 @@ def teardown_module(mod): tgen.stop_topology() +def test_router_bgp_as_pretty(): + tgen = get_topogen() + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + output = tgen.gears["r1"].vtysh_cmd("show run") + assert "router bgp 99\n" in output, "router bgp 99 not found in show run" + + def test_vrf_route_leak_donna(): logger.info("Ensure that routes are leaked back and forth") tgen = get_topogen() diff --git a/tests/topotests/lib/bgp.py b/tests/topotests/lib/bgp.py index 329c2b54f5..632aa4a10b 100644 --- a/tests/topotests/lib/bgp.py +++ b/tests/topotests/lib/bgp.py @@ -11,7 +11,7 @@ import traceback from copy import deepcopy from time import sleep -# Import common_config to use commomnly used APIs +# Import common_config to use commonly used APIs from lib.common_config import ( create_common_configurations, FRRCFG_FILE, @@ -63,29 +63,29 @@ def create_router_bgp(tgen, topo=None, input_dict=None, build=False, load_config "address_family": { "ipv4": { "unicast": { - "default_originate":{ - "neighbor":"R2", - "add_type":"lo" - "route_map":"rm" - + "default_originate": { + "neighbor": "R2", + "add_type": "lo", + "route_map": "rm", }, - "redistribute": [{ - "redist_type": "static", + "redistribute": [ + { + "redist_type": "static", "attribute": { - "metric" : 123 - } + "metric": 123, + }, }, - {"redist_type": "connected"} + {"redist_type": "connected"}, ], "advertise_networks": [ { "network": "20.0.0.0/32", - "no_of_network": 10 + "no_of_network": 10, }, { "network": "30.0.0.0/32", - "no_of_network": 10 - } + "no_of_network": 10, + }, ], "neighbor": { "r3": { @@ -94,32 +94,33 @@ def create_router_bgp(tgen, topo=None, input_dict=None, build=False, load_config "dest_link": { "r4": { "allowas-in": { - "number_occurences": 2 + "number_occurences": 2, }, "prefix_lists": [ { "name": "pf_list_1", - "direction": "in" - } + "direction": "in", + }, ], - "route_maps": [{ - "name": "RMAP_MED_R3", - "direction": "in" - }], - "next_hop_self": True + "route_maps": [ + { + "name": "RMAP_MED_R3", + "direction": "in", + }, + ], + "next_hop_self": True, }, - "r1": {"graceful-restart-helper": True} - } - } - } - } - } - } - } - } + "r1": {"graceful-restart-helper": True}, + }, + }, + }, + }, + }, + }, + }, + }, } - Returns ------- True or False diff --git a/tests/topotests/lib/fe_client.py b/tests/topotests/lib/fe_client.py index 078df8cb33..4868c1018e 100755 --- a/tests/topotests/lib/fe_client.py +++ b/tests/topotests/lib/fe_client.py @@ -8,6 +8,7 @@ # # noqa: E501 # +"""A MGMTD front-end client.""" import argparse import logging import os @@ -34,7 +35,7 @@ except Exception as error: try: sys.path[0:0] = "." - import mgmt_pb2 + import mgmt_pb2 # pylint: disable=E0401 except Exception as error: logging.error("can't import proto definition modules %s", error) raise @@ -124,16 +125,22 @@ MSG_FORMAT_LYB = 3 def cstr(mdata): + """Convert a null-term byte array into a string, excluding the null terminator.""" assert mdata[-1] == 0 return mdata[:-1] class FEClientError(Exception): + """Base class for frontend client errors.""" + pass class PBMessageError(FEClientError): + """Exception for errors related to protobuf messages.""" + def __init__(self, msg, errstr): + """Initialize PBMessageError with message and error string.""" self.msg = msg # self.sess_id = mhdr[HDR_FIELD_SESS_ID] # self.req_id = mhdr[HDR_FIELD_REQ_ID] @@ -143,7 +150,10 @@ class PBMessageError(FEClientError): class NativeMessageError(FEClientError): + """Exception for errors related to native messages.""" + def __init__(self, mhdr, mfixed, mdata): + """Initialize NativeMessageError with message header, fixed fields, and data.""" self.mhdr = mhdr self.sess_id = mhdr[HDR_FIELD_SESS_ID] self.req_id = mhdr[HDR_FIELD_REQ_ID] @@ -173,6 +183,7 @@ def recv_wait(sock, size): def recv_msg(sock): + """Receive a message from the socket, ensuring it has a valid marker.""" marker = recv_wait(sock, 4) assert marker in (MGMT_MSG_MARKER_PROTOBUF, MGMT_MSG_MARKER_NATIVE) @@ -197,15 +208,18 @@ class Session: client_id = 1 def __init__(self, sock, use_protobuf): + """Initialize a session with the mgmtd server.""" self.sock = sock self.next_req_id = 1 if use_protobuf: + # Register the client req = mgmt_pb2.FeMessage() req.register_req.client_name = "test-client" self.send_pb_msg(req) logging.debug("Sent FeRegisterReq: %s", req) + # Create a session req = mgmt_pb2.FeMessage() req.session_req.create = 1 req.session_req.client_conn_id = Session.client_id @@ -219,11 +233,11 @@ class Session: assert reply.session_reply.success self.sess_id = reply.session_reply.session_id else: + # Establish a native session self.sess_id = 0 mdata, _ = self.get_native_msg_header(MSG_CODE_SESSION_REQ) mdata += struct.pack(MSG_SESSION_REQ_FMT) mdata += "test-client".encode("utf-8") + b"\x00" - self.send_native_msg(mdata) logging.debug("Sent native SESSION-REQ") @@ -236,6 +250,7 @@ class Session: self.sess_id = mhdr[HDR_FIELD_SESS_ID] def close(self, clean=True): + """Close the session.""" if clean: req = mgmt_pb2.FeMessage() req.session_req.create = 0 @@ -245,6 +260,7 @@ class Session: self.sock = None def get_next_req_id(self): + """Generate the next request ID for a new session.""" req_id = self.next_req_id self.next_req_id += 1 return req_id @@ -301,10 +317,11 @@ class Session: return mhdr, mfixed, mdata def send_native_msg(self, mdata): - """Send a native message.""" + """Send a native message to the mgmtd server.""" return send_msg(self.sock, MGMT_MSG_MARKER_NATIVE, mdata) def get_native_msg_header(self, msg_code): + """Generate a native message header for a given message code.""" req_id = self.get_next_req_id() hdata = struct.pack(MSG_HDR_FMT, msg_code, 0, self.sess_id, req_id) return hdata, req_id @@ -314,6 +331,19 @@ class Session: # ----------------------- def lock(self, lock=True, ds_id=mgmt_pb2.CANDIDATE_DS): + """Lock or unlock a datastore. + + Args: + lock (bool, optional): Whether to lock (True) or unlock (False) the + datastore. Defaults to True. + ds_id (int, optional): The datastore ID. Defaults to mgmt_pb2.CANDIDATE_DS. + + Returns: + None + + Raises: + AssertionError: If the lock request fails. + """ req = mgmt_pb2.FeMessage() req.lockds_req.session_id = self.sess_id req.lockds_req.req_id = self.get_next_req_id() @@ -327,7 +357,20 @@ class Session: assert reply.lockds_reply.success def get_data(self, query, data=True, config=False): - # Create the message + """Retrieve data from the mgmtd server based on an XPath query. + + Args: + query (str): The XPath query string. + data (bool, optional): Whether to retrieve state data. Defaults to True. + config (bool, optional): Whether to retrieve configuration data. + Defaults to False. + + Returns: + str: The retrieved data in JSON format. + + Raises: + AssertionError: If the response data is not properly formatted. + """ mdata, _ = self.get_native_msg_header(MSG_CODE_GET_DATA) flags = GET_DATA_FLAG_STATE if data else 0 flags |= GET_DATA_FLAG_CONFIG if config else 0 @@ -345,7 +388,12 @@ class Session: return result def add_notify_select(self, replace, notif_xpaths): - # Create the message + """Send a request to add notification subscriptions to the given XPaths. + + Args: + replace (bool): Whether to replace existing notification subscriptions. + notif_xpaths (list of str): List of XPaths to subscribe to notifications on. + """ mdata, _ = self.get_native_msg_header(MSG_CODE_NOTIFY_SELECT) mdata += struct.pack(MSG_NOTIFY_SELECT_FMT, replace) @@ -356,6 +404,18 @@ class Session: logging.debug("Sent NOTIFY_SELECT") def recv_notify(self, xpaths=None): + """Receive a notification message, optionally setting up XPath filters first. + + Args: + xpaths (list of str, optional): List of XPaths to filter notifications. + + Returns: + tuple: (result_type, operation, xpath, message data) + + Raises: + TimeoutError: If no notification is received within the timeout period. + Exception: If a non-notification message is received. + """ if xpaths: self.add_notify_select(True, xpaths) @@ -379,6 +439,7 @@ class Session: def __parse_args(): + """Parse command-line arguments for the mgmtd client.""" MPATH = "/var/run/frr/mgmtd_fe.sock" parser = argparse.ArgumentParser() parser.add_argument( @@ -416,6 +477,14 @@ def __parse_args(): def __server_connect(spath): + """Establish a connection to the mgmtd server over a Unix socket. + + Args: + spath (str): Path to the Unix domain socket. + + Returns: + socket: A connected Unix socket. + """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) logging.debug("Connecting to server on %s", spath) while ec := sock.connect_ex(str(spath)): @@ -428,6 +497,7 @@ def __server_connect(spath): def __main(): + """Process client commands and handle queries or notifications.""" args = __parse_args() sock = __server_connect(Path(args.server)) sess = Session(sock, use_protobuf=args.use_protobuf) @@ -473,6 +543,7 @@ def __main(): def main(): + """Entry point for the mgmtd client application.""" try: __main() except KeyboardInterrupt: diff --git a/tests/topotests/multicast_features/r1/frr.conf b/tests/topotests/multicast_features/r1/frr.conf index bd1cc4103c..dcf73c0879 100644 --- a/tests/topotests/multicast_features/r1/frr.conf +++ b/tests/topotests/multicast_features/r1/frr.conf @@ -1,5 +1,8 @@ log commands ! +!ip prefix-list pim-eth0-neighbors permit 192.168.2.0/24 +!ipv6 prefix-list pimv6-eth0-neighbors permit 2001:db8:2::/64 +! interface r1-eth0 ip address 192.168.1.1/24 ip pim @@ -9,8 +12,10 @@ interface r1-eth0 interface r1-eth1 ip address 192.168.2.1/24 ip pim + ip pim allowed-neighbors prefix-list pim-eth0-neighbors ipv6 address 2001:db8:2::1/64 ipv6 pim + ipv6 pim allowed-neighbors prefix-list pimv6-eth0-neighbors ! interface r1-eth2 ip address 192.168.100.1/24 @@ -45,4 +50,4 @@ router pim ! router pim6 rp 2001:db8:ffff::1 -!
\ No newline at end of file +! diff --git a/tests/topotests/multicast_features/test_multicast_features.py b/tests/topotests/multicast_features/test_multicast_features.py index 9c1f4af99f..f336557520 100644 --- a/tests/topotests/multicast_features/test_multicast_features.py +++ b/tests/topotests/multicast_features/test_multicast_features.py @@ -69,8 +69,10 @@ def build_topo(tgen): # R1 interface eth2 switch = tgen.add_switch("s3") tgen.add_host("h1", "192.168.100.100/24", "via 192.168.100.1") + tgen.add_host("h3", "192.168.100.101/24", "via 192.168.100.1") switch.add_link(tgen.gears["r1"]) switch.add_link(tgen.gears["h1"]) + switch.add_link(tgen.gears["h3"]) # R2 interface eth1 switch = tgen.add_switch("s4") @@ -147,10 +149,14 @@ def test_pim_convergence(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - def expect_pim_peer(router, iptype, interface, peer): + def expect_pim_peer(router, iptype, interface, peer, missing=False): "Wait until peer is present." - logger.info(f"waiting peer {peer} in {router}") - expected = {interface: {peer: {"upTime": "*"}}} + if missing: + logger.info(f"waiting peer {peer} in {router} to disappear") + expected = {interface: {peer: None}} + else: + logger.info(f"waiting peer {peer} in {router}") + expected = {interface: {peer: {"upTime": "*"}}} test_func = partial( topotest.router_json_cmp, @@ -164,7 +170,15 @@ def test_pim_convergence(): expect_pim_peer("r1", "ip", "r1-eth0", "192.168.1.2") expect_pim_peer("r2", "ip", "r2-eth0", "192.168.1.1") - expect_pim_peer("r1", "ip", "r1-eth1", "192.168.2.2") + + # This neighbor is denied by default + expect_pim_peer("r1", "ip", "r1-eth1", "192.168.2.2", missing=True) + # Lets configure the prefix list so the above neighbor gets accepted: + tgen.gears["r1"].vtysh_cmd(""" + configure terminal + ip prefix-list pim-eth0-neighbors permit 192.168.2.0/24 + """) + expect_pim_peer("r1", "ip", "r1-eth1", "192.168.2.2", missing=False) # # IPv6 part @@ -180,7 +194,14 @@ def test_pim_convergence(): expect_pim_peer("r1", "ipv6", "r1-eth0", r2_link_address) expect_pim_peer("r2", "ipv6", "r2-eth0", r1_r2_link_address) - expect_pim_peer("r1", "ipv6", "r1-eth1", r3_link_address) + expect_pim_peer("r1", "ipv6", "r1-eth1", r3_link_address, missing=True) + + tgen.gears["r1"].vtysh_cmd(f""" + configure terminal + ipv6 prefix-list pimv6-eth0-neighbors permit {r3_link_address}/64 + """) + + expect_pim_peer("r1", "ipv6", "r1-eth1", r3_link_address, missing=False) def test_igmp_group_limit(): @@ -189,11 +210,13 @@ def test_igmp_group_limit(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 ip igmp max-groups 4 - """) + """ + ) app_helper.run("h1", ["224.0.100.1", "h1-eth0"]) app_helper.run("h1", ["224.0.100.2", "h1-eth0"]) app_helper.run("h1", ["224.0.100.3", "h1-eth0"]) @@ -202,7 +225,9 @@ def test_igmp_group_limit(): app_helper.run("h1", ["224.0.100.6", "h1-eth0"]) def expect_igmp_group_count(): - igmp_groups = tgen.gears["r1"].vtysh_cmd("show ip igmp groups json", isjson=True) + igmp_groups = tgen.gears["r1"].vtysh_cmd( + "show ip igmp groups json", isjson=True + ) try: return len(igmp_groups["r1-eth2"]["groups"]) except KeyError: @@ -212,13 +237,15 @@ def test_igmp_group_limit(): # Cleanup app_helper.stop_host("h1") - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 no ip igmp max-groups 4 exit clear ip igmp interfaces - """) + """ + ) def test_igmp_group_source_limit(): @@ -227,12 +254,14 @@ def test_igmp_group_source_limit(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 ip igmp max-sources 4 exit - """) + """ + ) app_helper.run("h1", ["--source=192.168.100.10", "232.0.101.10", "h1-eth0"]) app_helper.run("h1", ["--source=192.168.100.11", "232.0.101.10", "h1-eth0"]) @@ -243,7 +272,9 @@ def test_igmp_group_source_limit(): app_helper.run("h1", ["--source=192.168.100.16", "232.0.101.10", "h1-eth0"]) def expect_igmp_group_source_count(): - igmp_sources = tgen.gears["r1"].vtysh_cmd("show ip igmp sources json", isjson=True) + igmp_sources = tgen.gears["r1"].vtysh_cmd( + "show ip igmp sources json", isjson=True + ) try: return len(igmp_sources["r1-eth2"]["232.0.101.10"]["sources"]) except KeyError: @@ -252,13 +283,15 @@ def test_igmp_group_source_limit(): topotest.run_and_expect(expect_igmp_group_source_count, 4, count=10, wait=2) # Cleanup - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 no ip igmp max-sources 4 exit clear ip igmp interfaces - """) + """ + ) app_helper.stop_host("h1") @@ -268,11 +301,13 @@ def test_mld_group_limit(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 ipv6 mld max-groups 14 - """) + """ + ) app_helper.run("h1", ["FF05::100", "h1-eth0"]) app_helper.run("h1", ["FF05::101", "h1-eth0"]) app_helper.run("h1", ["FF05::102", "h1-eth0"]) @@ -291,25 +326,27 @@ def test_mld_group_limit(): app_helper.run("h1", ["FF05::115", "h1-eth0"]) def expect_mld_group_count(): - mld_groups = tgen.gears["r1"].vtysh_cmd("show ipv6 mld groups json", isjson=True) + mld_groups = tgen.gears["r1"].vtysh_cmd( + "show ipv6 mld groups json", isjson=True + ) try: return len(mld_groups["r1-eth2"]["groups"]) except KeyError: return 0 - topotest.run_and_expect(expect_mld_group_count, 14, count=10, wait=2) - # Cleanup app_helper.stop_host("h1") - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 no ipv6 mld max-groups 4 exit clear ipv6 mld interfaces - """) + """ + ) def test_mld_group_source_limit(): @@ -318,12 +355,14 @@ def test_mld_group_source_limit(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 ipv6 mld max-sources 4 exit - """) + """ + ) app_helper.run("h1", ["--source=2001:db8:1::100", "FF35::100", "h1-eth0"]) app_helper.run("h1", ["--source=2001:db8:1::101", "FF35::100", "h1-eth0"]) @@ -334,7 +373,9 @@ def test_mld_group_source_limit(): app_helper.run("h1", ["--source=2001:db8:1::106", "FF35::100", "h1-eth0"]) def expect_mld_source_group_count(): - mld_sources = tgen.gears["r1"].vtysh_cmd("show ipv6 mld joins json", isjson=True) + mld_sources = tgen.gears["r1"].vtysh_cmd( + "show ipv6 mld joins json", isjson=True + ) try: return len(mld_sources["default"]["r1-eth2"]["ff35::100"].keys()) except KeyError: @@ -343,14 +384,122 @@ def test_mld_group_source_limit(): topotest.run_and_expect(expect_mld_source_group_count, 4, count=10, wait=2) # Cleanup - tgen.gears["r1"].vtysh_cmd(""" + tgen.gears["r1"].vtysh_cmd( + """ configure terminal interface r1-eth2 no ipv6 mld max-sources 4 exit clear ipv6 mld interfaces - """) + """ + ) + app_helper.stop_host("h1") + + +def test_igmp_immediate_leave(): + "Test IGMPv2 immediate leave feature." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sysctl_assure( + tgen.gears["h1"], "net.ipv4.conf.h1-eth0.force_igmp_version", "2" + ) + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-eth2 + ip igmp immediate-leave + """ + ) + + app_helper.run("h1", ["224.0.110.1", "h1-eth0"]) + app_helper.run("h3", ["224.0.110.1", "h3-eth0"]) + + def expect_igmp_group(): + igmp_groups = tgen.gears["r1"].vtysh_cmd( + "show ip igmp groups json", isjson=True + ) + try: + for group in igmp_groups["r1-eth2"]["groups"]: + if group["group"] == "224.0.110.1": + return True + + return False + except KeyError: + return False + + topotest.run_and_expect(expect_igmp_group, True, count=10, wait=2) + + # Send leave and expect immediate leave app_helper.stop_host("h1") + topotest.run_and_expect(expect_igmp_group, False, count=10, wait=2) + + # Clean up + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-eth2 + no ip igmp immediate-leave + """ + ) + topotest.sysctl_assure( + tgen.gears["h1"], "net.ipv4.conf.h1-eth0.force_igmp_version", "0" + ) + app_helper.stop_host("h3") + + +def test_mldv1_immediate_leave(): + "Test MLDv1 immediate leave feature." + tgen = get_topogen() + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + topotest.sysctl_assure( + tgen.gears["h1"], "net.ipv6.conf.h1-eth0.force_mld_version", "1" + ) + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-eth2 + ipv6 mld immediate-leave + """ + ) + + app_helper.run("h1", ["ff05::2000", "h1-eth0"]) + app_helper.run("h3", ["ff05::2000", "h3-eth0"]) + + def expect_mld_group(): + igmp_groups = tgen.gears["r1"].vtysh_cmd( + "show ipv6 mld groups json", isjson=True + ) + try: + for group in igmp_groups["r1-eth2"]["groups"]: + if group["group"] == "ff05::2000": + return True + + return False + except KeyError: + return False + + topotest.run_and_expect(expect_mld_group, True, count=10, wait=2) + + # Send leave and expect immediate leave + app_helper.stop_host("h1") + topotest.run_and_expect(expect_mld_group, False, count=10, wait=2) + + # Clean up + tgen.gears["r1"].vtysh_cmd( + """ + configure terminal + interface r1-eth2 + no ipv6 mld immediate-leave + """ + ) + topotest.sysctl_assure( + tgen.gears["h1"], "net.ipv6.conf.h1-eth0.force_mld_version", "0" + ) + app_helper.stop_host("h3") def test_memory_leak(): diff --git a/tests/topotests/ospf_sr_te_topo1/rt1/ospfd.conf b/tests/topotests/ospf_sr_te_topo1/rt1/ospfd.conf index 064a4bf286..b7f0d3f40d 100644 --- a/tests/topotests/ospf_sr_te_topo1/rt1/ospfd.conf +++ b/tests/topotests/ospf_sr_te_topo1/rt1/ospfd.conf @@ -10,6 +10,7 @@ log file ospfd.log ! interface lo ip ospf area 0.0.0.0 + ip ospf passive ! interface eth-sw1 ip ospf network point-to-point @@ -27,7 +28,6 @@ router ospf mpls-te export mpls-te router-address 1.1.1.1 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_te_topo1/rt2/ospfd.conf b/tests/topotests/ospf_sr_te_topo1/rt2/ospfd.conf index 65b20f0e63..734a0a6a39 100644 --- a/tests/topotests/ospf_sr_te_topo1/rt2/ospfd.conf +++ b/tests/topotests/ospf_sr_te_topo1/rt2/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! interface lo ip ospf area 0.0.0.0 + ip ospf passive ! interface eth-sw1 ip ospf network point-to-point @@ -38,7 +39,6 @@ router ospf !mpls-te export mpls-te router-address 2.2.2.2 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_te_topo1/rt4/ospfd.conf b/tests/topotests/ospf_sr_te_topo1/rt4/ospfd.conf index 7cdf032eaa..6e51513ea6 100644 --- a/tests/topotests/ospf_sr_te_topo1/rt4/ospfd.conf +++ b/tests/topotests/ospf_sr_te_topo1/rt4/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! interface lo ip ospf area 0.0.0.0 + ip ospf passive ! interface eth-rt2-1 ip ospf network point-to-point @@ -44,7 +45,6 @@ router ospf !mpls-te export mpls-te router-address 4.4.4.4 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_te_topo1/rt5/ospfd.conf b/tests/topotests/ospf_sr_te_topo1/rt5/ospfd.conf index 8f71cda443..4cf0ae0ced 100644 --- a/tests/topotests/ospf_sr_te_topo1/rt5/ospfd.conf +++ b/tests/topotests/ospf_sr_te_topo1/rt5/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! interface lo ip ospf area 0.0.0.0 + ip ospf passive ! interface eth-rt3-1 ip ospf network point-to-point @@ -44,7 +45,6 @@ router ospf ! mpls-te export mpls-te router-address 5.5.5.5 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_te_topo1/rt6/ospfd.conf b/tests/topotests/ospf_sr_te_topo1/rt6/ospfd.conf index 20c89757a8..77e50fb770 100644 --- a/tests/topotests/ospf_sr_te_topo1/rt6/ospfd.conf +++ b/tests/topotests/ospf_sr_te_topo1/rt6/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! interface lo ip ospf area 0.0.0.0 + ip ospf passive ! interface eth-rt4 ip ospf network point-to-point @@ -32,7 +33,6 @@ router ospf mpls-te export mpls-te router-address 6.6.6.6 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt1/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt1/ospfd.conf index be9abf6238..396ef6800e 100644 --- a/tests/topotests/ospf_sr_topo1/rt1/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt1/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-sw1 ip ospf network broadcast @@ -23,7 +24,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.1 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt2/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt2/ospfd.conf index 30ef12a79e..aca0772c21 100644 --- a/tests/topotests/ospf_sr_topo1/rt2/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt2/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-sw1 ip ospf network broadcast @@ -33,7 +34,6 @@ router ospf mpls-te on mpls-te router-address 2.2.2.2 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt3/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt3/ospfd.conf index e315679765..7601283d83 100644 --- a/tests/topotests/ospf_sr_topo1/rt3/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt3/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-sw1 ip ospf network broadcast @@ -33,7 +34,6 @@ router ospf mpls-te on mpls-te router-address 3.3.3.3 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 17000 24999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt4/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt4/ospfd.conf index 681aaa3a8c..8ce8f2ed96 100644 --- a/tests/topotests/ospf_sr_topo1/rt4/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt4/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-rt2-1 ip ospf network point-to-point @@ -38,7 +39,6 @@ router ospf mpls-te on mpls-te router-address 4.4.4.4 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt5/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt5/ospfd.conf index 0b441c70de..55ab5aa0d7 100644 --- a/tests/topotests/ospf_sr_topo1/rt5/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt5/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-rt3-1 ip ospf network point-to-point @@ -38,7 +39,6 @@ router ospf mpls-te on mpls-te router-address 5.5.5.5 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_sr_topo1/rt6/ospfd.conf b/tests/topotests/ospf_sr_topo1/rt6/ospfd.conf index 7bb5de9440..bd1c65416c 100644 --- a/tests/topotests/ospf_sr_topo1/rt6/ospfd.conf +++ b/tests/topotests/ospf_sr_topo1/rt6/ospfd.conf @@ -9,6 +9,7 @@ log file ospfd.log ! debug ospf zebra ! interface lo + ip ospf passive ! interface eth-rt4 ip ospf network point-to-point @@ -28,7 +29,6 @@ router ospf mpls-te on mpls-te router-address 6.6.6.6 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_tilfa_topo1/rt1/ospfd.conf b/tests/topotests/ospf_tilfa_topo1/rt1/ospfd.conf index 04b2c381e2..f9c4eded48 100644 --- a/tests/topotests/ospf_tilfa_topo1/rt1/ospfd.conf +++ b/tests/topotests/ospf_tilfa_topo1/rt1/ospfd.conf @@ -2,6 +2,7 @@ ! debug ospf ti-lfa ! interface lo + ip ospf passive ! interface eth-rt2 ip ospf network point-to-point @@ -19,7 +20,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.1 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_tilfa_topo1/rt2/ospfd.conf b/tests/topotests/ospf_tilfa_topo1/rt2/ospfd.conf index e6e4847ba7..b364580532 100644 --- a/tests/topotests/ospf_tilfa_topo1/rt2/ospfd.conf +++ b/tests/topotests/ospf_tilfa_topo1/rt2/ospfd.conf @@ -2,6 +2,7 @@ ! debug ospf ti-lfa ! interface lo + ip ospf passive ! interface eth-rt1 ip ospf network point-to-point @@ -19,7 +20,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.2 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_tilfa_topo1/rt3/ospfd.conf b/tests/topotests/ospf_tilfa_topo1/rt3/ospfd.conf index 472cdc6bcb..a22eabcf95 100644 --- a/tests/topotests/ospf_tilfa_topo1/rt3/ospfd.conf +++ b/tests/topotests/ospf_tilfa_topo1/rt3/ospfd.conf @@ -2,6 +2,7 @@ ! debug ospf ti-lfa ! interface lo + ip ospf passive ! interface eth-rt1 ip ospf network point-to-point @@ -19,7 +20,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.3 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_tilfa_topo1/rt4/ospfd.conf b/tests/topotests/ospf_tilfa_topo1/rt4/ospfd.conf index 75770dc5dd..a128355adb 100644 --- a/tests/topotests/ospf_tilfa_topo1/rt4/ospfd.conf +++ b/tests/topotests/ospf_tilfa_topo1/rt4/ospfd.conf @@ -2,6 +2,7 @@ ! debug ospf ti-lfa ! interface lo + ip ospf passive ! interface eth-rt2 ip ospf network point-to-point @@ -19,7 +20,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.4 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/ospf_tilfa_topo1/rt5/ospfd.conf b/tests/topotests/ospf_tilfa_topo1/rt5/ospfd.conf index ef9d583ae9..8a0312afc8 100644 --- a/tests/topotests/ospf_tilfa_topo1/rt5/ospfd.conf +++ b/tests/topotests/ospf_tilfa_topo1/rt5/ospfd.conf @@ -2,6 +2,7 @@ ! debug ospf ti-lfa ! interface lo + ip ospf passive ! interface eth-rt3 ip ospf network point-to-point @@ -19,7 +20,6 @@ router ospf mpls-te on mpls-te router-address 1.1.1.5 router-info area 0.0.0.0 - passive-interface lo segment-routing on segment-routing global-block 16000 23999 segment-routing node-msd 8 diff --git a/tests/topotests/pim_acl/r1/ospfd.conf b/tests/topotests/pim_acl/r1/ospfd.conf index c453dec96c..569e8b279e 100644 --- a/tests/topotests/pim_acl/r1/ospfd.conf +++ b/tests/topotests/pim_acl/r1/ospfd.conf @@ -2,6 +2,9 @@ hostname r1 ! ! debug ospf event ! +interface r1-eth0 + ip ospf passive +! interface r1-eth1 ip ospf hello-interval 2 ip ospf dead-interval 10 @@ -9,7 +12,6 @@ interface r1-eth1 ! router ospf ospf router-id 192.168.0.1 - passive-interface r1-eth0 network 192.168.0.1/32 area 0 network 192.168.100.0/24 area 0 network 192.168.101.0/24 area 0 diff --git a/tests/topotests/pim_autorp/test_pim_autorp.py b/tests/topotests/pim_autorp/test_pim_autorp.py index 61cf8ebbc5..5edf1db6af 100644 --- a/tests/topotests/pim_autorp/test_pim_autorp.py +++ b/tests/topotests/pim_autorp/test_pim_autorp.py @@ -158,6 +158,103 @@ def test_pim_autorp_init(request): ) +def test_pim_autorp_disable_enable(request): + "Test PIM AutoRP disable and re-enable works properly" + tgen = get_topogen() + tc_name = request.node.name + write_test_header(tc_name) + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + step("Ensure AutoRP groups are joined on all routers") + for rtr in ["r1", "r2", "r3", "r4"]: + expected = { + f"{rtr}-eth0": { + "name": f"{rtr}-eth0", + "224.0.1.39": "*", + "224.0.1.40": "*", + }, + f"{rtr}-eth1": { + "name": f"{rtr}-eth1", + "224.0.1.39": "*", + "224.0.1.40": "*", + }, + } + + test_func = partial( + topotest.router_json_cmp, + tgen.gears[rtr], + "show ip igmp sources json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None) + assert result is None, "{} does not have correct autorp groups joined".format( + rtr + ) + + step("Disable AutoRP on all routers") + for rtr in ["r1", "r2", "r3", "r4"]: + tgen.routers()[rtr].vtysh_cmd( + """ + conf + router pim + no autorp discovery + """ + ) + + step("Ensure AutoRP groups are no longer joined on all routers") + for rtr in ["r1", "r2", "r3", "r4"]: + expected = {f"{rtr}-eth0": None, f"{rtr}-eth1": None} + + test_func = partial( + topotest.router_json_cmp, + tgen.gears[rtr], + "show ip igmp sources json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None) + assert result is None, "{} does not have correct autorp groups joined".format( + rtr + ) + + step("Re-enable AutoRP on all routers") + for rtr in ["r1", "r2", "r3", "r4"]: + tgen.routers()[rtr].vtysh_cmd( + """ + conf + router pim + autorp discovery + """ + ) + + step("Ensure AutoRP groups are re-joined on all routers") + for rtr in ["r1", "r2", "r3", "r4"]: + expected = { + f"{rtr}-eth0": { + "name": f"{rtr}-eth0", + "224.0.1.39": "*", + "224.0.1.40": "*", + }, + f"{rtr}-eth1": { + "name": f"{rtr}-eth1", + "224.0.1.39": "*", + "224.0.1.40": "*", + }, + } + + test_func = partial( + topotest.router_json_cmp, + tgen.gears[rtr], + "show ip igmp sources json", + expected, + ) + _, result = topotest.run_and_expect(test_func, None) + assert result is None, "{} does not have correct autorp groups joined".format( + rtr + ) + + def test_pim_autorp_no_mapping_agent_rp(request): "Test PIM AutoRP candidate with no mapping agent" tgen = get_topogen() diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids.json b/tests/topotests/static_srv6_sids/expected_srv6_sids.json index de78878445..1796c870a6 100644 --- a/tests/topotests/static_srv6_sids/expected_srv6_sids.json +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids.json @@ -162,5 +162,40 @@ } ] } + ], + "fcbb:bbbb:1:fe40::/64": [ + { + "prefix": "fcbb:bbbb:1:fe40::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "sr0", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.X" + }, + "seg6localContext": { + "nh6": "2001::2" + } + } + ] + } ] }
\ No newline at end of file diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json index dd0850fb3c..bd1f4bf87a 100644 --- a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json @@ -121,5 +121,40 @@ } ] } + ], + "fcbb:bbbb:1:fe40::/64": [ + { + "prefix": "fcbb:bbbb:1:fe40::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "sr0", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.X" + }, + "seg6localContext": { + "nh6": "2001::2" + } + } + ] + } ] }
\ No newline at end of file diff --git a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json index 4051c01425..2bd40cdb5c 100644 --- a/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json +++ b/tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json @@ -80,5 +80,40 @@ } ] } + ], + "fcbb:bbbb:1:fe40::/64": [ + { + "prefix": "fcbb:bbbb:1:fe40::/64", + "prefixLen": 64, + "protocol": "static", + "vrfId": 0, + "vrfName": "default", + "selected": true, + "destSelected": true, + "distance": 1, + "metric": 0, + "installed": true, + "table": 254, + "internalStatus": 16, + "internalFlags": 9, + "internalNextHopNum": 1, + "internalNextHopActiveNum": 1, + "nexthops": [ + { + "flags": 3, + "fib": true, + "directlyConnected": true, + "interfaceName": "sr0", + "active": true, + "weight": 1, + "seg6local": { + "action": "End.X" + }, + "seg6localContext": { + "nh6": "2001::2" + } + } + ] + } ] }
\ No newline at end of file diff --git a/tests/topotests/static_srv6_sids/r1/frr.conf b/tests/topotests/static_srv6_sids/r1/frr.conf index b4904d9ac2..ce8fb88165 100644 --- a/tests/topotests/static_srv6_sids/r1/frr.conf +++ b/tests/topotests/static_srv6_sids/r1/frr.conf @@ -12,6 +12,7 @@ segment-routing sid fcbb:bbbb:1:fe10::/64 locator MAIN behavior uDT4 vrf Vrf10 sid fcbb:bbbb:1:fe20::/64 locator MAIN behavior uDT6 vrf Vrf20 sid fcbb:bbbb:1:fe30::/64 locator MAIN behavior uDT46 vrf Vrf30 + sid fcbb:bbbb:1:fe40::/64 locator MAIN behavior uA interface sr0 nexthop 2001::2 ! ! !
\ No newline at end of file diff --git a/tests/topotests/zebra_operational_data/r1/frr.conf b/tests/topotests/zebra_operational_data/r1/frr.conf new file mode 100644 index 0000000000..72a67bf020 --- /dev/null +++ b/tests/topotests/zebra_operational_data/r1/frr.conf @@ -0,0 +1,41 @@ +log timestamp precision 6 +log file frr.log + +no debug memstats-at-exit + +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks + +debug mgmt backend datastore frontend transaction +debug mgmt client frontend +debug mgmt client backend + +interface r1-eth0 + ip address 1.1.1.1/24 + ipv6 address 2001:1111::1/64 +exit + +interface r1-eth1 + ip address 2.2.2.1/24 + ipv6 address 2002:2222::1/64 +exit + +interface r1-eth2 vrf red + ip address 3.3.3.1/24 + ipv6 address 2003:333::1/64 +exit + +interface r1-eth3 vrf red + ip address 4.4.4.1/24 + ipv6 address 2004:4444::1/64 +exit + +ip route 11.0.0.0/8 Null0 +ip route 11.11.11.11/32 1.1.1.2 +ip route 12.12.12.12/32 2.2.2.2 + +ip route 13.0.0.0/8 Null0 vrf red +ip route 13.13.13.13/32 3.3.3.2 vrf red +ip route 14.14.14.14/32 4.4.4.2 vrf red
\ No newline at end of file diff --git a/tests/topotests/zebra_operational_data/test_zebra_operational.py b/tests/topotests/zebra_operational_data/test_zebra_operational.py new file mode 100644 index 0000000000..c4ab5be973 --- /dev/null +++ b/tests/topotests/zebra_operational_data/test_zebra_operational.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# Copyright (c) 2025 Nvidia Inc. +# Donald Sharp +# +""" +Test zebra operational values +""" + +import pytest +import json +from lib.topogen import Topogen +from lib.topolog import logger + + +pytestmark = [pytest.mark.mgmtd] + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = {"s1": ("r1",), "s2": ("r1",), "s3": ("r1",), "s4": ("r1",)} + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + # Setup VRF red + router.net.add_l3vrf("red", 10) + router.net.add_loop("lo-red") + router.net.attach_iface_to_l3vrf("lo-red", "red") + router.net.attach_iface_to_l3vrf(rname + "-eth2", "red") + router.net.attach_iface_to_l3vrf(rname + "-eth3", "red") + router.load_frr_config("frr.conf") + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +def test_zebra_operationalr(tgen): + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + + output = json.loads(r1.vtysh_cmd("show mgmt get-data /frr-zebra:zebra")) + + logger.info("Output") + logger.info(output) + + logger.info("Ensuring that the max-multipath value is returned") + assert "max-multipath" in output["frr-zebra:zebra"].keys() + + +if __name__ == "__main__": + # To suppress tracebacks, either use the following pytest call or add "--tb=no" to cli + # retval = pytest.main(["-s", "--tb=no"]) + retval = pytest.main(["-s"]) + sys.exit(retval) diff --git a/yang/frr-gmp.yang b/yang/frr-gmp.yang index 26b19501f9..3843c88cff 100644 --- a/yang/frr-gmp.yang +++ b/yang/frr-gmp.yang @@ -186,6 +186,14 @@ module frr-gmp { } } + leaf immediate-leave { + type boolean; + default "false"; + description + "Immediately drop group memberships on receiving IGMPv2/MLDv1 Leave. + Has no effect when IGMPv3/MLDv2 is in use."; + } + list static-group { key "group-addr source-addr"; description diff --git a/yang/frr-pim.yang b/yang/frr-pim.yang index 6b6870f666..e0d8800f3e 100644 --- a/yang/frr-pim.yang +++ b/yang/frr-pim.yang @@ -516,6 +516,12 @@ module frr-pim { "Hello holdtime"; } + leaf neighbor-filter-prefix-list { + type plist-ref; + description + "Prefix-List to filter allowed PIM neighbors."; + } + container bfd { presence "Enable BFD support on the interface."; diff --git a/yang/frr-route-types.yang b/yang/frr-route-types.yang index aa676cebc2..76aa90e778 100644 --- a/yang/frr-route-types.yang +++ b/yang/frr-route-types.yang @@ -50,111 +50,144 @@ module frr-route-types { type enumeration { enum kernel { value 1; + description "Kernel route"; } enum connected { value 2; + description "Connected route"; } enum local { value 3; + description "Local route"; } enum static { value 4; + description "Static route"; } enum rip { value 5; + description "RIP route"; } enum ospf { value 7; + description "OSPF route"; } enum isis { value 9; + description "ISIS route"; } enum bgp { value 10; + description "BGP route"; } enum eigrp { value 12; + description "EIGRP route"; } enum nhrp { value 13; + description "NHRP route"; } enum table { value 16; + description "Table route"; } enum vnc { value 18; + description "VNC route"; } enum vnc-direct { value 19; + description "VNC Direct route"; } enum babel { value 23; + description "Babel route"; } enum sharp { value 24; + description "SHARP route"; } enum openfabric { value 27; + description "Openfabric route"; } } + description "Enumeration of supported IPv4 route types"; } typedef frr-route-types-v6 { type enumeration { enum kernel { value 1; + description "Kernel route"; } enum connected { value 2; + description "Connected route"; } enum local { value 3; + description "Local route"; } enum static { value 4; + description "Static route"; } enum ripng { value 6; + description "RIPng route"; } enum ospf6 { value 8; + description "OSPFv3 route"; } enum isis { value 9; + description "ISIS route"; } enum bgp { value 10; + description "BGP route"; } enum nhrp { value 13; + description "NHRP route"; } enum table { value 16; + description "Table route"; } enum vnc { value 18; + description "VNC route"; } enum vnc-direct { value 19; + description "VNC Direct route"; } enum babel { value 23; + description "Babel route"; } enum sharp { value 24; + description "SHARP route"; } enum openfabric { value 27; + description "OpenFabric route"; } } + description "Enumeration of supported IPv6 route types"; } typedef frr-route-types { - description "Route types as enumerated in `lib/route_types.txt`"; type union { type frr-route-types-v4; type frr-route-types-v6; } + description "Route types as enumerated in `lib/route_types.txt`"; } typedef ipv4-multicast-group-prefix { @@ -177,12 +210,12 @@ module frr-route-types { } typedef ip-multicast-group-prefix { - description "The IP-Multicast-Group-Address-Prefix type represents an IP multicast address - prefix and is IP version neutral. The format of the textual representations implies the IP - version. It includes a prefix-length, separated by a '/' sign."; type union { type ipv4-multicast-group-prefix; type ipv6-multicast-group-prefix; } + description "The IP-Multicast-Group-Address-Prefix type represents an IP multicast address + prefix and is IP version neutral. The format of the textual representations implies the IP + version. It includes a prefix-length, separated by a '/' sign."; } } diff --git a/yang/frr-staticd.yang b/yang/frr-staticd.yang index 8d0e58c0a5..3bf3a5e817 100644 --- a/yang/frr-staticd.yang +++ b/yang/frr-staticd.yang @@ -12,6 +12,10 @@ module frr-staticd { prefix frr-nexthop; } + import frr-interface { + prefix frr-interface; + } + import ietf-inet-types { prefix inet; } @@ -240,6 +244,24 @@ module frr-staticd { description "The VRF name."; } + list paths { + key "path-index"; + leaf path-index { + type uint8; + description + "Path index"; + } + leaf interface { + type frr-interface:interface-ref; + description + "Interface name."; + } + leaf next-hop { + type inet:ip-address; + description + "Nexthop IP address."; + } + } } } } diff --git a/yang/frr-test-module.yang b/yang/frr-test-module.yang index 773a959553..909c199b2f 100644 --- a/yang/frr-test-module.yang +++ b/yang/frr-test-module.yang @@ -139,5 +139,23 @@ module frr-test-module { } } } + choice bchoice { + description "a choice statement"; + case case3 { + leaf c3value { + type uint8; + description "A uint8 value for case 3"; + } + } + case case4 { + container c4cont { + description "case 2 container"; + leaf c4value { + type uint32; + description "A uint32 value for case 4"; + } + } + } + } } } diff --git a/yang/frr-zebra.yang b/yang/frr-zebra.yang index a3c066c56c..5f1b711b97 100644 --- a/yang/frr-zebra.yang +++ b/yang/frr-zebra.yang @@ -2842,6 +2842,17 @@ module frr-zebra { container zebra { description "Data model for the Zebra daemon."; + leaf max-multipath { + config false; + type uint16 { + range "1..65535"; + } + description + "The maximum number of nexthops for a route. At this point it + is unlikely that a multipath number will ever get larger then + 1024 but to allow for future expansions, the yang returns a + 16 bit number"; + } leaf ip-forwarding { type boolean; description diff --git a/zebra/if_netlink.c b/zebra/if_netlink.c index 7ef3fa2e61..1cfcc84bd9 100644 --- a/zebra/if_netlink.c +++ b/zebra/if_netlink.c @@ -221,6 +221,8 @@ static void netlink_determine_zebra_iftype(const char *kind, *zif_type = ZEBRA_IF_BOND; else if (strcmp(kind, "gre") == 0) *zif_type = ZEBRA_IF_GRE; + else if (strcmp(kind, "dummy") == 0) + *zif_type = ZEBRA_IF_DUMMY; } static void netlink_vrf_change(struct nlmsghdr *h, struct rtattr *tb, @@ -576,6 +578,7 @@ static void netlink_interface_update_l2info(struct zebra_dplane_ctx *ctx, case ZEBRA_IF_MACVLAN: case ZEBRA_IF_VETH: case ZEBRA_IF_BOND: + case ZEBRA_IF_DUMMY: break; } } diff --git a/zebra/interface.c b/zebra/interface.c index e49e8eac5e..b7a790382d 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -548,6 +548,9 @@ void if_add_update(struct interface *ifp) zebra_interface_add_update(ifp); + if (IS_ZEBRA_IF_DUMMY(ifp)) + SET_FLAG(ifp->status, ZEBRA_INTERFACE_DUMMY); + if (!CHECK_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE)) { SET_FLAG(ifp->status, ZEBRA_INTERFACE_ACTIVE); @@ -1616,6 +1619,7 @@ static void interface_update_l2info(struct zebra_dplane_ctx *ctx, case ZEBRA_IF_MACVLAN: case ZEBRA_IF_VETH: case ZEBRA_IF_BOND: + case ZEBRA_IF_DUMMY: break; } } @@ -2369,6 +2373,9 @@ static const char *zebra_ziftype_2str(enum zebra_iftype zif_type) case ZEBRA_IF_GRE: return "GRE"; + case ZEBRA_IF_DUMMY: + return "dummy"; + default: return "Unknown"; } diff --git a/zebra/interface.h b/zebra/interface.h index 2c7a079bf4..34e57088a7 100644 --- a/zebra/interface.h +++ b/zebra/interface.h @@ -39,7 +39,8 @@ enum zebra_iftype { ZEBRA_IF_MACVLAN, /* MAC VLAN interface*/ ZEBRA_IF_VETH, /* VETH interface*/ ZEBRA_IF_BOND, /* Bond */ - ZEBRA_IF_GRE, /* GRE interface */ + ZEBRA_IF_GRE, /* GRE interface */ + ZEBRA_IF_DUMMY, /* Dummy interface */ }; /* Zebra "slave" interface type */ @@ -246,6 +247,9 @@ DECLARE_HOOK(zebra_if_extra_info, (struct vty * vty, struct interface *ifp), #define IS_ZEBRA_IF_GRE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_GRE) +#define IS_ZEBRA_IF_DUMMY(ifp) \ + (((struct zebra_if *)(ifp->info))->zif_type == ZEBRA_IF_DUMMY) + #define IS_ZEBRA_IF_BRIDGE_SLAVE(ifp) \ (((struct zebra_if *)(ifp->info))->zif_slave_type \ == ZEBRA_IF_SLAVE_BRIDGE) diff --git a/zebra/rtadv.c b/zebra/rtadv.c index 8f6713517d..2641831bcd 100644 --- a/zebra/rtadv.c +++ b/zebra/rtadv.c @@ -47,14 +47,6 @@ DEFINE_MTYPE_STATIC(ZEBRA, ADV_IF, "Advertised Interface"); #include <netinet/icmp6.h> #endif -/* If RFC2133 definition is used. */ -#ifndef IPV6_JOIN_GROUP -#define IPV6_JOIN_GROUP IPV6_ADD_MEMBERSHIP -#endif -#ifndef IPV6_LEAVE_GROUP -#define IPV6_LEAVE_GROUP IPV6_DROP_MEMBERSHIP -#endif - #define ALLNODE "ff02::1" #define ALLROUTER "ff02::2" diff --git a/zebra/zebra_cli.c b/zebra/zebra_cli.c index bb79928326..8b2eab3f5d 100644 --- a/zebra/zebra_cli.c +++ b/zebra/zebra_cli.c @@ -2355,12 +2355,32 @@ DEFPY_YANG (vni_mapping, "VNI-ID\n" "prefix-routes-only\n") { - if (!no) + const struct lyd_node *dnode; + const char *vrf; + + if (!no) { nb_cli_enqueue_change(vty, "./frr-zebra:zebra/l3vni-id", NB_OP_MODIFY, vni_str); - else - nb_cli_enqueue_change(vty, "./frr-zebra:zebra/l3vni-id", NB_OP_DESTROY, - NULL); + } else { + if (vty->node == CONFIG_NODE) { + if (yang_dnode_existsf(vty->candidate_config->dnode, + "/frr-vrf:lib/vrf[name='%s']/frr-zebra:zebra[l3vni-id='%lu']", + VRF_DEFAULT_NAME, vni)) + nb_cli_enqueue_change(vty, "./frr-zebra:zebra/l3vni-id", + NB_OP_DESTROY, NULL); + } else { + dnode = yang_dnode_get(vty->candidate_config->dnode, VTY_CURR_XPATH); + if (dnode) { + vrf = yang_dnode_get_string(dnode, "name"); + + if (yang_dnode_existsf(vty->candidate_config->dnode, + "/frr-vrf:lib/vrf[name='%s']/frr-zebra:zebra[l3vni-id='%lu']", + vrf, vni)) + nb_cli_enqueue_change(vty, "./frr-zebra:zebra/l3vni-id", + NB_OP_DESTROY, NULL); + } + } + } if (filter) nb_cli_enqueue_change(vty, "./frr-zebra:zebra/prefix-only", diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c index 9c252cc63c..4344a8d79a 100644 --- a/zebra/zebra_dplane.c +++ b/zebra/zebra_dplane.c @@ -6210,8 +6210,7 @@ int dplane_show_provs_helper(struct vty *vty, bool detailed) in_max = atomic_load_explicit(&prov->dp_in_max, memory_order_relaxed); - out = atomic_load_explicit(&prov->dp_out_counter, - memory_order_relaxed); + out = dplane_provider_out_ctx_queue_len(prov); out_max = atomic_load_explicit(&prov->dp_out_max, memory_order_relaxed); diff --git a/zebra/zebra_nb.c b/zebra/zebra_nb.c index 6b41993a95..81fe2ab6a0 100644 --- a/zebra/zebra_nb.c +++ b/zebra/zebra_nb.c @@ -26,6 +26,12 @@ const struct frr_yang_module_info frr_zebra_info = { .features = features, .nodes = { { + .xpath = "/frr-zebra:zebra/max-multipath", + .cbs = { + .get_elem = zebra_max_multipath_get_elem, + } + }, + { .xpath = "/frr-zebra:zebra/ip-forwarding", .cbs = { .modify = zebra_ip_forwarding_modify, diff --git a/zebra/zebra_nb.h b/zebra/zebra_nb.h index 785291bc68..426ad4b7de 100644 --- a/zebra/zebra_nb.h +++ b/zebra/zebra_nb.h @@ -14,6 +14,7 @@ extern "C" { extern const struct frr_yang_module_info frr_zebra_info; /* prototypes */ +struct yang_data *zebra_max_multipath_get_elem(struct nb_cb_get_elem_args *args); int get_route_information_rpc(struct nb_cb_rpc_args *args); int get_v6_mroute_info_rpc(struct nb_cb_rpc_args *args); int get_vrf_info_rpc(struct nb_cb_rpc_args *args); diff --git a/zebra/zebra_nb_state.c b/zebra/zebra_nb_state.c index 6ed11f75f1..bbed4535e1 100644 --- a/zebra/zebra_nb_state.c +++ b/zebra/zebra_nb_state.c @@ -87,6 +87,9 @@ lib_interface_zebra_state_zif_type_get_elem(struct nb_cb_get_elem_args *args) case ZEBRA_IF_GRE: type = "frr-zebra:zif-gre"; break; + case ZEBRA_IF_DUMMY: + type = "frr-zebra:zif-dummy"; + break; } if (!type) @@ -1157,3 +1160,12 @@ lib_vrf_zebra_ribs_rib_route_route_entry_nexthop_group_nexthop_weight_get_elem( return NULL; } + +/* + * XPath: + * /frr-zebra:zebra/max-multipath + */ +struct yang_data *zebra_max_multipath_get_elem(struct nb_cb_get_elem_args *args) +{ + return yang_data_new_uint16(args->xpath, zrouter.multipath_num); +} diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c index a1c8cd3059..8cea605f41 100644 --- a/zebra/zebra_rib.c +++ b/zebra/zebra_rib.c @@ -391,11 +391,10 @@ int zebra_check_addr(const struct prefix *p) if (p->family == AF_INET) { uint32_t addr; - addr = p->u.prefix4.s_addr; - addr = ntohl(addr); + addr = ntohl(p->u.prefix4.s_addr); - if (IPV4_NET127(addr) || IN_CLASSD(addr) - || IPV4_LINKLOCAL(addr)) + if (IPV4_NET127(addr) || IN_CLASSD(addr) || + (IPV4_LINKLOCAL(addr) && !IPV4_CLASS_E(addr))) return 0; } if (p->family == AF_INET6) { diff --git a/zebra/zebra_srv6.c b/zebra/zebra_srv6.c index 6d228c5e24..51efcceb75 100644 --- a/zebra/zebra_srv6.c +++ b/zebra/zebra_srv6.c @@ -18,6 +18,7 @@ #include "zebra/zebra_srv6.h" #include "zebra/zebra_errors.h" #include "zebra/ge_netlink.h" +#include "zebra/interface.h" #include <stdio.h> #include <string.h> @@ -1745,6 +1746,13 @@ int get_srv6_sid(struct zebra_srv6_sid **sid, struct srv6_sid_ctx *ctx, int ret = -1; struct srv6_locator *locator; char buf[256]; + struct nhg_connected *rb_node_dep = NULL; + struct listnode *node; + struct nexthop *nexthop; + struct nbr_connected *nc; + bool found = false; + struct interface *ifp; + struct zebra_if *zebra_if; enum srv6_sid_alloc_mode alloc_mode = (sid_value) ? SRV6_SID_ALLOC_MODE_EXPLICIT @@ -1755,6 +1763,44 @@ int get_srv6_sid(struct zebra_srv6_sid **sid, struct srv6_sid_ctx *ctx, __func__, srv6_sid_ctx2str(buf, sizeof(buf), ctx), sid_value, srv6_sid_alloc_mode2str(alloc_mode)); + if (ctx->ifindex != 0 && IPV6_ADDR_SAME(&ctx->nh6, &in6addr_any)) { + ifp = if_lookup_by_index(ctx->ifindex, VRF_DEFAULT); + if (!ifp) { + zlog_err("%s: interface %u does not exist", __func__, ctx->ifindex); + return -1; + } + + for (ALL_LIST_ELEMENTS_RO(ifp->nbr_connected, node, nc)) + if (nc->address && nc->address->family == AF_INET6 && + IN6_IS_ADDR_LINKLOCAL(&nc->address->u.prefix6)) { + ctx->nh6 = nc->address->u.prefix6; + found = true; + break; + } + + if (!found) { + zebra_if = ifp->info; + + frr_each (nhg_connected_tree, &zebra_if->nhg_dependents, rb_node_dep) { + for (ALL_NEXTHOPS(rb_node_dep->nhe->nhg, nexthop)) { + /* skip non link-local addresses */ + if (!IPV6_ADDR_SAME(&nexthop->gate.ipv6, &in6addr_any)) { + ctx->nh6 = nexthop->gate.ipv6; + found = true; + break; + } + } + if (found) + break; + } + if (!found) { + zlog_err("%s: cannot get SID, interface (ifindex %u) not found", + __func__, ctx->ifindex); + return -1; + } + } + } + if (alloc_mode == SRV6_SID_ALLOC_MODE_EXPLICIT) { /* * Explicit SID allocation: allocate a specific SID value |
