summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_packet.c8
-rw-r--r--bgpd/bgp_route.c15
-rw-r--r--bgpd/bgpd.c49
-rw-r--r--doc/developer/mgmtd-dev.rst8
-rw-r--r--doc/manpages/frr-fabricd.rst4
-rw-r--r--doc/user/basic.rst9
-rw-r--r--doc/user/fabricd.rst17
-rw-r--r--doc/user/pim.rst8
-rw-r--r--doc/user/pimv6.rst8
-rw-r--r--doc/user/ripd.rst6
-rw-r--r--doc/user/static.rst11
-rw-r--r--isisd/isis_circuit.c9
-rw-r--r--isisd/isis_main.c16
-rw-r--r--isisd/isis_zebra.c32
-rw-r--r--isisd/isisd.c19
-rw-r--r--isisd/isisd.h4
-rw-r--r--lib/if.h1
-rw-r--r--lib/keychain_nb.c4
-rw-r--r--lib/northbound.c50
-rw-r--r--lib/northbound.h50
-rw-r--r--lib/northbound_notif.c45
-rw-r--r--lib/northbound_oper.c371
-rw-r--r--lib/prefix.c5
-rw-r--r--lib/srv6.h27
-rw-r--r--lib/yang.c16
-rw-r--r--lib/yang.h9
-rw-r--r--ospf6d/ospf6d.h16
-rw-r--r--ospfd/ospf_lsa.c10
-rw-r--r--ospfd/ospf_lsa.h1
-rw-r--r--pimd/pim6_cmd.c44
-rw-r--r--pimd/pim6_mld.c34
-rw-r--r--pimd/pim_autorp.c63
-rw-r--r--pimd/pim_autorp.h1
-rw-r--r--pimd/pim_cmd.c44
-rw-r--r--pimd/pim_iface.c8
-rw-r--r--pimd/pim_iface.h4
-rw-r--r--pimd/pim_igmpv3.c24
-rw-r--r--pimd/pim_instance.c7
-rw-r--r--pimd/pim_mroute.c10
-rw-r--r--pimd/pim_nb.c15
-rw-r--r--pimd/pim_nb.h3
-rw-r--r--pimd/pim_nb_config.c72
-rw-r--r--pimd/pim_pim.c38
-rw-r--r--pimd/pim_register.c19
-rw-r--r--pimd/pim_upstream.c6
-rw-r--r--pimd/pim_vty.c12
-rw-r--r--ripd/rip_cli.c4
-rw-r--r--ripngd/ripng_interface.c8
-rw-r--r--staticd/static_nb.c21
-rw-r--r--staticd/static_nb.h16
-rw-r--r--staticd/static_nb_config.c107
-rw-r--r--staticd/static_routes.c5
-rw-r--r--staticd/static_vty.c70
-rw-r--r--staticd/static_zebra.c98
-rw-r--r--tests/lib/northbound/test_oper_data.c26
-rw-r--r--tests/lib/northbound/test_oper_data.in4
-rw-r--r--tests/lib/northbound/test_oper_data.refout34
-rw-r--r--tests/lib/northbound/test_oper_exists.c266
-rw-r--r--tests/lib/northbound/test_oper_exists.in8
-rw-r--r--tests/lib/northbound/test_oper_exists.py5
-rw-r--r--tests/lib/northbound/test_oper_exists.refout208
-rw-r--r--tests/lib/subdir.am13
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/__init__.py0
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/frr.conf52
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast.json24
l---------tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_unicast_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv4_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast.json25
l---------tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_unicast_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_ipv6_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/ce1/show_bgp_summary.json24
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/frr.conf107
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn.json29
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv4_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn.json29
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_ipv6_vpn_step1.json1
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/pe1/show_bgp_summary.json46
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/frr.conf79
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn.json28
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv4_vpn_step1.json12
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn.json28
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_ipv6_vpn_step1.json12
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/rr1/show_bgp_summary.json24
-rw-r--r--tests/topotests/bgp_l3vpn_hidden/test_bgp_l3vpn_hidden.py287
-rw-r--r--tests/topotests/bgp_vrf_route_leak_basic/r1/bgpd.conf14
-rw-r--r--tests/topotests/bgp_vrf_route_leak_basic/test_bgp-vrf-route-leak-basic.py10
-rw-r--r--tests/topotests/lib/bgp.py65
-rwxr-xr-xtests/topotests/lib/fe_client.py81
-rw-r--r--tests/topotests/multicast_features/r1/frr.conf7
-rw-r--r--tests/topotests/multicast_features/test_multicast_features.py203
-rw-r--r--tests/topotests/ospf_sr_te_topo1/rt1/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_te_topo1/rt2/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_te_topo1/rt4/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_te_topo1/rt5/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_te_topo1/rt6/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt1/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt2/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt3/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt4/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt5/ospfd.conf2
-rw-r--r--tests/topotests/ospf_sr_topo1/rt6/ospfd.conf2
-rw-r--r--tests/topotests/ospf_tilfa_topo1/rt1/ospfd.conf2
-rw-r--r--tests/topotests/ospf_tilfa_topo1/rt2/ospfd.conf2
-rw-r--r--tests/topotests/ospf_tilfa_topo1/rt3/ospfd.conf2
-rw-r--r--tests/topotests/ospf_tilfa_topo1/rt4/ospfd.conf2
-rw-r--r--tests/topotests/ospf_tilfa_topo1/rt5/ospfd.conf2
-rw-r--r--tests/topotests/pim_acl/r1/ospfd.conf4
-rw-r--r--tests/topotests/pim_autorp/test_pim_autorp.py97
-rw-r--r--tests/topotests/static_srv6_sids/expected_srv6_sids.json35
-rw-r--r--tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_1.json35
-rw-r--r--tests/topotests/static_srv6_sids/expected_srv6_sids_sid_delete_2.json35
-rw-r--r--tests/topotests/static_srv6_sids/r1/frr.conf1
-rw-r--r--tests/topotests/zebra_operational_data/r1/frr.conf41
-rw-r--r--tests/topotests/zebra_operational_data/test_zebra_operational.py64
-rw-r--r--yang/frr-gmp.yang8
-rw-r--r--yang/frr-pim.yang6
-rw-r--r--yang/frr-route-types.yang41
-rw-r--r--yang/frr-staticd.yang22
-rw-r--r--yang/frr-test-module.yang18
-rw-r--r--yang/frr-zebra.yang11
-rw-r--r--zebra/if_netlink.c3
-rw-r--r--zebra/interface.c7
-rw-r--r--zebra/interface.h6
-rw-r--r--zebra/rtadv.c8
-rw-r--r--zebra/zebra_cli.c28
-rw-r--r--zebra/zebra_dplane.c3
-rw-r--r--zebra/zebra_nb.c6
-rw-r--r--zebra/zebra_nb.h1
-rw-r--r--zebra/zebra_nb_state.c12
-rw-r--r--zebra/zebra_rib.c7
-rw-r--r--zebra/zebra_srv6.c46
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);
diff --git a/lib/if.h b/lib/if.h
index 1e52020b64..897f2be239 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -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