summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bgpd/bgp_mplsvpn.c28
-rw-r--r--bgpd/bgp_mplsvpn.h5
-rw-r--r--bgpd/bgp_route.c51
-rw-r--r--bgpd/bgp_route.h3
-rw-r--r--bgpd/rfapi/rfapi.c2
-rw-r--r--configure.ac53
-rw-r--r--doc/developer/building-doc.rst62
-rw-r--r--doc/developer/subdir.am1
-rw-r--r--doc/user/_static/overrides.js2
-rw-r--r--doc/user/about.rst26
-rw-r--r--lib/darr.h20
-rw-r--r--lib/frrscript.c2
-rw-r--r--lib/if.c128
-rw-r--r--lib/if.h10
-rw-r--r--lib/mgmt_be_client.c22
-rw-r--r--lib/mgmt_msg_native.c3
-rw-r--r--lib/mgmt_msg_native.h1
-rw-r--r--lib/northbound.c5
-rw-r--r--lib/northbound.h83
-rw-r--r--lib/northbound_notif.c680
-rw-r--r--lib/northbound_oper.c15
-rw-r--r--lib/subdir.am1
-rw-r--r--lib/vrf.c48
-rw-r--r--lib/vrf.h3
-rw-r--r--lib/yang.c111
-rw-r--r--lib/yang.h60
-rw-r--r--m4/ax_lua.m492
-rw-r--r--mgmtd/mgmt_be_adapter.c35
-rw-r--r--mgmtd/mgmt_fe_adapter.c235
-rw-r--r--mgmtd/mgmt_fe_adapter.h7
-rw-r--r--mgmtd/mgmt_history.c1
-rw-r--r--mgmtd/mgmt_txn.c47
-rw-r--r--mgmtd/mgmt_txn.h10
-rw-r--r--ospfd/ospf_asbr.c3
-rw-r--r--pimd/pim_bsm.c41
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py0
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf35
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf48
-rw-r--r--tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py142
-rwxr-xr-xtests/topotests/conftest.py2
-rw-r--r--tests/topotests/mgmt_notif/r1/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/r2/frr.conf2
-rw-r--r--tests/topotests/mgmt_notif/test_notif.py228
-rw-r--r--zebra/dplane_fpm_nl.c20
-rw-r--r--zebra/interface.c32
-rw-r--r--zebra/main.c3
-rw-r--r--zebra/rt_netlink.c460
-rw-r--r--zebra/rt_netlink.h13
-rw-r--r--zebra/zebra_dplane.c89
-rw-r--r--zebra/zebra_dplane.h26
-rw-r--r--zebra/zebra_rib.c36
-rw-r--r--zebra/zebra_vrf.c19
-rw-r--r--zebra/zebra_vrf.h3
53 files changed, 2674 insertions, 382 deletions
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c
index b96c287f8..46e529f03 100644
--- a/bgpd/bgp_mplsvpn.c
+++ b/bgpd/bgp_mplsvpn.c
@@ -1312,8 +1312,8 @@ leak_update(struct bgp *to_bgp, struct bgp_dest *bn,
else
bgp_path_info_unset_flag(bn, new, BGP_PATH_VALID);
- bgp_aggregate_increment(to_bgp, p, new, afi, safi);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(to_bgp, p, new, afi, safi);
bgp_process(to_bgp, bn, new, afi, safi);
@@ -1951,7 +1951,7 @@ void vpn_leak_from_vrf_update(struct bgp *to_bgp, /* to */
* because of loop checking.
*/
if (new_info)
- vpn_leak_to_vrf_update(from_bgp, new_info, NULL);
+ vpn_leak_to_vrf_update(from_bgp, new_info, NULL, path_vrf->peer);
else
bgp_dest_unlock_node(bn);
}
@@ -2143,10 +2143,10 @@ static struct bgp *bgp_lookup_by_rd(struct bgp_path_info *bpi,
return NULL;
}
-static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
+static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
struct bgp *from_bgp, /* from */
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd)
+ struct bgp_path_info *path_vpn, struct prefix_rd *prd,
+ struct peer *from)
{
const struct prefix *p = bgp_dest_get_prefix(path_vpn->net);
afi_t afi = family2afi(p->family);
@@ -2231,6 +2231,12 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */
/* Check if leaked route has our asn. If so, don't import it. */
if (CHECK_FLAG(peer->af_flags[afi][SAFI_MPLS_VPN], PEER_FLAG_ALLOWAS_IN))
aspath_loop_count = peer->allowas_in[afi][SAFI_MPLS_VPN];
+ else if (peer == peer->bgp->peer_self && from)
+ /* If this is an import from one VRF to another and the source
+ * VRF's peer has allowas-in applied, respect it.
+ */
+ aspath_loop_count = from->allowas_in[afi][SAFI_UNICAST];
+
if (aspath_loop_check(path_vpn->attr->aspath, to_bgp->as) > aspath_loop_count) {
for (bpi = bgp_dest_get_bgp_path_info(bn); bpi;
bpi = bpi->next) {
@@ -2511,9 +2517,8 @@ bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp,
return true;
}
-void vpn_leak_to_vrf_update(struct bgp *from_bgp,
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd)
+void vpn_leak_to_vrf_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer)
{
struct listnode *mnode, *mnnode;
struct bgp *bgp;
@@ -2528,8 +2533,7 @@ void vpn_leak_to_vrf_update(struct bgp *from_bgp,
for (ALL_LIST_ELEMENTS(bm->bgp, mnode, mnnode, bgp)) {
if (!path_vpn->extra || !path_vpn->extra->vrfleak ||
path_vpn->extra->vrfleak->bgp_orig != bgp) { /* no loop */
- vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn,
- prd);
+ vpn_leak_to_vrf_update_onevrf(bgp, from_bgp, path_vpn, prd, peer);
}
}
}
@@ -2728,8 +2732,8 @@ void vpn_leak_to_vrf_update_all(struct bgp *to_bgp, struct bgp *vpn_from,
bpi->extra->vrfleak->bgp_orig == to_bgp)
continue;
- vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from,
- bpi, NULL);
+ vpn_leak_to_vrf_update_onevrf(to_bgp, vpn_from, bpi, NULL,
+ bpi->peer);
}
}
}
diff --git a/bgpd/bgp_mplsvpn.h b/bgpd/bgp_mplsvpn.h
index 18639fc69..56dd33f9b 100644
--- a/bgpd/bgp_mplsvpn.h
+++ b/bgpd/bgp_mplsvpn.h
@@ -67,9 +67,8 @@ extern bool vpn_leak_to_vrf_no_retain_filter_check(struct bgp *from_bgp,
struct attr *attr,
afi_t afi);
-extern void vpn_leak_to_vrf_update(struct bgp *from_bgp,
- struct bgp_path_info *path_vpn,
- struct prefix_rd *prd);
+extern void vpn_leak_to_vrf_update(struct bgp *from_bgp, struct bgp_path_info *path_vpn,
+ struct prefix_rd *prd, struct peer *peer);
extern void vpn_leak_to_vrf_withdraw(struct bgp_path_info *path_vpn);
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index f520c2e2b..c02638a5b 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -5539,7 +5539,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, pi, prd);
+ vpn_leak_to_vrf_update(bgp, pi, prd, peer);
}
#ifdef ENABLE_BGP_VNC
@@ -5592,12 +5592,12 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
/* Addpath ID */
new->addpath_rx_id = addpath_id;
- /* Increment prefix */
- bgp_aggregate_increment(bgp, p, new, afi, safi);
-
/* Register new BGP information. */
bgp_path_info_add(dest, new);
+ /* Increment prefix */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -5633,7 +5633,7 @@ void bgp_update(struct peer *peer, const struct prefix *p, uint32_t addpath_id,
}
if ((SAFI_MPLS_VPN == safi)
&& (bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT)) {
- vpn_leak_to_vrf_update(bgp, new, prd);
+ vpn_leak_to_vrf_update(bgp, new, prd, peer);
}
#ifdef ENABLE_BGP_VNC
if (SAFI_MPLS_VPN == safi) {
@@ -7142,8 +7142,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, pi,
- &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, pi, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP ||
@@ -7187,12 +7186,12 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
bgp_nexthop_reachability_check(afi, safi, new, p, dest, bgp, bgp);
- /* Aggregate address increment. */
- bgp_aggregate_increment(bgp, p, new, afi, safi);
-
/* Register new BGP information. */
bgp_path_info_add(dest, new);
+ /* Aggregate address increment. */
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
+
/* route_node_get lock */
bgp_dest_unlock_node(dest);
@@ -7207,7 +7206,7 @@ void bgp_static_update(struct bgp *bgp, const struct prefix *p,
if (SAFI_MPLS_VPN == safi &&
bgp->inst_type == BGP_INSTANCE_TYPE_DEFAULT) {
- vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd);
+ vpn_leak_to_vrf_update(bgp, new, &bgp_static->prd, NULL);
}
#ifdef ENABLE_BGP_VNC
if (safi == SAFI_MPLS_VPN || safi == SAFI_ENCAP || safi == SAFI_EVPN)
@@ -8874,6 +8873,27 @@ static int bgp_aggregate_unset(struct vty *vty, const char *prefix_str,
return CMD_SUCCESS;
}
+static bool bgp_aggregate_cmp_params(struct bgp_aggregate *aggregate, const char *rmap,
+ uint8_t summary_only, uint8_t as_set, uint8_t origin,
+ bool match_med, const char *suppress_map)
+{
+ if ((aggregate->origin != origin) || (aggregate->as_set != as_set) ||
+ (aggregate->match_med != match_med) || (aggregate->summary_only != summary_only))
+ return false;
+
+ if ((!rmap && aggregate->rmap.name) || (rmap && !aggregate->rmap.name) ||
+ (rmap && aggregate->rmap.name && !strmatch(rmap, aggregate->rmap.name)))
+ return false;
+
+ if ((!suppress_map && aggregate->suppress_map_name) ||
+ (suppress_map && !aggregate->suppress_map_name) ||
+ (suppress_map && aggregate->suppress_map_name &&
+ !strmatch(suppress_map, aggregate->suppress_map_name)))
+ return false;
+
+ return true;
+}
+
static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
safi_t safi, const char *rmap,
uint8_t summary_only, uint8_t as_set,
@@ -8913,6 +8933,11 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
aggregate = bgp_dest_get_bgp_aggregate_info(dest);
if (aggregate) {
+ /* Check for duplicate configs */
+ if (bgp_aggregate_cmp_params(aggregate, rmap, summary_only, as_set, origin,
+ match_med, suppress_map))
+ return CMD_SUCCESS;
+
vty_out(vty, "There is already same aggregate network.\n");
/* try to remove the old entry */
ret = bgp_aggregate_unset(vty, prefix_str, afi, safi);
@@ -8948,7 +8973,7 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi,
}
aggregate->as_set = as_set_new;
- aggregate->safi = safi;
+
/* Override ORIGIN attribute if defined.
* E.g.: Cisco and Juniper set ORIGIN for aggregated address
* to IGP which is not what rfc4271 says.
@@ -9311,8 +9336,8 @@ void bgp_redistribute_add(struct bgp *bgp, struct prefix *p,
bgp->peer_self, new_attr, bn);
SET_FLAG(new->flags, BGP_PATH_VALID);
- bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(bgp, p, new, afi, SAFI_UNICAST);
bgp_dest_unlock_node(bn);
SET_FLAG(bn->flags, BGP_NODE_FIB_INSTALLED);
bgp_process(bgp, bn, new, afi, SAFI_UNICAST);
diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h
index 43033c8c3..7f4a3b918 100644
--- a/bgpd/bgp_route.h
+++ b/bgpd/bgp_route.h
@@ -492,9 +492,6 @@ struct bgp_aggregate {
/* Aggregate route's as-path. */
struct aspath *aspath;
- /* SAFI configuration. */
- safi_t safi;
-
/** MED value found in current group. */
uint32_t med_matched_value;
diff --git a/bgpd/rfapi/rfapi.c b/bgpd/rfapi/rfapi.c
index 61d154f1b..241cbcb35 100644
--- a/bgpd/rfapi/rfapi.c
+++ b/bgpd/rfapi/rfapi.c
@@ -1029,8 +1029,8 @@ void add_vnc_route(struct rfapi_descriptor *rfd, /* cookie, VPN UN addr, peer */
rfapiPrintBi(NULL, new);
}
- bgp_aggregate_increment(bgp, p, new, afi, safi);
bgp_path_info_add(bn, new);
+ bgp_aggregate_increment(bgp, p, new, afi, safi);
if (safi == SAFI_MPLS_VPN) {
struct bgp_dest *pdest = NULL;
diff --git a/configure.ac b/configure.ac
index e8036fcff..e04c0b6d4 100644
--- a/configure.ac
+++ b/configure.ac
@@ -375,26 +375,45 @@ fi
AM_CONDITIONAL([SCRIPTING], [test "$enable_scripting" = "yes"])
if test "$enable_scripting" = "yes"; then
- AX_PROG_LUA([5.3], [5.4], [], [
- AC_MSG_ERROR([Lua 5.3 is required to build with Lua support. No other version is supported.])
+ AX_PROG_LUA([5.3], [], [], [
+ AC_MSG_ERROR([Lua >= 5.3 is required to build with Lua support. No other version is supported.])
])
AX_LUA_HEADERS([], [
- AC_MSG_ERROR([Lua 5.3 headers are required to build with Lua support. No other version is supported.])
+ AC_MSG_ERROR([Lua >= 5.3 headers are required to build with Lua support. No other version is supported.])
])
- PKG_CHECK_MODULES([LUA], [lua5.3], [
- AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
- LIBS="$LIBS $LUA_LIBS"
- SCRIPTING=true
- ], [
- AX_LUA_LIBS([
- AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
- LIBS="$LIBS $LUA_LIB"
- SCRIPTING=true
- ], [
- SCRIPTING=false
- AC_MSG_ERROR([Lua 5.3 libraries are required to build with Lua support. No other version is supported.])
- ])
- ])
+
+ for version in 5.3 5.4; do
+ PKG_CHECK_MODULES([LUA], [lua >= $version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [
+ PKG_CHECK_MODULES([LUA], [lua-$version], [
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIBS"
+ SCRIPTING=true
+ break
+ ], [])
+ ])
+ ])
+ done
+
+ if [ "$SCRIPTING" != "true" ]; then
+ AX_LUA_LIBS([
+ AC_DEFINE([HAVE_SCRIPTING], [1], [Have support for scripting])
+ LIBS="$LIBS $LUA_LIB"
+ SCRIPTING=true
+ ], [
+ AC_MSG_ERROR([Lua >= 5.3 libraries are required to build with Lua support. No other version is supported.])
+ ])
+ fi
fi
dnl the following flags go in CFLAGS rather than AC_CFLAGS since they make
diff --git a/doc/developer/building-doc.rst b/doc/developer/building-doc.rst
new file mode 100644
index 000000000..bf0544ccc
--- /dev/null
+++ b/doc/developer/building-doc.rst
@@ -0,0 +1,62 @@
+Building Documentation
+======================
+
+To build FRR documentation, first install the dependencies.
+Notice that if you plan to only build html documenation, you only
+need the package ``python3-sphinx``.
+
+.. code-block:: console
+
+ sudo apt-get install -y python3-sphinx \
+ texlive-latex-base texlive-latex-extra latexmk
+
+To prepate for building both user and developer documentation, do:
+
+.. code-block:: console
+
+ cd doc
+ make
+
+User documentation
+------------------
+
+To build html user documentation:
+
+.. code-block:: console
+
+ cd user
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd user
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
+
+Developer documentation
+-----------------------
+
+To build the developer documentation:
+
+.. code-block:: console
+
+ cd developer
+ make html
+
+This will generate html documentation files under ``_build/html/``.
+With the main page named ``index.html``.
+
+PFD can then be built by:
+
+.. code-block:: console
+
+ cd developer
+ make pdf
+
+The generated PDF file will be saved at ``_build/latex/FRR.pdf``
diff --git a/doc/developer/subdir.am b/doc/developer/subdir.am
index 652ee4e1a..bdf93a05c 100644
--- a/doc/developer/subdir.am
+++ b/doc/developer/subdir.am
@@ -28,6 +28,7 @@ dev_RSTFILES = \
doc/developer/building-frr-for-ubuntu1804.rst \
doc/developer/building-frr-for-ubuntu2004.rst \
doc/developer/building-frr-for-ubuntu2204.rst \
+ doc/developer/building-doc.rst \
doc/developer/building-libunwind-note.rst \
doc/developer/building-libyang.rst \
doc/developer/building.rst \
diff --git a/doc/user/_static/overrides.js b/doc/user/_static/overrides.js
index 73bf6123b..f6af539bf 100644
--- a/doc/user/_static/overrides.js
+++ b/doc/user/_static/overrides.js
@@ -5,7 +5,7 @@
*/
$(document).ready(function() {
$("span.mark:contains('Y')" ).addClass("mark-y" ).parent("td").addClass("mark");
- $("span.mark:contains('≥')" ).addClass("mark-geq").parent("td").addClass("mark");
+ $("span.mark:contains('>=')").addClass("mark-geq").parent("td").addClass("mark");
$("span.mark:contains('N')" ).addClass("mark-n" ).parent("td").addClass("mark");
$("span.mark:contains('CP')").addClass("mark-cp" ).parent("td").addClass("mark");
$("span.mark:contains('†')" ).addClass("mark-dag").parent("td").addClass("mark");
diff --git a/doc/user/about.rst b/doc/user/about.rst
index e16ed7e3c..d9470f5f3 100644
--- a/doc/user/about.rst
+++ b/doc/user/about.rst
@@ -153,7 +153,7 @@ feature you're interested in, it should be supported on your platform.
.. comment - the :mark:`X` pieces mesh with a little bit of JavaScript and
CSS in _static/overrides.{js,css} respectively. The JS code looks at the
- presence of the 'Y' 'N' '≥' '†' or 'CP' strings. This seemed to be the
+ presence of the 'Y' 'N' '>=' '†' or 'CP' strings. This seemed to be the
best / least intrusive way of getting a nice table in HTML. The table
will look somewhat shoddy on other sphinx targets like PDF or info (but
should still be readable.)
@@ -165,9 +165,9 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `zebra` | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF | :mark:`≥4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+| VRF | :mark:`>=4.8` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| MPLS | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+| MPLS | :mark:`>=4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `pbrd` (Policy Routing) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -175,21 +175,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `bgpd` (BGP) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VRF / L3VPN | :mark:`≥4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| VRF / L3VPN | :mark:`>=4.8` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
| | :mark:`†4.3` | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| EVPN | :mark:`≥4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+| EVPN | :mark:`>=4.18` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
| | :mark:`†4.9` | | | |
+-----------------------------------+----------------+--------------+------------+------------+
| VNC (Virtual Network Control) | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+-----------------------------------+----------------+--------------+------------+------------+
| Flowspec | :mark:`CP` | :mark:`CP` | :mark:`CP` | :mark:`CP` |
+-----------------------------------+----------------+--------------+------------+------------+
-| `ldpd` (LDP) | :mark:`≥4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+| `ldpd` (LDP) | :mark:`>=4.5` | :mark:`Y` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VPWS / PW | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` |
+| VPWS / PW | :mark:`N` | :mark:`>=5.8`| :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| VPLS | :mark:`N` | :mark:`≥5.8` | :mark:`N` | :mark:`N` |
+| VPLS | :mark:`N` | :mark:`>=5.8`| :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `nhrpd` (NHRP) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -197,7 +197,7 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| `ospfd` (OSPFv2) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
-| Segment Routing | :mark:`≥4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+| Segment Routing | :mark:`>=4.12` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
| `ospf6d` (OSPFv3) | :mark:`Y` | :mark:`Y` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
@@ -215,21 +215,21 @@ feature you're interested in, it should be supported on your platform.
+-----------------------------------+----------------+--------------+------------+------------+
| **Multicast Routing** | | | | |
+-----------------------------------+----------------+--------------+------------+------------+
-| `pimd` (PIM) | :mark:`≥4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+| `pimd` (PIM) | :mark:`>=4.19` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
| SSM (Source Specific) | :mark:`Y` | :mark:`N` | :mark:`Y` | :mark:`Y` |
+-----------------------------------+----------------+--------------+------------+------------+
| ASM (Any Source) | :mark:`Y` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| EVPN BUM Forwarding | :mark:`≥5.0` | :mark:`N` | :mark:`N` | :mark:`N` |
+| EVPN BUM Forwarding | :mark:`>=5.0` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
-| `vrrpd` (VRRP) | :mark:`≥5.1` | :mark:`N` | :mark:`N` | :mark:`N` |
+| `vrrpd` (VRRP) | :mark:`>=5.1` | :mark:`N` | :mark:`N` | :mark:`N` |
+-----------------------------------+----------------+--------------+------------+------------+
The indicators have the following semantics:
* :mark:`Y` - daemon/feature fully functional
-* :mark:`≥X.X` - fully functional with kernel version X.X or newer
+* :mark:`>=X.X` - fully functional with kernel version X.X or newer
* :mark:`†X.X` - restricted functionality or impaired performance with kernel version X.X or newer
* :mark:`CP` - control plane only (i.e. BGP route server / route reflector)
* :mark:`N` - daemon/feature not supported by operating system
diff --git a/lib/darr.h b/lib/darr.h
index 121e3dd14..084c2a103 100644
--- a/lib/darr.h
+++ b/lib/darr.h
@@ -571,16 +571,16 @@ void *__darr_resize(void *a, uint count, size_t esize, struct memtype *mt);
* Return:
* The dynamic_array D with the new string content.
*/
-#define darr_in_strcat(D, S) \
- ({ \
- uint __dlen = darr_strlen(D); \
- uint __slen = strlen(S); \
- darr_ensure_cap_mt(D, __dlen + __slen + 1, MTYPE_DARR_STR); \
- if (darr_len(D) == 0) \
- *darr_append(D) = 0; \
- memcpy(darr_last(D), (S), __slen + 1); \
- _darr_len(D) += __slen; \
- D; \
+#define darr_in_strcat(D, S) \
+ ({ \
+ uint __dlen = darr_strlen(D); \
+ uint __slen = strlen(S); \
+ darr_ensure_cap_mt(D, __dlen + __slen + 1, MTYPE_DARR_STR); \
+ if (darr_len(D) == 0) \
+ *darr_append(D) = 0; \
+ memcpy(&(D)[darr_strlen(D)] /* darr_last(D) clangSA :( */, (S), __slen + 1); \
+ _darr_len(D) += __slen; \
+ D; \
})
/**
diff --git a/lib/frrscript.c b/lib/frrscript.c
index 06460b014..8b068ba61 100644
--- a/lib/frrscript.c
+++ b/lib/frrscript.c
@@ -248,10 +248,12 @@ int _frrscript_call_lua(struct lua_function_state *lfs, int nargs)
zlog_err("Lua hook call '%s' : error handler error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#if defined(LUA_VERSION_NUM) && LUA_VERSION_NUM <= 503
case LUA_ERRGCMM:
zlog_err("Lua hook call '%s' : garbage collector error: %s",
lfs->name, lua_tostring(lfs->L, -1));
break;
+#endif
default:
zlog_err("Lua hook call '%s' : unknown error: %s", lfs->name,
lua_tostring(lfs->L, -1));
diff --git a/lib/if.c b/lib/if.c
index 864c82bbf..796929ef0 100644
--- a/lib/if.c
+++ b/lib/if.c
@@ -29,6 +29,10 @@
#include "admin_group.h"
#include "lib/if_clippy.c"
+
+/* Set by the owner (zebra). */
+bool if_notify_oper_changes;
+
DEFINE_MTYPE_STATIC(LIB, IF, "Interface");
DEFINE_MTYPE_STATIC(LIB, IFDESC, "Intf Desc");
DEFINE_MTYPE_STATIC(LIB, CONNECTED, "Connected");
@@ -208,6 +212,104 @@ void if_down_via_zapi(struct interface *ifp)
hook_call(if_down, ifp);
}
+void if_update_state_metric(struct interface *ifp, uint32_t metric)
+{
+ if (ifp->metric == metric)
+ return;
+ ifp->metric = metric;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "metric", "%u", ifp->metric);
+}
+
+void if_update_state_mtu(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu == mtu)
+ return;
+ ifp->mtu = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu", "%u", ifp->mtu);
+}
+
+void if_update_state_mtu6(struct interface *ifp, uint mtu)
+{
+ if (ifp->mtu6 == mtu)
+ return;
+ ifp->mtu6 = mtu;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "mtu6", "%u", ifp->mtu);
+}
+
+void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len)
+{
+ if (len == (uint)ifp->hw_addr_len && (len == 0 || !memcmp(hw_addr, ifp->hw_addr, len)))
+ return;
+ memcpy(ifp->hw_addr, hw_addr, len);
+ ifp->hw_addr_len = len;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+void if_update_state_speed(struct interface *ifp, uint32_t speed)
+{
+ if (ifp->speed == speed)
+ return;
+ ifp->speed = speed;
+ if (ifp->state && if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "speed", "%u", ifp->speed);
+}
+
+void if_update_state(struct interface *ifp)
+{
+ struct lyd_node *state = ifp->state;
+
+ if (!state || !if_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 6 separate REPLACE messages though.
+ */
+ // nb_op_update(state, ".", NULL);
+ nb_op_updatef(state, "if-index", "%d", ifp->ifindex);
+ nb_op_updatef(state, "mtu", "%u", ifp->mtu);
+ nb_op_updatef(state, "mtu6", "%u", ifp->mtu);
+ nb_op_updatef(state, "speed", "%u", ifp->speed);
+ nb_op_updatef(state, "metric", "%u", ifp->metric);
+ nb_op_updatef(state, "phy-address", "%pEA", (struct ethaddr *)ifp->hw_addr);
+}
+
+static void if_update_state_remove(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ ifp->vrf->name, ifp->name);
+ else
+ nb_op_update_delete_pathf(NULL, "/frr-interface:lib/interface[name=\"%s\"]/state",
+ ifp->name);
+ if (ifp->state) {
+ lyd_free_all(ifp->state);
+ ifp->state = NULL;
+ }
+}
+
+static void if_update_state_add(struct interface *ifp)
+{
+ if (!if_notify_oper_changes || ifp->name[0] == 0)
+ return;
+
+ if (vrf_is_backend_netns())
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s:%s\"]/state",
+ NULL, ifp->vrf->name, ifp->name);
+ else
+ ifp->state = nb_op_update_pathf(NULL,
+ "/frr-interface:lib/interface[name=\"%s\"]/state",
+ NULL, ifp->name);
+}
+
static struct interface *if_create_name(const char *name, struct vrf *vrf)
{
struct interface *ifp;
@@ -216,7 +318,11 @@ static struct interface *if_create_name(const char *name, struct vrf *vrf)
if_set_name(ifp, name);
+ if (if_notify_oper_changes && ifp->state)
+ if_update_state(ifp);
+
hook_call(if_add, ifp);
+
return ifp;
}
@@ -228,8 +334,10 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
/* remove interface from old master vrf list */
old_vrf = ifp->vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(old_vrf, ifp);
+ if_update_state_remove(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_REMOVE(old_vrf, ifp);
@@ -237,8 +345,11 @@ void if_update_to_new_vrf(struct interface *ifp, vrf_id_t vrf_id)
vrf = vrf_get(vrf_id, NULL);
ifp->vrf = vrf;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(vrf, ifp);
+ if_update_state_add(ifp);
+ if_update_state(ifp);
+ }
if (ifp->ifindex != IFINDEX_INTERNAL)
IFINDEX_RB_INSERT(vrf, ifp);
@@ -280,6 +391,8 @@ void if_delete(struct interface **ifp)
XFREE(MTYPE_IFDESC, ptr->desc);
+ if_update_state_remove(ptr);
+
XFREE(MTYPE_IF, ptr);
*ifp = NULL;
}
@@ -630,6 +743,9 @@ int if_set_index(struct interface *ifp, ifindex_t ifindex)
ifp->ifindex = ifindex;
+ if (if_notify_oper_changes)
+ nb_op_updatef(ifp->state, "if-index", "%d", ifp->ifindex);
+
if (ifp->ifindex != IFINDEX_INTERNAL) {
/*
* This should never happen, since we checked if there was
@@ -648,13 +764,17 @@ static void if_set_name(struct interface *ifp, const char *name)
if (if_cmp_name_func(ifp->name, name) == 0)
return;
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_REMOVE(ifp->vrf, ifp);
+ if_update_state_remove(ifp);
+ }
strlcpy(ifp->name, name, sizeof(ifp->name));
- if (ifp->name[0] != '\0')
+ if (ifp->name[0] != '\0') {
IFNAME_RB_INSERT(ifp->vrf, ifp);
+ if_update_state_add(ifp);
+ }
}
/* Does interface up ? */
diff --git a/lib/if.h b/lib/if.h
index c2ec73378..1e52020b6 100644
--- a/lib/if.h
+++ b/lib/if.h
@@ -297,6 +297,8 @@ struct interface {
struct vrf *vrf;
+ struct lyd_node *state;
+
/*
* Has the end users entered `interface XXXX` from the cli in some
* fashion?
@@ -633,6 +635,14 @@ extern void if_up_via_zapi(struct interface *ifp);
extern void if_down_via_zapi(struct interface *ifp);
extern void if_destroy_via_zapi(struct interface *ifp);
+extern void if_update_state(struct interface *ifp);
+extern void if_update_state_metric(struct interface *ifp, uint32_t metric);
+extern void if_update_state_mtu(struct interface *ifp, uint mtu);
+extern void if_update_state_mtu6(struct interface *ifp, uint mtu);
+extern void if_update_state_hw_addr(struct interface *ifp, const uint8_t *hw_addr, uint len);
+extern void if_update_state_speed(struct interface *ifp, uint32_t speed);
+
+extern bool if_notify_oper_changes;
extern const struct frr_yang_module_info frr_interface_info;
extern const struct frr_yang_module_info frr_interface_cli_info;
diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c
index 155d56aa6..efd5d70a7 100644
--- a/lib/mgmt_be_client.c
+++ b/lib/mgmt_be_client.c
@@ -322,8 +322,7 @@ static int __send_notification(struct mgmt_be_client *client, const char *xpath,
LY_ERR err;
int ret = 0;
- assert(tree);
-
+ assert(op != NOTIFY_OP_NOTIFICATION || xpath || tree);
debug_be_client("%s: sending %sYANG %snotification: %s", __func__,
op == NOTIFY_OP_DS_DELETE ? "delete "
: op == NOTIFY_OP_DS_REPLACE ? "replace "
@@ -1155,6 +1154,22 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf,
}
/*
+ * Process a notify select msg
+ */
+static void be_client_handle_notify_select(struct mgmt_be_client *client, void *msgbuf,
+ size_t msg_len)
+{
+ struct mgmt_msg_notify_select *msg = msgbuf;
+ const char **selectors = NULL;
+
+ debug_be_client("Received notify-select for client %s", client->name);
+
+ if (msg_len >= sizeof(*msg))
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
+ nb_notif_set_filters(selectors, msg->replace);
+}
+
+/*
* Handle a native encoded message
*
* We don't create transactions with native messaging.
@@ -1175,6 +1190,9 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client,
case MGMT_MSG_CODE_NOTIFY:
be_client_handle_notify(client, msg, msg_len);
break;
+ case MGMT_MSG_CODE_NOTIFY_SELECT:
+ be_client_handle_notify_select(client, msg, msg_len);
+ break;
default:
log_err_be_client("unknown native message txn-id %" PRIu64
" req-id %" PRIu64 " code %u to client %s",
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index b85c7d1b6..46dfe7f2e 100644
--- a/lib/mgmt_msg_native.c
+++ b/lib/mgmt_msg_native.c
@@ -14,7 +14,8 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_ERROR, "native error msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg");
-DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native notify msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY_SELECT, "native notify select msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg");
DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_RPC, "native RPC msg");
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index 4076977a2..73303846e 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -159,6 +159,7 @@ DECLARE_MTYPE(MSG_NATIVE_GET_TREE);
DECLARE_MTYPE(MSG_NATIVE_TREE_DATA);
DECLARE_MTYPE(MSG_NATIVE_GET_DATA);
DECLARE_MTYPE(MSG_NATIVE_NOTIFY);
+DECLARE_MTYPE(MSG_NATIVE_NOTIFY_SELECT);
DECLARE_MTYPE(MSG_NATIVE_EDIT);
DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY);
DECLARE_MTYPE(MSG_NATIVE_RPC);
diff --git a/lib/northbound.c b/lib/northbound.c
index c67ed924a..418cb246f 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -2754,10 +2754,15 @@ void nb_init(struct event_loop *tm,
/* Initialize oper-state */
nb_oper_init(tm);
+
+ /* Initialize notification-state */
+ nb_notif_init(tm);
}
void nb_terminate(void)
{
+ nb_notif_terminate();
+
nb_oper_terminate();
/* Terminate the northbound CLI. */
diff --git a/lib/northbound.h b/lib/northbound.h
index 38d8c2bdc..bd802e088 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -1512,6 +1512,15 @@ extern void nb_oper_cancel_walk(void *walk);
*/
extern void nb_oper_cancel_all_walks(void);
+/**
+ * nb_oper_walk_finish_arg() - return the finish arg for this walk
+ */
+extern void *nb_oper_walk_finish_arg(void *walk);
+/**
+ * nb_oper_walk_cb_arg() - return the callback arg for this walk
+ */
+extern void *nb_oper_walk_cb_arg(void *walk);
+
/*
* Validate if the northbound callback operation is valid for the given node.
*
@@ -1744,6 +1753,80 @@ extern void nb_oper_init(struct event_loop *loop);
extern void nb_oper_terminate(void);
extern bool nb_oper_is_yang_lib_query(const char *xpath);
+
+/**
+ * nb_op_update() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * nb_op_update_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void nb_op_update_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * nb_op_update_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * nb_op_update_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+ PRINTFRR(2, 3);
+extern void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * nb_op_updatef() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap);
+
+/**
+ * nb_notif_set_filters() - add or replace notification filters
+ * @selectors: darr array of selector (filter) xpath strings, can be NULL if
+ * @replace is true. nb_notif_set_filters takes ownership of this
+ * array and the contained darr strings.
+ * @replace: true to replace existing set otherwise append.
+ */
+extern void nb_notif_set_filters(const char **selectors, bool replace);
+
+extern void nb_notif_init(struct event_loop *loop);
+extern void nb_notif_terminate(void);
+
#ifdef __cplusplus
}
#endif
diff --git a/lib/northbound_notif.c b/lib/northbound_notif.c
new file mode 100644
index 000000000..b75c86561
--- /dev/null
+++ b/lib/northbound_notif.c
@@ -0,0 +1,680 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * December 1 2024, Christian Hopps <chopps@labn.net>
+ *
+ * Copyright (c) 2024, LabN Consulting, L.L.C.
+ *
+ */
+#include <zebra.h>
+#include "debug.h"
+#include "lib_errors.h"
+#include "typesafe.h"
+#include "northbound.h"
+#include "mgmt_be_client.h"
+
+#define __dbg(fmt, ...) DEBUGD(&nb_dbg_notif, "NB_OP_CHANGE: %s: " fmt, __func__, ##__VA_ARGS__)
+#define __log_err(fmt, ...) zlog_err("NB_OP_CHANGE: %s: ERROR: " fmt, __func__, ##__VA_ARGS__)
+
+#define NB_NOTIF_TIMER_MSEC (10) /* 10msec */
+
+/*
+ * ADDS:
+ * - Less specific:
+ * - Any new add will cause more specific pending adds to be dropped and equal
+ * or more specific deletes to be dropped.
+ * - More specific:
+ * - Ignore any new add that is the same or more specific than an existing add.
+ * - A new add that is more specific than a delete should change the delete
+ * into an add query (since adds are reported as a replace).
+ *
+ * DELETES:
+ * - Less specific:
+ * - Any new delete will cause more specific pending deletes to be dropped and
+ * equal or more specific adds to be dropped.
+ * - More specific:
+ * - Ignore new deletes that are the same or more specific than existing
+ * deletes.
+ * - A new delete that is more specific than an add can be dropped since we
+ * use replacement methodology for the add.
+ *
+ * One thing we have to pay close attention to is that the state is going to be
+ * queried when the notification sent, not when we are told of the change.
+ */
+
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGE, "NB Oper Change");
+DEFINE_MTYPE_STATIC(LIB, OP_CHANGES_GROUP, "NB Oper Changes Group");
+DEFINE_MTYPE_STATIC(LIB, NB_NOTIF_WALK_ARGS, "NB Notify Oper Walk");
+
+struct op_change {
+ RB_ENTRY(op_change) link;
+ char path[];
+};
+
+/*
+ * RB tree for op_change
+ */
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2);
+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)
+
+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;
+struct event *nb_notif_timer;
+void *nb_notif_walk;
+
+const char **nb_notif_filters;
+
+/*
+ * We maintain a queue of change lists one entry per query and notification send
+ * action
+ */
+PREDECL_LIST(op_changes_queue);
+struct op_changes_group {
+ struct op_changes_queue_item item;
+ struct op_changes adds;
+ struct op_changes dels;
+ struct op_changes *cur_changes; /* used when walking */
+ struct op_change *cur_change; /* " " " */
+};
+
+DECLARE_LIST(op_changes_queue, struct op_changes_group, item);
+static struct op_changes_queue_head op_changes_queue;
+
+struct nb_notif_walk_args {
+ struct op_changes_group *group;
+ struct lyd_node *tree;
+};
+
+static void nb_notif_set_walk_timer(void);
+
+
+static int pathncmp(const char *s1, const char *s2, size_t n)
+{
+ size_t i = 0;
+
+ while (i < n && *s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ i++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ i++;
+ }
+ if (i < n)
+ return (unsigned char)*s1 - (unsigned char)*s2;
+ return 0;
+}
+
+static int pathcmp(const char *s1, const char *s2)
+{
+ while (*s1 && *s2) {
+ char c1 = *s1;
+ char c2 = *s2;
+
+ if ((c1 == '\'' && c2 == '\"') || (c1 == '\"' && c2 == '\'')) {
+ s1++;
+ s2++;
+ continue;
+ }
+ if (c1 != c2)
+ return (unsigned char)c1 - (unsigned char)c2;
+ s1++;
+ s2++;
+ }
+ return (unsigned char)*s1 - (unsigned char)*s2;
+}
+
+
+static int op_change_cmp(const struct op_change *e1, const struct op_change *e2)
+{
+ return pathcmp(e1->path, e2->path);
+}
+
+static struct op_change *op_change_alloc(const char *path)
+{
+ struct op_change *note;
+ size_t ssize = strlen(path) + 1;
+
+ note = XMALLOC(MTYPE_OP_CHANGE, sizeof(*note) + ssize);
+ memset(note, 0, sizeof(*note));
+ strlcpy(note->path, path, ssize);
+
+ return note;
+}
+
+static void op_change_free(struct op_change *note)
+{
+ XFREE(MTYPE_OP_CHANGE, note);
+}
+
+/**
+ * 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.
+ */
+static void op_changes_group_push(void)
+{
+ struct op_changes_group *changes;
+
+ if (RB_EMPTY(op_changes, &nb_notif_adds) && RB_EMPTY(op_changes, &nb_notif_dels))
+ return;
+
+ __dbg("pushing current oper changes onto queue");
+
+ changes = XCALLOC(MTYPE_OP_CHANGES_GROUP, sizeof(*changes));
+ changes->adds = nb_notif_adds;
+ changes->dels = nb_notif_dels;
+ op_changes_queue_add_tail(&op_changes_queue, changes);
+
+ RB_INIT(op_changes, &nb_notif_adds);
+ RB_INIT(op_changes, &nb_notif_dels);
+}
+
+static void op_changes_group_free(struct op_changes_group *group)
+{
+ struct op_change *e, *next;
+
+ RB_FOREACH_SAFE (e, op_changes, &group->adds, next) {
+ RB_REMOVE(op_changes, &group->adds, e);
+ op_change_free(e);
+ }
+ RB_FOREACH_SAFE (e, op_changes, &group->dels, next) {
+ RB_REMOVE(op_changes, &group->dels, e);
+ op_change_free(e);
+ }
+ XFREE(MTYPE_OP_CHANGES_GROUP, group);
+}
+
+static struct op_change *__find_less_specific(struct op_changes *head, struct op_change *note)
+{
+ struct op_change *e;
+ size_t plen;
+
+ /*
+ * RB_NFIND finds equal or greater (more specific) than the key,
+ * so the previous node will be a less specific or no match that
+ * sorts earlier. We want to find when we are a more specific
+ * match.
+ */
+ e = RB_NFIND(op_changes, head, note);
+ if (e)
+ e = RB_PREV(op_changes, e);
+ else
+ e = RB_MAX(op_changes, head);
+ if (!e)
+ return NULL;
+ plen = strlen(e->path);
+ if (pathncmp(e->path, note->path, plen))
+ return NULL;
+ /* equal would have been returned from RB_NFIND() then we went RB_PREV */
+ assert(strlen(note->path) != plen);
+ return e;
+}
+
+static void __drop_eq_or_more_specific(struct op_changes *head, const char *path, int plen,
+ struct op_change *next)
+{
+ struct op_change *e;
+
+ for (e = next; e != NULL; e = next) {
+ /* if the prefix no longer matches we are done */
+ if (pathncmp(path, e->path, plen))
+ break;
+ __dbg("dropping more specific %s: %s", head == &nb_notif_adds ? "add" : "delete",
+ e->path);
+ next = RB_NEXT(op_changes, e);
+ RB_REMOVE(op_changes, head, e);
+ op_change_free(e);
+ }
+}
+
+static void __op_change_add_del(const char *path, struct op_changes *this_head,
+ struct op_changes *other_head)
+{
+ /* find out if this has been subsumed or will subsume */
+
+ const char *op = this_head == &nb_notif_adds ? "add" : "delete";
+ struct op_change *note = op_change_alloc(path);
+ struct op_change *next, *e;
+ int plen;
+
+ __dbg("processing oper %s change path: %s", op, path);
+
+ /*
+ * See if we are already covered by a more general `op`.
+ */
+ e = __find_less_specific(this_head, note);
+ if (e) {
+ __dbg("%s path already covered by: %s", op, e->path);
+ op_change_free(note);
+ return;
+ }
+
+ /*
+ * Handle having a less-specific `other op`.
+ */
+ e = __find_less_specific(other_head, note);
+ if (e) {
+ if (this_head == &nb_notif_dels) {
+ /*
+ * If we have a less-specific add then drop this
+ * more-specific delete as the add-replace will remove
+ * this missing state.
+ */
+ __dbg("delete path already covered add-replace: %s", e->path);
+ } else {
+ /*
+ * If we have a less-specific delete, convert the delete
+ * to an add, and drop this more-specific add. The new
+ * less-specific add will pick up the more specific add
+ * during the walk and as adds are processed as replaces
+ * any other existing state that was to be deleted will
+ * still be deleted (unless it also returns) by the replace.
+ */
+ __dbg("add covered, converting covering delete to add-replace: %s", e->path);
+ RB_REMOVE(op_changes, other_head, e);
+ __op_change_add_del(e->path, &nb_notif_adds, &nb_notif_dels);
+ op_change_free(e);
+ }
+ op_change_free(note);
+ return;
+ }
+
+ e = RB_INSERT(op_changes, this_head, note);
+ if (e) {
+ __dbg("path already in %s tree: %s", op, path);
+ op_change_free(note);
+ return;
+ }
+
+ __dbg("scanning for subsumed or subsuming: %s", path);
+
+ plen = strlen(path);
+
+ next = RB_NEXT(op_changes, note);
+ __drop_eq_or_more_specific(this_head, path, plen, next);
+
+ /* Drop exact match or more specific `other op` */
+ next = RB_NFIND(op_changes, other_head, note);
+ __drop_eq_or_more_specific(other_head, path, plen, next);
+
+ nb_notif_set_walk_timer();
+}
+
+static void nb_notif_add(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_adds, &nb_notif_dels);
+}
+
+
+static void nb_notif_delete(const char *path)
+{
+ __op_change_add_del(path, &nb_notif_dels, &nb_notif_adds);
+}
+
+struct lyd_node *nb_op_update(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode;
+ const char *abs_path = NULL;
+
+ __dbg("updating path: %s with value: %s", path, value);
+
+ dnode = yang_state_new(tree, path, value);
+
+ if (path[0] == '/')
+ abs_path = path;
+ else
+ abs_path = lyd_path(dnode, LYD_PATH_STD, NULL, 0);
+
+ nb_notif_add(abs_path);
+
+ if (abs_path != path)
+ free((char *)abs_path);
+
+ return dnode;
+}
+
+void nb_op_update_delete(struct lyd_node *tree, const char *path)
+{
+ char *abs_path = NULL;
+
+ __dbg("deleting path: %s", path);
+
+ if (path && path[0] == '/')
+ abs_path = (char *)path;
+ else {
+ assert(tree);
+ abs_path = lyd_path(tree, LYD_PATH_STD, NULL, 0);
+ assert(abs_path);
+ if (path) {
+ char *tmp = darr_strdup(abs_path);
+
+ free(abs_path);
+ abs_path = tmp;
+ if (*darr_last(abs_path) != '/')
+ darr_in_strcat(abs_path, "/");
+ assert(abs_path); /* silence bad CLANG NULL warning */
+ darr_in_strcat(abs_path, path);
+ }
+ }
+
+ yang_state_delete(tree, path);
+
+ nb_notif_delete(abs_path);
+
+ if (abs_path != path) {
+ if (path)
+ darr_free(abs_path);
+ else
+ free(abs_path);
+ }
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *nb_op_update_vpathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *nb_op_update_pathf(struct lyd_node *tree, const char *path_fmt, const char *value,
+ ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = nb_op_update_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void nb_op_update_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ nb_op_update_delete(tree, path);
+ darr_free(path);
+}
+
+void nb_op_update_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ nb_op_update_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+
+PRINTFRR(3, 0)
+struct lyd_node *nb_op_vupdatef(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = nb_op_update(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+
+struct lyd_node *nb_op_updatef(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = nb_op_vupdatef(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+static struct op_changes_group *op_changes_group_next(void)
+{
+ struct op_changes_group *group;
+
+ group = op_changes_queue_pop(&op_changes_queue);
+ if (!group) {
+ op_changes_group_push();
+ group = op_changes_queue_pop(&op_changes_queue);
+ }
+ if (!group)
+ return NULL;
+ group->cur_changes = &group->dels;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (!group->cur_change) {
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ assert(group->cur_change);
+ }
+ return group;
+}
+
+/* ---------------------------- */
+/* Query for changes and notify */
+/* ---------------------------- */
+
+static void timer_walk_continue(struct event *event);
+
+static enum nb_error oper_walk_done(const struct lyd_node *tree, void *arg, enum nb_error ret)
+{
+ struct nb_notif_walk_args *args = arg;
+ struct op_changes_group *group = args->group;
+ const char *path = group->cur_change->path;
+ const char *op = group->cur_changes == &group->adds ? "add" : "delete";
+
+ /* we don't send batches when yielding as we need completed edit in any patch */
+ assert(ret != NB_YIELD);
+
+ nb_notif_walk = NULL;
+
+ if (ret == NB_ERR_NOT_FOUND) {
+ __dbg("Path not found while walking oper tree: %s", path);
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ return ret;
+ }
+ /* Something else went wrong with the walk */
+ if (ret != NB_OK) {
+error:
+ __log_err("Error notifying for datastore change on path: %s: %s", path,
+ nb_err_name(ret));
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ /* XXX Need to inform mgmtd/front-ends things are out-of-sync */
+ return ret;
+ }
+
+ __dbg("done with oper-path collection for %s path: %s", op, path);
+
+ /* Do we need this? */
+ while (tree->parent)
+ tree = lyd_parent(tree);
+
+ /* Send the add (replace) notification */
+ if (mgmt_be_send_ds_replace_notification(path, tree)) {
+ ret = NB_ERR;
+ goto error;
+ }
+
+ /*
+ * Advance to next change (either dels or adds or both).
+ */
+
+ group->cur_change = RB_NEXT(op_changes, group->cur_change);
+ if (!group->cur_change) {
+ __dbg("done with oper-path collection for group");
+ op_changes_group_free(group);
+
+ group = op_changes_group_next();
+ args->group = group;
+ if (!group) {
+ __dbg("done with ALL oper-path collection for notification");
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ goto done;
+ }
+ }
+
+ event_add_timer_msec(nb_notif_master, timer_walk_continue, args, 0, &nb_notif_timer);
+done:
+ /* Done with current walk and scheduled next one if there is more */
+ nb_notif_walk = NULL;
+
+ return NB_OK;
+}
+
+static LY_ERR nb_notify_delete_changes(struct nb_notif_walk_args *args)
+{
+ struct op_changes_group *group = args->group;
+ LY_ERR err;
+
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ while (group->cur_change) {
+ err = mgmt_be_send_ds_delete_notification(group->cur_change->path);
+ assert(err == LY_SUCCESS); /* XXX */
+
+ group->cur_change = RB_NEXT(op_changes, group->cur_change);
+ }
+
+ return LY_SUCCESS;
+}
+
+static void timer_walk_continue(struct event *event)
+{
+ struct nb_notif_walk_args *args = EVENT_ARG(event);
+ struct op_changes_group *group = args->group;
+ const char *path;
+ LY_ERR err;
+
+ /*
+ * Notify about deletes until we have add changes to collect.
+ */
+ while (group->cur_changes == &group->dels) {
+ err = nb_notify_delete_changes(args);
+ assert(err == LY_SUCCESS); /* XXX */
+ assert(!group->cur_change); /* we send all the deletes in one message */
+
+ /* after deletes advance to adds */
+ group->cur_changes = &group->adds;
+ group->cur_change = RB_MIN(op_changes, group->cur_changes);
+ if (group->cur_change)
+ break;
+
+ __dbg("done with oper-path change group");
+ op_changes_group_free(group);
+
+ group = op_changes_group_next();
+ args->group = group;
+ if (!group) {
+ __dbg("done with ALL oper-path changes");
+ XFREE(MTYPE_NB_NOTIF_WALK_ARGS, args);
+ return;
+ }
+ }
+
+ path = group->cur_change->path;
+ __dbg("starting next oper-path replace walk for path: %s", path);
+ nb_notif_walk = nb_oper_walk(path, NULL, 0, false, NULL, NULL, oper_walk_done, args);
+}
+
+static void timer_walk_start(struct event *event)
+{
+ struct op_changes_group *group;
+ struct nb_notif_walk_args *args;
+
+ __dbg("oper-state change notification timer fires");
+
+ group = op_changes_group_next();
+ if (!group) {
+ __dbg("no oper changes to notify");
+ return;
+ }
+
+ args = XCALLOC(MTYPE_NB_NOTIF_WALK_ARGS, sizeof(*args));
+ args->group = group;
+
+ EVENT_ARG(event) = args;
+ timer_walk_continue(event);
+}
+
+static void nb_notif_set_walk_timer(void)
+{
+ if (nb_notif_walk) {
+ __dbg("oper-state walk already in progress.");
+ return;
+ }
+ if (event_is_scheduled(nb_notif_timer)) {
+ __dbg("oper-state notification timer already set.");
+ return;
+ }
+
+ __dbg("oper-state notification setting timer to fire in: %d msec ", NB_NOTIF_TIMER_MSEC);
+ event_add_timer_msec(nb_notif_master, timer_walk_start, NULL, NB_NOTIF_TIMER_MSEC,
+ &nb_notif_timer);
+}
+
+void nb_notif_set_filters(const char **selectors, bool replace)
+{
+ const char **csp;
+
+ if (replace) {
+ darr_free_free(nb_notif_filters);
+ nb_notif_filters = selectors;
+ return;
+ }
+ darr_foreach_p (selectors, csp)
+ *darr_append(nb_notif_filters) = *csp;
+ darr_free(selectors);
+}
+
+void nb_notif_init(struct event_loop *tm)
+{
+ nb_notif_master = tm;
+ op_changes_queue_init(&op_changes_queue);
+}
+
+void nb_notif_terminate(void)
+{
+ struct nb_notif_walk_args *args;
+ struct op_changes_group *group;
+
+ EVENT_OFF(nb_notif_timer);
+
+ if (nb_notif_walk) {
+ nb_oper_cancel_walk(nb_notif_walk);
+ /* need to free the group that's in the walk */
+ args = nb_oper_walk_finish_arg(nb_notif_walk);
+ if (args)
+ op_changes_group_free(args->group);
+ nb_notif_walk = NULL;
+ }
+
+ while ((group = op_changes_group_next()))
+ op_changes_group_free(group);
+
+ darr_free_free(nb_notif_filters);
+}
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index c80cdc116..a296b147a 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -35,6 +35,7 @@
* We must also process containers with lookup-next descendants last.
*/
+DEFINE_MTYPE_STATIC(LIB, NB_STATE, "Northbound State");
DEFINE_MTYPE_STATIC(LIB, NB_YIELD_STATE, "NB Yield State");
DEFINE_MTYPE_STATIC(LIB, NB_NODE_INFOS, "NB Node Infos");
@@ -1833,6 +1834,20 @@ bool nb_oper_is_yang_lib_query(const char *xpath)
return strlen(xpath) > liblen;
}
+void *nb_oper_walk_finish_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->finish_arg;
+}
+
+void *nb_oper_walk_cb_arg(void *walk)
+{
+ struct nb_op_yield_state *ys = walk;
+
+ return ys->cb_arg;
+}
+
void *nb_oper_walk(const char *xpath, struct yang_translator *translator,
uint32_t flags, bool should_batch, nb_oper_data_cb cb,
void *cb_arg, nb_oper_data_finish_cb finish, void *finish_arg)
diff --git a/lib/subdir.am b/lib/subdir.am
index 4bcce9a2b..984e92ff1 100644
--- a/lib/subdir.am
+++ b/lib/subdir.am
@@ -84,6 +84,7 @@ lib_libfrr_la_SOURCES = \
lib/northbound.c \
lib/northbound_cli.c \
lib/northbound_db.c \
+ lib/northbound_notif.c \
lib/northbound_oper.c \
lib/ntop.c \
lib/openbsd-tree.c \
diff --git a/lib/vrf.c b/lib/vrf.c
index 31632a80d..9be8a9faa 100644
--- a/lib/vrf.c
+++ b/lib/vrf.c
@@ -22,6 +22,9 @@
#include "northbound.h"
#include "northbound_cli.h"
+/* Set by the owner (zebra). */
+bool vrf_notify_oper_changes;
+
/* default VRF name value used when VRF backend is not NETNS */
#define VRF_DEFAULT_NAME_INTERNAL "default"
@@ -105,6 +108,19 @@ int vrf_switchback_to_initial(void)
return ret;
}
+static void vrf_update_state(struct vrf *vrf)
+{
+ if (!vrf->state || !vrf_notify_oper_changes)
+ return;
+
+ /*
+ * Remove top level container update when we have patch support, for now
+ * this keeps us from generating 2 separate REPLACE messages though.
+ */
+ nb_op_updatef(vrf->state, "id", "%u", vrf->vrf_id);
+ nb_op_update(vrf->state, "active", CHECK_FLAG(vrf->status, VRF_ACTIVE) ? "true" : "false");
+}
+
/* Get a VRF. If not found, create one.
* Arg:
* name - The name of the vrf. May be NULL if unknown.
@@ -155,16 +171,32 @@ struct vrf *vrf_get(vrf_id_t vrf_id, const char *name)
/* Set name */
if (name && vrf->name[0] != '\0' && strcmp(name, vrf->name)) {
- /* update the vrf name */
+ /* vrf name has changed */
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
- strlcpy(vrf->data.l.netns_name,
- name, NS_NAMSIZ);
+ strlcpy(vrf->data.l.netns_name, name, NS_NAMSIZ);
strlcpy(vrf->name, name, sizeof(vrf->name));
RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+ /* New state with new name */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
} else if (name && vrf->name[0] == '\0') {
strlcpy(vrf->name, name, sizeof(vrf->name));
RB_INSERT(vrf_name_head, &vrfs_by_name, vrf);
+
+ /* We have a name now so we can have state */
+ if (vrf_notify_oper_changes)
+ vrf->state = nb_op_update_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]/state",
+ NULL, vrf->name);
}
+ /* Update state before hook call */
+ if (vrf->state)
+ vrf_update_state(vrf);
+
if (new &&vrf_master.vrf_new_hook)
(*vrf_master.vrf_new_hook)(vrf);
@@ -208,6 +240,7 @@ struct vrf *vrf_update(vrf_id_t new_vrf_id, const char *name)
vrf->vrf_id = new_vrf_id;
RB_INSERT(vrf_id_head, &vrfs_by_id, vrf);
+ vrf_update_state(vrf);
} else {
/*
@@ -254,6 +287,11 @@ void vrf_delete(struct vrf *vrf)
if (vrf->name[0] != '\0')
RB_REMOVE(vrf_name_head, &vrfs_by_name, vrf);
+ if (vrf_notify_oper_changes) {
+ nb_op_update_delete_pathf(NULL, "/frr-vrf:lib/vrf[name=\"%s\"]", vrf->name);
+ lyd_free_all(vrf->state);
+ }
+
XFREE(MTYPE_VRF, vrf);
}
@@ -282,6 +320,8 @@ int vrf_enable(struct vrf *vrf)
SET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (vrf_master.vrf_enable_hook)
(*vrf_master.vrf_enable_hook)(vrf);
@@ -307,6 +347,8 @@ void vrf_disable(struct vrf *vrf)
UNSET_FLAG(vrf->status, VRF_ACTIVE);
+ vrf_update_state(vrf);
+
if (debug_vrf)
zlog_debug("VRF %s(%u) is to be disabled.", vrf->name,
vrf->vrf_id);
diff --git a/lib/vrf.h b/lib/vrf.h
index 3ebb6ddf5..ad302de9b 100644
--- a/lib/vrf.h
+++ b/lib/vrf.h
@@ -80,6 +80,8 @@ struct vrf {
/* Back pointer to namespace context */
void *ns_ctxt;
+ struct lyd_node *state;
+
QOBJ_FIELDS;
};
RB_HEAD(vrf_id_head, vrf);
@@ -299,6 +301,7 @@ extern void vrf_disable(struct vrf *vrf);
extern int vrf_enable(struct vrf *vrf);
extern void vrf_delete(struct vrf *vrf);
+extern bool vrf_notify_oper_changes;
extern const struct frr_yang_module_info frr_vrf_info;
extern const struct frr_yang_module_info frr_vrf_cli_info;
diff --git a/lib/yang.c b/lib/yang.c
index b847b8b77..2aa353925 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -14,6 +14,7 @@
#include <libyang/version.h>
#include "northbound.h"
#include "frrstr.h"
+#include "darr.h"
#include "lib/config_paths.h"
@@ -680,6 +681,116 @@ void yang_dnode_rpc_output_add(struct lyd_node *output, const char *xpath,
assert(err == LY_SUCCESS);
}
+struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value)
+{
+ struct lyd_node *dnode, *parent;
+ LY_ERR err;
+
+ err = lyd_new_path2(tree, ly_native_ctx, path, value, 0, 0, LYD_NEW_PATH_UPDATE, &parent,
+ &dnode);
+ assert(err == LY_SUCCESS);
+
+ /*
+ * If the node exists and isn't updated returned dnode will be NULL, so
+ * we need to find it. But even if returned it can be the first newly
+ * created node (could be container of path) not the actual path dnode.
+ * So we always find.
+ */
+ err = lyd_find_path(tree ?: parent, path, false, &dnode);
+ assert(err == LY_SUCCESS);
+
+ return dnode;
+}
+
+void yang_state_delete(struct lyd_node *tree, const char *path)
+{
+ LY_ERR err;
+
+ if (!tree)
+ return;
+
+ if (path) {
+ err = lyd_find_path(tree, path, false, &tree);
+ if (err != LY_SUCCESS) {
+ zlog_info("State %s has already been deleted", path);
+ return;
+ }
+ }
+ lyd_free_tree(tree);
+}
+
+PRINTFRR(2, 0)
+struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap)
+{
+ struct lyd_node *dnode;
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(path);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, value);
+ dnode = yang_state_new_vpathf(tree, path_fmt, value, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
+PRINTFRR(2, 0)
+void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap)
+{
+ char *path;
+
+ path = darr_vsprintf(path_fmt, ap);
+ yang_state_delete(tree, path);
+ darr_free(path);
+}
+
+void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, path_fmt);
+ yang_state_delete_vpathf(tree, path_fmt, ap);
+ va_end(ap);
+}
+
+PRINTFRR(3, 0)
+struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path, const char *val_fmt,
+ va_list ap)
+{
+ struct lyd_node *dnode;
+ char *value;
+
+ value = darr_vsprintf(val_fmt, ap);
+ dnode = yang_state_new(tree, path, value);
+ darr_free(value);
+
+ return dnode;
+}
+
+struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path, const char *val_fmt, ...)
+{
+ struct lyd_node *dnode;
+ va_list ap;
+
+ va_start(ap, val_fmt);
+ dnode = yang_state_vnewf(tree, path, val_fmt, ap);
+ va_end(ap);
+
+ return dnode;
+}
+
struct yang_data *yang_data_new(const char *xpath, const char *value)
{
struct yang_data *data;
diff --git a/lib/yang.h b/lib/yang.h
index 52857ecf0..eed2fa8db 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -535,6 +535,66 @@ extern struct lyd_node *yang_dnode_dup(const struct lyd_node *dnode);
*/
extern void yang_dnode_free(struct lyd_node *dnode);
+/**
+ * yang_state_new() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @value: The canonical value of the state.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new(struct lyd_node *tree, const char *path, const char *value);
+
+/**
+ * yang_state_delete() - Delete state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to delete, or NULL if @tree should just be
+ * deleted.
+ */
+extern void yang_state_delete(struct lyd_node *tree, const char *path);
+
+/**
+ * yang_state_new_pathf() - Create new state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path_fmt: The path format string of the state node to create.
+ * @value: The canonical value of the state.
+ * @...: The values to substitute into @path_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_new_pathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, ...) PRINTFRR(2, 4);
+extern struct lyd_node *yang_state_new_vpathf(struct lyd_node *tree, const char *path_fmt,
+ const char *value, va_list ap);
+/**
+ * yang_state_delete_pathf() - Delete state data.
+ * @tree: subtree @path_fmt is relative to or NULL in which case @path_fmt must
+ * be absolute.
+ * @path: The path of the state node to delete.
+ * @...: The values to substitute into @path_fmt.
+ */
+extern void yang_state_delete_pathf(struct lyd_node *tree, const char *path_fmt, ...) PRINTFRR(2, 3);
+extern void yang_state_delete_vpathf(struct lyd_node *tree, const char *path_fmt, va_list ap);
+
+/**
+ * yang_state_newf() - Create new state data.
+ * @tree: subtree @path is relative to or NULL in which case @path must be
+ * absolute.
+ * @path: The path of the state node to create.
+ * @val_fmt: The value format string to set the canonical value of the state.
+ * @...: The values to substitute into @val_fmt.
+ *
+ * Return: The new libyang node.
+ */
+extern struct lyd_node *yang_state_newf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, ...) PRINTFRR(3, 4);
+
+extern struct lyd_node *yang_state_vnewf(struct lyd_node *tree, const char *path,
+ const char *val_fmt, va_list ap);
+
/*
* Add a libyang data node to an RPC/action output container.
*
diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4
index f4236cf08..60bdf3da3 100644
--- a/m4/ax_lua.m4
+++ b/m4/ax_lua.m4
@@ -1,5 +1,5 @@
# ===========================================================================
-# http://www.gnu.org/software/autoconf-archive/ax_lua.html
+# https://www.gnu.org/software/autoconf-archive/ax_lua.html
# ===========================================================================
#
# SYNOPSIS
@@ -19,7 +19,7 @@
# header is checked to match the Lua interpreter version exactly. When
# searching for Lua libraries, the version number is used as a suffix.
# This is done with the goal of supporting multiple Lua installs (5.1,
-# 5.2, and 5.3 side-by-side).
+# 5.2, 5.3, and 5.4 side-by-side).
#
# A note on compatibility with previous versions: This file has been
# mostly rewritten for serial 18. Most developers should be able to use
@@ -49,6 +49,14 @@
# interpreter. If LUA is blank, the user's path is searched for an
# suitable interpreter.
#
+# Optionally a LUAJIT option may be set ahead of time to look for and
+# validate a LuaJIT install instead of PUC Lua. Usage might look like:
+#
+# AC_ARG_WITH(luajit, [AS_HELP_STRING([--with-luajit],
+# [Prefer LuaJIT over PUC Lua, even if the latter is newer. Default: no])
+# ])
+# AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" != 'xno'])
+#
# If MINIMUM-VERSION is supplied, then only Lua interpreters with a
# version number greater or equal to MINIMUM-VERSION will be accepted. If
# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a
@@ -82,7 +90,7 @@
# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES.
#
# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is
-# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT-
+# performed, otherwise ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-
# FOUND is blank, then it will default to printing an error. To prevent
# the default behavior, give ':' as an action.
#
@@ -152,6 +160,7 @@
#
# LICENSE
#
+# Copyright (c) 2023 Caleb Maclennan <caleb@alerque.com>
# Copyright (c) 2015 Reuben Thomas <rrt@sc3d.org>
# Copyright (c) 2014 Tim Perkins <tprk77@gmail.com>
#
@@ -166,7 +175,7 @@
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
-# with this program. If not, see <http://www.gnu.org/licenses/>.
+# with this program. If not, see <https://www.gnu.org/licenses/>.
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
@@ -181,7 +190,7 @@
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
-#serial 39
+#serial 47
dnl =========================================================================
dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION],
@@ -197,13 +206,13 @@ AC_DEFUN([AX_PROG_LUA],
AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1])
dnl Find a Lua interpreter.
- m4_define_default([_AX_LUA_INTERPRETER_LIST],
- [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50])
+ m4_define_default([_ax_lua_interpreter_list],
+ [lua lua5.4 lua54 lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50])
m4_if([$1], [],
[ dnl No version check is needed. Find any Lua interpreter.
AS_IF([test "x$LUA" = 'x'],
- [AC_PATH_PROGS([LUA], [_AX_LUA_INTERPRETER_LIST], [:])])
+ [AC_PATH_PROGS([LUA], [_ax_lua_interpreter_list], [:])])
ax_display_LUA='lua'
AS_IF([test "x$LUA" != 'x:'],
@@ -242,7 +251,7 @@ AC_DEFUN([AX_PROG_LUA],
[_ax_check_text="for a Lua interpreter with version >= $1, < $2"])
AC_CACHE_CHECK([$_ax_check_text],
[ax_cv_pathless_LUA],
- [ for ax_cv_pathless_LUA in _AX_LUA_INTERPRETER_LIST none; do
+ [ for ax_cv_pathless_LUA in _ax_lua_interpreter_list none; do
test "x$ax_cv_pathless_LUA" = 'xnone' && break
_AX_LUA_CHK_IS_INTRP([$ax_cv_pathless_LUA], [], [continue])
_AX_LUA_CHK_VER([$ax_cv_pathless_LUA], [$1], [$2], [break])
@@ -268,7 +277,7 @@ AC_DEFUN([AX_PROG_LUA],
ax_cv_lua_version=[`$LUA -e '
-- return a version number in X.Y format
local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)")
- print(ver)'`]
+ print(ver or "")'`]
])
AS_IF([test "x$ax_cv_lua_version" = 'x'],
[AC_MSG_ERROR([invalid Lua version number])])
@@ -469,7 +478,7 @@ AC_DEFUN([AX_LUA_HEADERS],
dnl Some default directories to search.
LUA_SHORT_VERSION=`echo "$LUA_VERSION" | $SED 's|\.||'`
- m4_define_default([_AX_LUA_INCLUDE_LIST],
+ m4_define_default([_ax_lua_include_list],
[ /usr/include/lua$LUA_VERSION \
/usr/include/lua-$LUA_VERSION \
/usr/include/lua/$LUA_VERSION \
@@ -488,9 +497,11 @@ AC_DEFUN([AX_LUA_HEADERS],
dnl Try some other directories if LUA_INCLUDE was not set.
AS_IF([test "x$LUA_INCLUDE" = 'x' &&
- test "x$ac_cv_header_lua_h" != 'xyes'],
+ test "x$ac_cv_header_lua_h" != 'xyes' ||
+ test "x$with_luajit" != 'xno' &&
+ test "x$ac_cv_header_luajit_h" != 'xyes'],
[ dnl Try some common include paths.
- for _ax_include_path in _AX_LUA_INCLUDE_LIST; do
+ for _ax_include_path in _ax_lua_include_list; do
test ! -d "$_ax_include_path" && continue
AC_MSG_CHECKING([for Lua headers in])
@@ -500,6 +511,7 @@ AC_DEFUN([AX_LUA_HEADERS],
AS_UNSET([ac_cv_header_lualib_h])
AS_UNSET([ac_cv_header_lauxlib_h])
AS_UNSET([ac_cv_header_luaconf_h])
+ AS_UNSET([ac_cv_header_luajit_h])
_ax_lua_saved_cppflags=$CPPFLAGS
CPPFLAGS="$CPPFLAGS -I$_ax_include_path"
@@ -514,24 +526,42 @@ AC_DEFUN([AX_LUA_HEADERS],
])
AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'],
- [ AC_CACHE_CHECK([for Lua header version],
- [ax_cv_lua_header_version],
- [
- ax_cv_lua_header_version=`echo LUA_VERSION | \
- $CC -P -E $LUA_INCLUDE -imacros lua.h - | \
- $SED -e 's%"@<:@@<:@:space:@:>@@:>@*"%%g' -e 's%^@<:@@<:@:space:@:>@@:>@*%%' | \
- tr -d '"\n' | \
- $SED -n "s|^Lua \(@<:@0-9@:>@\{1,\}\.@<:@0-9@:>@\{1,\}\).\{0,\}|\1|p"`
- ])
-
- dnl Compare this to the previously found LUA_VERSION.
- AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
- AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
- [ AC_MSG_RESULT([yes])
- ax_header_version_match='yes'
+ [ dnl Make a program to print LUA_VERSION defined in the header.
+ dnl TODO It would be really nice if we could do this without compiling a
+ dnl program, then it would work when cross compiling. But I'm not sure how
+ dnl to do this reliably. For now, assume versions match when cross compiling.
+
+ AS_IF([test "x$cross_compiling" != 'xyes'],
+ [ AC_CACHE_CHECK([for Lua header version],
+ [ax_cv_lua_header_version],
+ [ _ax_lua_saved_cppflags=$CPPFLAGS
+ CPPFLAGS="$CPPFLAGS $LUA_INCLUDE"
+ AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_major=unknown])
+ AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT
+#include <lua.h>
+],[ax_cv_lua_header_version_minor=unknown])
+ AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[
+ ax_cv_lua_header_version=unknown
+ ],[
+ ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor"
+ ])
+ CPPFLAGS=$_ax_lua_saved_cppflags
+ ])
+
+ dnl Compare this to the previously found LUA_VERSION.
+ AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION])
+ AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"],
+ [ AC_MSG_RESULT([yes])
+ ax_header_version_match='yes'
+ ],
+ [ AC_MSG_RESULT([no])
+ ax_header_version_match='no'
+ ])
],
- [ AC_MSG_RESULT([no])
- ax_header_version_match='no'
+ [ AC_MSG_WARN([cross compiling so assuming header version number matches])
+ ax_header_version_match='yes'
])
])
@@ -612,7 +642,7 @@ AC_DEFUN([AX_LUA_LIBS],
],
[_ax_found_lua_libs='yes'],
[_ax_found_lua_libs='no'],
- [$_ax_lua_extra_libs])
+ [$_ax_lua_extra_libs])])
LIBS=$_ax_lua_saved_libs
AS_IF([test "x$ac_cv_search_lua_load" != 'xno' &&
diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c
index 45e154d83..e159d68ec 100644
--- a/mgmtd/mgmt_be_adapter.c
+++ b/mgmtd/mgmt_be_adapter.c
@@ -320,7 +320,7 @@ static void mgmt_be_xpath_map_init(void)
__dbg("Total Cfg XPath Maps: %u", darr_len(be_cfg_xpath_map));
__dbg("Total Oper XPath Maps: %u", darr_len(be_oper_xpath_map));
- __dbg("Total Noitf XPath Maps: %u", darr_len(be_notif_xpath_map));
+ __dbg("Total Notif XPath Maps: %u", darr_len(be_notif_xpath_map));
__dbg("Total RPC XPath Maps: %u", darr_len(be_rpc_xpath_map));
}
@@ -651,13 +651,17 @@ int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg)
return mgmt_msg_native_send_msg(adapter->conn, msg, false);
}
+/*
+ * Send notification to back-ends that subscribed for them.
+ */
static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
size_t msglen)
{
struct mgmt_be_client_adapter *adapter;
struct mgmt_be_xpath_map *map;
- struct nb_node *nb_node;
+ struct nb_node *nb_node = NULL;
const char *notif;
+ bool is_root;
uint id, len;
if (!darr_len(be_notif_xpath_map))
@@ -669,28 +673,34 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
return;
}
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ nb_node = nb_node_find(notif);
+ if (!nb_node) {
+ __log_err("No schema found for notification: %s", notif);
+ return;
+ }
}
darr_foreach_p (be_notif_xpath_map, map) {
- len = strlen(map->xpath_prefix);
- if (strncmp(map->xpath_prefix, nb_node->xpath, len) &&
- strncmp(map->xpath_prefix, notif, len))
- continue;
-
+ if (!is_root) {
+ len = strlen(map->xpath_prefix);
+ if (strncmp(map->xpath_prefix, nb_node->xpath, len) &&
+ strncmp(map->xpath_prefix, notif, len))
+ continue;
+ }
FOREACH_BE_CLIENT_BITS (id, map->clients) {
adapter = mgmt_be_get_adapter_by_id(id);
if (!adapter)
continue;
+
msg_conn_send_msg(adapter->conn, MGMT_MSG_VERSION_NATIVE,
msg, msglen, NULL, false);
}
}
}
+
/*
* Handle a native encoded message
*/
@@ -735,6 +745,9 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter,
mgmt_txn_notify_rpc_reply(adapter, rpc_msg, msg_len);
break;
case MGMT_MSG_CODE_NOTIFY:
+ /*
+ * Handle notify message from a back-end client
+ */
notify_msg = (typeof(notify_msg))msg;
__dbg("Got NOTIFY from '%s'", adapter->name);
mgmt_be_adapter_send_notify(notify_msg, msg_len);
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index 22656f189..96b7cbd59 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -9,6 +9,7 @@
#include <zebra.h>
#include "darr.h"
+#include "frrstr.h"
#include "sockopt.h"
#include "network.h"
#include "libfrr.h"
@@ -31,6 +32,7 @@
#define FOREACH_ADAPTER_IN_LIST(adapter) \
frr_each_safe (mgmt_fe_adapters, &mgmt_fe_adapters, (adapter))
+
enum mgmt_session_event {
MGMTD_FE_SESSION_CFG_TXN_CLNUP = 1,
MGMTD_FE_SESSION_SHOW_TXN_CLNUP,
@@ -55,6 +57,22 @@ DECLARE_LIST(mgmt_fe_sessions, struct mgmt_fe_session_ctx, list_linkage);
#define FOREACH_SESSION_IN_LIST(adapter, session) \
frr_each_safe (mgmt_fe_sessions, &(adapter)->fe_sessions, (session))
+/*
+ * A tree for storing unique notify-select strings.
+ */
+PREDECL_RBTREE_UNIQ(ns_string);
+struct ns_string {
+ struct ns_string_item link;
+ struct list *sessions;
+ char s[];
+};
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2);
+DECLARE_RBTREE_UNIQ(ns_string, struct ns_string, link, ns_string_compare);
+
+/* ---------------- */
+/* Global variables */
+/* ---------------- */
+
static struct event_loop *mgmt_loop;
static struct msg_server mgmt_fe_server = {.fd = -1};
@@ -63,6 +81,72 @@ static struct mgmt_fe_adapters_head mgmt_fe_adapters;
static struct hash *mgmt_fe_sessions;
static uint64_t mgmt_fe_next_session_id;
+static struct ns_string_head mgmt_fe_ns_strings;
+
+/* ------------------------------ */
+/* Notify select string functions */
+/* ------------------------------ */
+
+static uint32_t ns_string_compare(const struct ns_string *ns1, const struct ns_string *ns2)
+{
+ return strcmp(ns1->s, ns2->s);
+}
+
+static void mgmt_fe_free_ns_string(struct ns_string *ns)
+{
+ list_delete(&ns->sessions);
+ XFREE(MTYPE_MGMTD_XPATH, ns);
+}
+
+static void mgmt_fe_free_ns_strings(struct ns_string_head *head)
+{
+ struct ns_string *ns;
+
+ while ((ns = ns_string_pop(head)))
+ mgmt_fe_free_ns_string(ns);
+ ns_string_fini(head);
+}
+
+static void mgmt_fe_ns_string_remove_session(struct ns_string_head *head,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct ns_string *ns;
+
+ frr_each_safe (ns_string, head, ns) {
+ listnode_delete(ns->sessions, session);
+ if (list_isempty(ns->sessions)) {
+ ns_string_del(head, ns);
+ mgmt_fe_free_ns_string(ns);
+ }
+ }
+}
+
+static void mgmt_fe_add_ns_string(struct ns_string_head *head, const char *path, size_t plen,
+ struct mgmt_fe_session_ctx *session)
+{
+ struct ns_string *e, *ns;
+
+ ns = XCALLOC(MTYPE_MGMTD_XPATH, sizeof(*ns) + plen + 1);
+ strlcpy(ns->s, path, plen + 1);
+ e = ns_string_add(head, ns);
+ if (!e)
+ ns->sessions = list_new();
+ if (!listnode_lookup(ns->sessions, session))
+ listnode_add(ns->sessions, session);
+}
+
+char **mgmt_fe_get_all_selectors(void)
+{
+ char **selectors = NULL;
+ struct ns_string *ns;
+
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns)
+ *darr_append(selectors) = darr_strdup(ns->s);
+
+ return selectors;
+}
+
+
/* Forward declarations */
static void
mgmt_fe_session_register_event(struct mgmt_fe_session_ctx *session,
@@ -190,6 +274,7 @@ static void mgmt_fe_cleanup_session(struct mgmt_fe_session_ctx **sessionp)
assert(session->adapter->refcount > 1);
mgmt_fe_adapter_unlock(&session->adapter);
}
+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
darr_free_free(session->notify_xpaths);
hash_release(mgmt_fe_sessions, session);
XFREE(MTYPE_MGMTD_FE_SESSION, session);
@@ -1542,32 +1627,90 @@ static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session,
* @__msg: the message data.
* @msg_len: the length of the message data.
*/
-static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session,
- void *__msg, size_t msg_len)
+static void fe_adapter_handle_notify_select(struct mgmt_fe_session_ctx *session, void *__msg,
+ size_t msg_len)
{
struct mgmt_msg_notify_select *msg = __msg;
uint64_t req_id = msg->req_id;
const char **selectors = NULL;
const char **new;
+ const char **sp;
+ char *selstr = NULL;
+ uint64_t clients = 0;
+ uint ret;
if (msg_len >= sizeof(*msg)) {
- selectors = mgmt_msg_native_strings_decode(msg, msg_len,
- msg->selectors);
+ selectors = mgmt_msg_native_strings_decode(msg, msg_len, msg->selectors);
if (!selectors) {
- fe_adapter_send_error(session, req_id, false, -EINVAL,
- "Invalid message");
+ fe_adapter_send_error(session, req_id, false, -EINVAL, "Invalid message");
return;
}
}
+ if (DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ selstr = frrstr_join(selectors, darr_len(selectors), ", ");
+ if (!selstr)
+ selstr = XSTRDUP(MTYPE_TMP, "");
+ }
+
if (msg->replace) {
+ mgmt_fe_ns_string_remove_session(&mgmt_fe_ns_strings, session);
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
darr_free_free(session->notify_xpaths);
session->notify_xpaths = selectors;
} else if (selectors) {
- new = darr_append_nz(session->notify_xpaths,
- darr_len(selectors));
+ // [ ] Keep a local tree to optimize sending selectors to BE?
+ // [*] Or just KISS and fanout the original message to BEs?
+ // mgmt_remove_add_notify_selectors(session->notify_xpaths, selectors);
+ new = darr_append_nz(session->notify_xpaths, darr_len(selectors));
memcpy(new, selectors, darr_len(selectors) * sizeof(*selectors));
- darr_free(selectors);
+ } else {
+ __log_err("Invalid msg from session-id: %Lu: no selectors present in non-replace msg",
+ session->session_id);
+ darr_free_free(selectors);
+ selectors = NULL;
+ goto done;
}
+
+
+ if (session->notify_xpaths && DEBUG_MODE_CHECK(&mgmt_debug_fe, DEBUG_MODE_ALL)) {
+ const char **sel = session->notify_xpaths;
+ char *s = frrstr_join(sel, darr_len(sel), ", ");
+ __dbg("New NOTIF %d selectors '%s' (replace: %d) txn-id: %Lu for session-id: %Lu",
+ darr_len(sel), s, msg->replace, session->cfg_txn_id, session->session_id);
+ XFREE(MTYPE_TMP, s);
+ }
+
+ /* Add the new selectors to the global tree */
+ darr_foreach_p (selectors, sp)
+ mgmt_fe_add_ns_string(&mgmt_fe_ns_strings, *sp, darr_strlen(*sp), session);
+
+ /* Check if any backends are interested in the new selectors. */
+ if (msg->replace) {
+ /* If we are replacing we'll send all the selectors again with replace flag */
+ clients = mgmt_be_interested_clients("/", MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ } else {
+ darr_foreach_p (selectors, sp)
+ clients |= mgmt_be_interested_clients(*sp, MGMT_BE_XPATH_SUBSCR_TYPE_OPER);
+ }
+ if (!clients) {
+ __dbg("No backends provide oper for notify selectors: '%s' txn-id %Lu session-id: %Lu",
+ selstr, session->txn_id, session->session_id);
+ goto done;
+ }
+
+ /* We don't use a transaction for this, just send the message */
+ ret = mgmt_txn_send_notify_selectors(req_id, clients, msg->replace ? NULL : selectors);
+ if (ret) {
+ fe_adapter_send_error(session, req_id, false, -EINPROGRESS,
+ "Failed to create a NOTIFY_SELECT transaction");
+ }
+done:
+ if (session->notify_xpaths != selectors)
+ darr_free(selectors);
+ if (selstr)
+ XFREE(MTYPE_TMP, selstr);
}
/**
@@ -1758,10 +1901,11 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
{
struct mgmt_fe_client_adapter *adapter;
struct mgmt_fe_session_ctx *session;
- struct nb_node *nb_node;
- const char **xpath_prefix;
+ struct nb_node *nb_node = NULL;
+ struct listnode *node;
+ struct ns_string *ns;
const char *notif;
- bool sendit;
+ bool is_root;
uint len;
assert(msg->refer_id == 0);
@@ -1772,36 +1916,48 @@ void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, size_t msglen
return;
}
- /*
- * We need the nb_node to obtain a path which does not include any
- * specific list entry selectors
- */
- nb_node = nb_node_find(notif);
- if (!nb_node) {
- __log_err("No schema found for notification: %s", notif);
- return;
+ is_root = !strcmp(notif, "/");
+ if (!is_root) {
+ /*
+ * We need the nb_node to obtain a path which does not include any
+ * specific list entry selectors
+ */
+ nb_node = nb_node_find(notif);
+ if (!nb_node) {
+ __log_err("No schema found for notification: %s", notif);
+ return;
+ }
}
- FOREACH_ADAPTER_IN_LIST (adapter) {
- FOREACH_SESSION_IN_LIST (adapter, session) {
- /* If no selectors then always send */
- sendit = !session->notify_xpaths;
- darr_foreach_p (session->notify_xpaths, xpath_prefix) {
- len = strlen(*xpath_prefix);
- if (!strncmp(*xpath_prefix, notif, len) ||
- !strncmp(*xpath_prefix, nb_node->xpath,
- len)) {
- sendit = true;
- break;
- }
- }
- if (sendit) {
+ frr_each (ns_string, &mgmt_fe_ns_strings, ns) {
+ if (!is_root) {
+ len = strlen(ns->s);
+ if (strncmp(ns->s, notif, len) && strncmp(ns->s, nb_node->xpath, len))
+ continue;
+ }
+ for (ALL_LIST_ELEMENTS_RO(ns->sessions, node, session)) {
+ msg->refer_id = session->session_id;
+ (void)fe_adapter_send_native_msg(session->adapter, msg, msglen, false);
+ }
+ }
+
+ /*
+ * Send all YANG defined notifications to all sesisons with *no*
+ * selectors as well (i.e., original NETCONF/RESTCONF notification
+ * scheme).
+ */
+ if (!is_root && CHECK_FLAG(nb_node->snode->nodetype, LYS_NOTIF)) {
+ FOREACH_ADAPTER_IN_LIST (adapter) {
+ FOREACH_SESSION_IN_LIST (adapter, session) {
+ if (session->notify_xpaths)
+ continue;
msg->refer_id = session->session_id;
(void)fe_adapter_send_native_msg(adapter, msg,
msglen, false);
}
}
}
+
msg->refer_id = 0;
}
@@ -1810,9 +1966,10 @@ void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter)
adapter->refcount++;
}
-extern void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter)
+void mgmt_fe_adapter_unlock(struct mgmt_fe_client_adapter **adapter)
{
struct mgmt_fe_client_adapter *a = *adapter;
+
assert(a && a->refcount);
if (!--a->refcount) {
@@ -1840,6 +1997,8 @@ void mgmt_fe_adapter_init(struct event_loop *tm)
hash_create(mgmt_fe_session_hash_key, mgmt_fe_session_hash_cmp,
"MGMT Frontend Sessions");
+ ns_string_init(&mgmt_fe_ns_strings);
+
snprintf(server_path, sizeof(server_path), MGMTD_FE_SOCK_NAME);
if (msg_server_init(&mgmt_fe_server, server_path, tm,
@@ -1869,10 +2028,13 @@ void mgmt_fe_adapter_destroy(void)
msg_server_cleanup(&mgmt_fe_server);
+
/* Deleting the adapters will delete all the sessions */
FOREACH_ADAPTER_IN_LIST (adapter)
mgmt_fe_adapter_delete(adapter);
+ mgmt_fe_free_ns_strings(&mgmt_fe_ns_strings);
+
hash_clean_and_free(&mgmt_fe_sessions, mgmt_fe_abort_if_session);
}
@@ -1885,8 +2047,7 @@ struct msg_conn *mgmt_fe_create_adapter(int conn_fd, union sockunion *from)
adapter = mgmt_fe_find_adapter_by_fd(conn_fd);
if (!adapter) {
- adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER,
- sizeof(struct mgmt_fe_client_adapter));
+ adapter = XCALLOC(MTYPE_MGMTD_FE_ADPATER, sizeof(struct mgmt_fe_client_adapter));
snprintf(adapter->name, sizeof(adapter->name), "Unknown-FD-%d",
conn_fd);
diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h
index 4d94e7604..19a3d1634 100644
--- a/mgmtd/mgmt_fe_adapter.h
+++ b/mgmtd/mgmt_fe_adapter.h
@@ -225,6 +225,13 @@ extern int mgmt_fe_adapter_txn_error(uint64_t txn_id, uint64_t req_id,
const char *errstr);
+/**
+ * mgmt_fe_get_all_selectors() - Get all selectors for all frontend adapters.
+ *
+ * Returns: A darr array of all selectors for all frontend adapters.
+ */
+extern char **mgmt_fe_get_all_selectors(void);
+
/* Fetch frontend client session set-config stats */
extern struct mgmt_setcfg_stats *
mgmt_fe_get_session_setcfg_stats(uint64_t session_id);
diff --git a/mgmtd/mgmt_history.c b/mgmtd/mgmt_history.c
index c97cb7f0f..934748b1f 100644
--- a/mgmtd/mgmt_history.c
+++ b/mgmtd/mgmt_history.c
@@ -177,6 +177,7 @@ static bool mgmt_history_dump_cmt_record_index(void)
return false;
}
+ assert(cnt <= 10); /* silence bad CLANG SA warning */
ret = fwrite(&cmt_info_set, sizeof(struct mgmt_cmt_info_t), cnt, fp);
fclose(fp);
if (ret != cnt) {
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index ccfdd7539..4afab389b 100644
--- a/mgmtd/mgmt_txn.c
+++ b/mgmtd/mgmt_txn.c
@@ -237,6 +237,7 @@ struct mgmt_txn_ctx {
struct event *clnup;
/* List of backend adapters involved in this transaction */
+ /* XXX reap this */
struct mgmt_txn_badapters_head be_adapters;
int refcount;
@@ -2651,6 +2652,52 @@ int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
return 0;
}
+int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors)
+{
+ struct mgmt_msg_notify_select *msg;
+ char **all_selectors = NULL;
+ uint64_t id;
+ int ret;
+ uint i;
+
+ msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_select, 0,
+ MTYPE_MSG_NATIVE_NOTIFY_SELECT);
+ msg->refer_id = MGMTD_TXN_ID_NONE;
+ msg->req_id = req_id;
+ msg->code = MGMT_MSG_CODE_NOTIFY_SELECT;
+ msg->replace = selectors == NULL;
+
+ if (selectors == NULL) {
+ /* Get selectors for all sessions */
+ all_selectors = mgmt_fe_get_all_selectors();
+ selectors = (const char **)all_selectors;
+ }
+
+ darr_foreach_i (selectors, i)
+ mgmt_msg_native_add_str(msg, selectors[i]);
+
+ assert(clients);
+ FOREACH_BE_CLIENT_BITS (id, clients) {
+ /* make sure the backend is running/connected */
+ if (!mgmt_be_get_adapter_by_id(id))
+ continue;
+ ret = mgmt_be_send_native(id, msg);
+ if (ret) {
+ __log_err("Could not send notify-select message to backend client %s",
+ mgmt_be_client_id2name(id));
+ continue;
+ }
+
+ __dbg("Sent notify-select req to backend client %s", mgmt_be_client_id2name(id));
+ }
+ mgmt_msg_native_free_msg(msg);
+
+ if (all_selectors)
+ darr_free_free(all_selectors);
+
+ return 0;
+}
+
/*
* Error reply from the backend client.
*/
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
index 37dadc017..879d20517 100644
--- a/mgmtd/mgmt_txn.h
+++ b/mgmtd/mgmt_txn.h
@@ -297,6 +297,16 @@ extern int mgmt_txn_send_rpc(uint64_t txn_id, uint64_t req_id, uint64_t clients,
LYD_FORMAT result_type, const char *xpath,
const char *data, size_t data_len);
+/**
+ * mgmt_txn_send_notify_selectors() - Send NOTIFY SELECT request.
+ * @req_id: FE client request identifier.
+ * @clients: Bitmask of clients to send RPC to.
+ * @selectors: Array of selectors or NULL to resend all selectors to BE clients.
+ *
+ * Returns 0 on success.
+ */
+extern int mgmt_txn_send_notify_selectors(uint64_t req_id, uint64_t clients, const char **selectors);
+
/*
* Notifiy backend adapter on connection.
*/
diff --git a/ospfd/ospf_asbr.c b/ospfd/ospf_asbr.c
index aa1146702..978a6fcc1 100644
--- a/ospfd/ospf_asbr.c
+++ b/ospfd/ospf_asbr.c
@@ -1145,8 +1145,7 @@ static void ospf_external_aggr_timer(struct ospf *ospf,
aggr->action = operation;
if (ospf->t_external_aggr) {
- if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD) {
-
+ if (ospf->aggr_action == OSPF_ROUTE_AGGR_ADD || operation != OSPF_ROUTE_AGGR_ADD) {
if (IS_DEBUG_OSPF(lsa, EXTNL_LSA_AGGR))
zlog_debug("%s: Not required to restart timer,set is already added.",
__func__);
diff --git a/pimd/pim_bsm.c b/pimd/pim_bsm.c
index 6c4d64923..50fe543b2 100644
--- a/pimd/pim_bsm.c
+++ b/pimd/pim_bsm.c
@@ -354,24 +354,29 @@ static void pim_on_g2rp_timer(struct event *t)
bsrp = EVENT_ARG(t);
EVENT_OFF(bsrp->g2rp_timer);
bsgrp_node = bsrp->bsgrp_node;
-
- /* elapse time is the hold time of expired node */
- elapse = bsrp->rp_holdtime;
+ pim = bsgrp_node->scope->pim;
bsrp_addr = bsrp->rp_address;
- /* update elapse for all bsrp nodes */
- frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) {
- bsrp_node->elapse_time += elapse;
-
- if (is_hold_time_elapsed(bsrp_node)) {
- bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node);
- pim_bsm_rpinfo_free(bsrp_node);
+ /*
+ * Update elapse for all bsrp nodes except on the BSR itself.
+ * The timer is meant to remove any bsr RPs learned from the BSR that
+ * we don't hear from anymore. on the BSR itself, no need to do this.
+ */
+ if (pim->global_scope.state != BSR_ELECTED) {
+ /* elapse time is the hold time of expired node */
+ elapse = bsrp->rp_holdtime;
+ frr_each_safe (bsm_rpinfos, bsgrp_node->bsrp_list, bsrp_node) {
+ bsrp_node->elapse_time += elapse;
+
+ if (is_hold_time_elapsed(bsrp_node)) {
+ bsm_rpinfos_del(bsgrp_node->bsrp_list, bsrp_node);
+ pim_bsm_rpinfo_free(bsrp_node);
+ }
}
}
/* Get the next elected rp node */
bsrp = bsm_rpinfos_first(bsgrp_node->bsrp_list);
- pim = bsgrp_node->scope->pim;
rn = route_node_lookup(pim->rp_table, &bsgrp_node->group);
if (!rn) {
@@ -386,7 +391,7 @@ static void pim_on_g2rp_timer(struct event *t)
return;
}
- if (rp_info->rp_src != RP_SRC_STATIC) {
+ if (rp_info->rp_src == RP_SRC_BSR) {
/* If new rp available, change it else delete the existing */
if (bsrp) {
pim_g2rp_timer_start(
@@ -2165,6 +2170,7 @@ static void cand_addrsel_config_write(struct vty *vty,
int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
{
struct bsm_scope *scope = &pim->global_scope;
+ struct cand_rp_group *group;
int ret = 0;
if (scope->cand_rp_addrsel.cfg_enable) {
@@ -2176,14 +2182,11 @@ int pim_cand_config_write(struct pim_instance *pim, struct vty *vty)
cand_addrsel_config_write(vty, &scope->cand_rp_addrsel);
vty_out(vty, "\n");
ret++;
+ }
- struct cand_rp_group *group;
-
- frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
- vty_out(vty, " bsr candidate-rp group %pFX\n",
- &group->p);
- ret++;
- }
+ frr_each (cand_rp_groups, scope->cand_rp_groups, group) {
+ vty_out(vty, " bsr candidate-rp group %pFX\n", &group->p);
+ ret++;
}
if (scope->bsr_addrsel.cfg_enable) {
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/__init__.py
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
new file mode 100644
index 000000000..428b1d992
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r1/frr.conf
@@ -0,0 +1,35 @@
+!
+interface r1-eth0
+ ip address 192.168.179.4/24
+exit
+!
+router bgp 65001
+!
+router bgp 65001 vrf CUSTOMER-A
+ bgp router-id 192.168.179.4
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.5 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.5 next-hop-self
+ neighbor 192.168.179.5 allowas-in 10
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn both 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65001 vrf CUSTOMER-B
+ bgp router-id 192.168.0.1
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
new file mode 100644
index 000000000..58e63d6cf
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/r2/frr.conf
@@ -0,0 +1,48 @@
+!
+interface lo
+ ip address 10.10.10.10/32
+!
+interface r2-eth0
+ ip address 192.168.179.5/24
+exit
+!
+interface r2-eth1
+ ip address 192.168.2.2/24
+exit
+!
+router bgp 65002
+!
+router bgp 65002 vrf CUSTOMER-A
+ bgp router-id 192.168.179.5
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+ neighbor 192.168.179.4 remote-as external
+!
+ address-family ipv4 unicast
+ neighbor 192.168.179.4 next-hop-self
+ neighbor 192.168.179.4 route-map r1 out
+ label vpn export auto
+ rd vpn export 100:1
+ rt vpn import 100:1 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+router bgp 65002 vrf CUSTOMER-B
+ bgp router-id 192.168.0.2
+ no bgp ebgp-requires-policy
+ no bgp network import-check
+!
+ address-family ipv4 unicast
+ redistribute connected
+ network 10.10.10.10/32
+ label vpn export auto
+ rd vpn export 100:2
+ rt vpn both 100:2
+ export vpn
+ import vpn
+ exit-address-family
+!
+route-map r1 permit 10
+ set as-path prepend 65001
+!
diff --git a/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
new file mode 100644
index 000000000..23325c7a1
--- /dev/null
+++ b/tests/topotests/bgp_vpnv4_import_allowas_in_between_vrf/test_bgp_vpnv4_import_allowas_in_between_vrf.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: ISC
+
+#
+# Copyright (c) 2024 by
+# Donatas Abraitis <donatas@opensourcerouting.org>
+#
+
+import os
+import sys
+import json
+import pytest
+import functools
+
+CWD = os.path.dirname(os.path.realpath(__file__))
+sys.path.append(os.path.join(CWD, "../"))
+
+# pylint: disable=C0413
+from lib import topotest
+from lib.topogen import Topogen, get_topogen
+
+pytestmark = [pytest.mark.bgpd]
+
+
+def build_topo(tgen):
+ tgen.add_router("r1")
+ tgen.add_router("r2")
+
+ switch = tgen.add_switch("s1")
+ switch.add_link(tgen.gears["r1"])
+ switch.add_link(tgen.gears["r2"])
+
+ switch = tgen.add_switch("s2")
+ switch.add_link(tgen.gears["r1"])
+
+ switch = tgen.add_switch("s3")
+ switch.add_link(tgen.gears["r2"])
+
+
+def setup_module(mod):
+ tgen = Topogen(build_topo, mod.__name__)
+ tgen.start_topology()
+
+ r1 = tgen.gears["r1"]
+ r2 = tgen.gears["r2"]
+
+ r1.run("ip link add CUSTOMER-A type vrf table 1001")
+ r1.run("ip link set up dev CUSTOMER-A")
+ r1.run("ip link set r1-eth0 master CUSTOMER-A")
+
+ r1.run("ip link add CUSTOMER-B type vrf table 1002")
+ r1.run("ip link set up dev CUSTOMER-B")
+ r1.run("ip link set r1-eth1 master CUSTOMER-B")
+
+ r2.run("ip link add CUSTOMER-A type vrf table 1001")
+ r2.run("ip link set up dev CUSTOMER-A")
+ r2.run("ip link set r2-eth0 master CUSTOMER-A")
+
+ r2.run("ip link add CUSTOMER-B type vrf table 1002")
+ r2.run("ip link set up dev CUSTOMER-B")
+ r2.run("ip link set r2-eth1 master CUSTOMER-B")
+
+ 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)))
+
+ tgen.start_router()
+
+
+def teardown_module(mod):
+ tgen = get_topogen()
+ tgen.stop_topology()
+
+
+def test_bgp_vpnv4_import_allowas_in_between_vrf():
+ tgen = get_topogen()
+
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"]
+
+ def _bgp_converge():
+ output = json.loads(
+ r1.vtysh_cmd("show bgp vrf CUSTOMER-A ipv4 unicast 10.10.10.10/32 json")
+ )
+ expected = {
+ "paths": [
+ {
+ "aspath": {
+ "string": "65002 65001",
+ },
+ "valid": True,
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_bgp_converge)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert result is None, "Failed to see 10.10.10.10/32 with a valid next-hop"
+
+ def _vrf_route_imported_to_vrf():
+ output = json.loads(
+ r1.vtysh_cmd("show ip route vrf CUSTOMER-B 10.10.10.10/32 json")
+ )
+ expected = {
+ "10.10.10.10/32": [
+ {
+ "protocol": "bgp",
+ "vrfName": "CUSTOMER-B",
+ "selected": True,
+ "installed": True,
+ "table": 1002,
+ "internalNextHopNum": 1,
+ "internalNextHopActiveNum": 1,
+ "nexthops": [
+ {
+ "fib": True,
+ "ip": "192.168.179.5",
+ "afi": "ipv4",
+ "interfaceName": "r1-eth0",
+ "vrf": "CUSTOMER-A",
+ "active": True,
+ }
+ ],
+ }
+ ]
+ }
+ return topotest.json_cmp(output, expected)
+
+ test_func = functools.partial(_vrf_route_imported_to_vrf)
+ _, result = topotest.run_and_expect(test_func, None, count=30, wait=1)
+ assert (
+ result is None
+ ), "Failed to see 10.10.10.10/32 to be imported into CUSTOMER-B VRF (Zebra)"
+
+
+if __name__ == "__main__":
+ args = ["-s"] + sys.argv[1:]
+ sys.exit(pytest.main(args))
diff --git a/tests/topotests/conftest.py b/tests/topotests/conftest.py
index dafd19c28..117ff74e4 100755
--- a/tests/topotests/conftest.py
+++ b/tests/topotests/conftest.py
@@ -31,7 +31,7 @@ from lib import topolog, topotest
try:
# Used by munet native tests
- from munet.testing.fixtures import unet # pylint: disable=all # noqa
+ from munet.testing.fixtures import stepf, unet # pylint: disable=all # noqa
@pytest.fixture(scope="module")
def rundir_module(pytestconfig):
diff --git a/tests/topotests/mgmt_notif/r1/frr.conf b/tests/topotests/mgmt_notif/r1/frr.conf
index 47e73956c..36981c94d 100644
--- a/tests/topotests/mgmt_notif/r1/frr.conf
+++ b/tests/topotests/mgmt_notif/r1/frr.conf
@@ -4,7 +4,7 @@ log file frr.log
no debug memstats-at-exit
debug northbound notifications
-debug northbound libyang
+!! debug northbound libyang
debug northbound events
debug northbound callbacks
diff --git a/tests/topotests/mgmt_notif/r2/frr.conf b/tests/topotests/mgmt_notif/r2/frr.conf
index cd052011e..540961a0e 100644
--- a/tests/topotests/mgmt_notif/r2/frr.conf
+++ b/tests/topotests/mgmt_notif/r2/frr.conf
@@ -16,7 +16,7 @@ ip route 22.22.22.22/32 lo
interface r2-eth0
ip address 1.1.1.2/24
- ip rip authentication string bar
+ ip rip authentication string foo
ip rip authentication mode text
exit
diff --git a/tests/topotests/mgmt_notif/test_notif.py b/tests/topotests/mgmt_notif/test_notif.py
index e5286faae..526f051e6 100644
--- a/tests/topotests/mgmt_notif/test_notif.py
+++ b/tests/topotests/mgmt_notif/test_notif.py
@@ -10,9 +10,12 @@
Test YANG Notifications
"""
import json
+import logging
import os
+import re
import pytest
+from lib.micronet import Timeout, comm_error
from lib.topogen import Topogen
from lib.topotest import json_cmp
from oper import check_kernel_32
@@ -42,7 +45,57 @@ def tgen(request):
tgen.stop_topology()
-def test_frontend_notification(tgen):
+def myreadline(f):
+ buf = ""
+ while True:
+ # logging.debug("READING 1 CHAR")
+ c = f.read(1)
+ if not c:
+ return buf if buf else None
+ buf += c
+ # logging.debug("READ CHAR: '%s'", c)
+ if c == "\n":
+ return buf
+
+
+def _wait_output(f, regex, maxwait=120):
+ timeout = Timeout(maxwait)
+ while not timeout.is_expired():
+ # line = p.stdout.readline()
+ line = myreadline(f)
+ if not line:
+ assert None, "EOF waiting for '{}'".format(regex)
+ line = line.rstrip()
+ if line:
+ logging.debug("GOT LINE: '%s'", line)
+ m = re.search(regex, line)
+ if m:
+ return m
+ assert None, "Failed to get output matching '{}' withint {} actual {}s".format(
+ regex, maxwait, timeout.elapsed()
+ )
+
+
+def get_op_and_json(output):
+ op = ""
+ path = ""
+ data = ""
+ for line in output.split("\n"):
+ if not line:
+ continue
+ if not op:
+ m = re.match("#OP=([A-Z]*): (.*)", line)
+ if m:
+ op = m.group(1)
+ path = m.group(2)
+ continue
+ data += line + "\n"
+ if not op:
+ assert False, f"No notifcation op present in:\n{output}"
+ return op, path, data
+
+
+def test_frontend_datastore_notification(tgen):
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
@@ -50,30 +103,141 @@ def test_frontend_notification(tgen):
check_kernel_32(r1, "11.11.11.11", 1, "")
- fe_client_path = CWD + "/../lib/fe_client.py --verbose"
+ fe_client_path = CWD + "/../lib/fe_client.py"
rc, _, _ = r1.cmd_status(fe_client_path + " --help")
if rc:
pytest.skip("No protoc or present cannot run test")
- # The first notifications is a frr-ripd:authentication-type-failure
- # So we filter to avoid that, all the rest are frr-ripd:authentication-failure
- # making our test deterministic
- output = r1.cmd_raises(
- fe_client_path + " --listen /frr-ripd:authentication-failure"
+ # Start our FE client in the background
+ p = r1.popen(
+ [fe_client_path, "--datastore", "--listen=/frr-interface:lib/interface"]
)
- jsout = json.loads(output)
+ _wait_output(p.stderr, "Connected", maxwait=10)
+
+ r1.cmd_raises("ip link set r1-eth0 mtu 1200")
+
+ # {"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"if-index":2,"mtu":1200,"mtu6":1200,"speed":10000,"metric":0,"phy-address":"ba:fd:de:b5:8b:90"}}]}}
+
+ try:
+ # Wait for FE client to exit
+ output, error = p.communicate(timeout=10)
+ op, path, data = get_op_and_json(output)
+
+ assert op == "REPLACE"
+ assert path.startswith("/frr-interface:lib/interface[name='r1-eth0']/state")
+
+ jsout = json.loads(data)
+ expected = json.loads(
+ '{"frr-interface:lib":{"interface":[{"name":"r1-eth0","state":{"mtu":1200}}]}}'
+ )
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ p.kill()
+ r1.cmd_raises("ip link set r1-eth0 mtu 1500")
+
+
+def test_frontend_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ fe_client_path = CWD + "/../lib/fe_client.py"
+ rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ output = r1.cmd_raises(
+ fe_client_path + " --listen /frr-ripd:authentication-failure"
+ )
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
- output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen")
- jsout = json.loads(output)
+ output = r1.cmd_raises(
+ fe_client_path + " --use-protobuf --listen /frr-ripd:authentication-failure"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+
+def test_frontend_all_notification(tgen):
+ if tgen.routers_have_failure():
+ pytest.skip(tgen.errors)
+
+ r1 = tgen.gears["r1"].net
+
+ check_kernel_32(r1, "11.11.11.11", 1, "")
+
+ fe_client_path = CWD + "/../lib/fe_client.py"
+ rc, _, _ = r1.cmd_status(fe_client_path + " --help")
+
+ if rc:
+ pytest.skip("No protoc or present cannot run test")
+
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
+
+ try:
+ # The first notifications is a frr-ripd:authentication-type-failure
+ # All the rest are frr-ripd:authentication-failure so we check for both.
+ output = r1.cmd_raises(fe_client_path + " --listen /")
+ jsout = json.loads(output)
+ expected = {
+ "frr-ripd:authentication-type-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ if result is not None:
+ expected = {
+ "frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}
+ }
+ result = json_cmp(jsout, expected)
+ assert result is None
+
+ output = r1.cmd_raises(fe_client_path + " --use-protobuf --listen /")
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
def test_backend_notification(tgen):
@@ -90,12 +254,28 @@ def test_backend_notification(tgen):
if rc:
pytest.skip("No mgmtd_testc")
- output = r1.cmd_raises(
- be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
- )
-
- jsout = json.loads(output)
+ # Update config to non-matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string bar
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
- expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
- result = json_cmp(jsout, expected)
- assert result is None
+ try:
+ output = r1.cmd_raises(
+ be_client_path
+ + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
+ )
+ jsout = json.loads(output)
+ expected = {"frr-ripd:authentication-failure": {"interface-name": "r1-eth0"}}
+ result = json_cmp(jsout, expected)
+ assert result is None
+ finally:
+ # Update config to matching authentication.
+ conf = """
+ conf t
+ interface r1-eth0
+ ip rip authentication string foo
+ """
+ r1.cmd_raises("vtysh", stdin=conf)
diff --git a/zebra/dplane_fpm_nl.c b/zebra/dplane_fpm_nl.c
index 3ec1c9d65..b8dbabb60 100644
--- a/zebra/dplane_fpm_nl.c
+++ b/zebra/dplane_fpm_nl.c
@@ -587,6 +587,7 @@ static void fpm_read(struct event *t)
struct zebra_dplane_ctx *ctx;
size_t available_bytes;
size_t hdr_available_bytes;
+ int ival;
/* Let's ignore the input at the moment. */
rv = stream_read_try(fnc->ibuf, fnc->socket,
@@ -715,17 +716,28 @@ static void fpm_read(struct event *t)
break;
}
+ /* Parse the route data into a dplane ctx, then
+ * enqueue it to zebra for processing.
+ */
ctx = dplane_ctx_alloc();
dplane_ctx_route_init(ctx, DPLANE_OP_ROUTE_NOTIFY, NULL,
NULL);
- if (netlink_route_change_read_unicast_internal(
- hdr, 0, false, ctx) != 1) {
- dplane_ctx_fini(&ctx);
- stream_pulldown(fnc->ibuf);
+
+ if (netlink_route_notify_read_ctx(hdr, 0, ctx) >= 0) {
+ /* In the FPM encoding, the vrfid is present */
+ ival = dplane_ctx_get_table(ctx);
+ dplane_ctx_set_vrf(ctx, ival);
+ dplane_ctx_set_table(ctx,
+ ZEBRA_ROUTE_TABLE_UNKNOWN);
+
+ dplane_provider_enqueue_to_zebra(ctx);
+ } else {
/*
* Let's continue to read other messages
* Even if we ignore this one.
*/
+ dplane_ctx_fini(&ctx);
+ stream_pulldown(fnc->ibuf);
}
break;
default:
diff --git a/zebra/interface.c b/zebra/interface.c
index 1c86a6a5c..e49e8eac5 100644
--- a/zebra/interface.c
+++ b/zebra/interface.c
@@ -81,7 +81,7 @@ static void if_zebra_speed_update(struct event *thread)
if (new_speed != ifp->speed) {
zlog_info("%s: %s old speed: %u new speed: %u", __func__,
ifp->name, ifp->speed, new_speed);
- ifp->speed = new_speed;
+ if_update_state_speed(ifp, new_speed);
if_add_update(ifp);
changed = true;
}
@@ -1563,17 +1563,20 @@ static inline void zebra_if_set_ziftype(struct interface *ifp,
static void interface_update_hw_addr(struct zebra_dplane_ctx *ctx,
struct interface *ifp)
{
- int i;
+ uint8_t hw_addr[INTERFACE_HWADDR_MAX];
+ uint i, hw_addr_len;
- ifp->hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
- memcpy(ifp->hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), ifp->hw_addr_len);
+ hw_addr_len = dplane_ctx_get_ifp_hw_addr_len(ctx);
+ memcpy(hw_addr, dplane_ctx_get_ifp_hw_addr(ctx), hw_addr_len);
- for (i = 0; i < ifp->hw_addr_len; i++)
- if (ifp->hw_addr[i] != 0)
+ for (i = 0; i < hw_addr_len; i++)
+ if (hw_addr[i] != 0)
break;
- if (i == ifp->hw_addr_len)
- ifp->hw_addr_len = 0;
+ if (i == hw_addr_len)
+ hw_addr_len = 0;
+
+ if_update_state_hw_addr(ifp, hw_addr, hw_addr_len);
}
static void interface_update_l2info(struct zebra_dplane_ctx *ctx,
@@ -1984,9 +1987,10 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
/* Update interface information. */
set_ifindex(ifp, ifindex, zns);
ifp->flags = flags;
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
- ifp->speed = kernel_get_speed(ifp, NULL);
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
+ if_update_state_speed(ifp, kernel_get_speed(ifp, NULL));
ifp->ptm_status = ZEBRA_PTM_STATUS_UNKNOWN;
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
@@ -2036,6 +2040,7 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
IS_ZEBRA_IF_BRIDGE_VLAN_AWARE(
zif));
}
+ // if_update_state(ifp);
} else if (ifp->vrf->vrf_id != vrf_id) {
/* VRF change for an interface. */
if (IS_ZEBRA_DEBUG_KERNEL)
@@ -2058,8 +2063,9 @@ static void zebra_if_dplane_ifp_handling(struct zebra_dplane_ctx *ctx)
(unsigned long long)flags);
set_ifindex(ifp, ifindex, zns);
- ifp->mtu6 = ifp->mtu = mtu;
- ifp->metric = 0;
+ if_update_state_mtu(ifp, mtu);
+ if_update_state_mtu6(ifp, mtu);
+ if_update_state_metric(ifp, 0);
ifp->txqlen = dplane_ctx_get_intf_txqlen(ctx);
/*
diff --git a/zebra/main.c b/zebra/main.c
index 4546d1477..47b4a4408 100644
--- a/zebra/main.c
+++ b/zebra/main.c
@@ -356,6 +356,9 @@ int main(int argc, char **argv)
zserv_path = NULL;
+ if_notify_oper_changes = true;
+ vrf_notify_oper_changes = true;
+
vrf_configure_backend(VRF_BACKEND_VRF_LITE);
frr_preinit(&zebra_di, argc, argv);
diff --git a/zebra/rt_netlink.c b/zebra/rt_netlink.c
index 492fe5288..b32882e85 100644
--- a/zebra/rt_netlink.c
+++ b/zebra/rt_netlink.c
@@ -723,44 +723,52 @@ static uint16_t parse_multipath_nexthops_unicast(ns_id_t ns_id, struct nexthop_g
return nhop_num;
}
-/* Looking up routing table by netlink interface. */
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx)
+/*
+ * Parse netlink route message and capture info into a dplane ctx.
+ * Returns <0 if the message is to be skipped (might be an error)
+ */
+static int netlink_route_read_unicast_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct rtattr **tb_in,
+ struct zebra_dplane_ctx *ctx)
{
+ int ret = 0;
int len;
struct rtmsg *rtm;
- struct rtattr *tb[RTA_MAX + 1];
+ struct rtattr **tb, *tb_array[RTA_MAX + 1];
uint32_t flags = 0;
struct prefix p;
- struct prefix_ipv6 src_p = {};
- vrf_id_t vrf_id;
+ struct prefix src_p = {};
bool selfroute;
-
- char anyaddr[16] = {0};
-
+ char anyaddr[16] = {};
int proto = ZEBRA_ROUTE_KERNEL;
int index = 0;
- int table;
+ int tableid;
int metric = 0;
uint32_t mtu = 0;
uint8_t distance = 0;
route_tag_t tag = 0;
- uint32_t nhe_id = 0;
-
+ uint32_t nhg_id = 0;
void *dest = NULL;
void *gate = NULL;
+ int gate_len;
void *prefsrc = NULL; /* IPv4 preferred source host address */
+ int prefsrc_len;
void *src = NULL; /* IPv6 srcdest source prefix */
enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi = AFI_IP;
+ struct ipaddr addr = {};
- frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
- startup);
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Netlink route message received with invalid size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
rtm = NLMSG_DATA(h);
- if (startup && h->nlmsg_type != RTM_NEWROUTE)
- return 0;
switch (rtm->rtm_type) {
case RTN_UNICAST:
break;
@@ -778,54 +786,42 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
nl_rttype_to_str(rtm->rtm_type),
rtm->rtm_type);
- return 0;
+ ret = -1;
+ goto done;
}
- len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
- if (len < 0) {
- zlog_err(
- "%s: Message received from netlink is of a broken size %d %zu",
- __func__, h->nlmsg_len,
- (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
- return -1;
+ if ((rtm->rtm_flags & RTM_F_CLONED) ||
+ (rtm->rtm_protocol == RTPROT_REDIRECT)) {
+ ret = -1;
+ goto done;
}
- netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
-
- if (rtm->rtm_flags & RTM_F_CLONED)
- return 0;
- if (rtm->rtm_protocol == RTPROT_REDIRECT)
- return 0;
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS) {
+ ret = -1;
+ goto done;
+ }
- selfroute = is_selfroute(rtm->rtm_protocol);
+ dplane_ctx_set_ns_id(ctx, ns_id);
- if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
- !zrouter.asic_offloaded && !ctx) {
- if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("Route type: %d Received that we think we have originated, ignoring",
- rtm->rtm_protocol);
- return 0;
+ /* Parse attrs if necessary */
+ if (tb_in != NULL) {
+ tb = tb_in;
+ } else {
+ netlink_parse_rtattr(tb_array, RTA_MAX, RTM_RTA(rtm), len);
+ tb = tb_array;
}
- /* We don't care about change notifications for the MPLS table. */
- /* TODO: Revisit this. */
- if (rtm->rtm_family == AF_MPLS)
- return 0;
+ selfroute = is_selfroute(rtm->rtm_protocol);
/* Table corresponding to route. */
if (tb[RTA_TABLE])
- table = *(int *)RTA_DATA(tb[RTA_TABLE]);
+ tableid = *(int *)RTA_DATA(tb[RTA_TABLE]);
else
- table = rtm->rtm_table;
-
- /* Map to VRF */
- vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
- if (vrf_id == VRF_DEFAULT) {
- if (!is_zebra_valid_kernel_table(table)
- && !is_zebra_main_routing_table(table))
- return 0;
- }
+ tableid = rtm->rtm_table;
+ /* Map flags values */
if (rtm->rtm_flags & RTM_F_TRAP)
flags |= ZEBRA_FLAG_TRAPPED;
if (rtm->rtm_flags & RTM_F_OFFLOAD)
@@ -836,7 +832,7 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (h->nlmsg_flags & NLM_F_APPEND)
flags |= ZEBRA_FLAG_OUTOFSYNC;
- /* Route which inserted by Zebra. */
+ /* Route which was inserted by Zebra. */
if (selfroute) {
flags |= ZEBRA_FLAG_SELFROUTE;
proto = proto2zebra(rtm->rtm_protocol, rtm->rtm_family, false);
@@ -854,14 +850,18 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
else
src = anyaddr;
- if (tb[RTA_PREFSRC])
+ if (tb[RTA_PREFSRC]) {
prefsrc = RTA_DATA(tb[RTA_PREFSRC]);
+ prefsrc_len = RTA_PAYLOAD(tb[RTA_PREFSRC]);
+ }
- if (tb[RTA_GATEWAY])
+ if (tb[RTA_GATEWAY]) {
gate = RTA_DATA(tb[RTA_GATEWAY]);
+ gate_len = RTA_PAYLOAD(tb[RTA_GATEWAY]);
+ }
if (tb[RTA_NH_ID])
- nhe_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
+ nhg_id = *(uint32_t *)RTA_DATA(tb[RTA_NH_ID]);
if (tb[RTA_PRIORITY])
metric = *(int *)RTA_DATA(tb[RTA_PRIORITY]);
@@ -887,7 +887,8 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zlog_err(
"Invalid destination prefix length: %u received from kernel route change",
rtm->rtm_dst_len);
- return -1;
+ ret = -1;
+ goto done;
}
memcpy(&p.u.prefix4, dest, 4);
p.prefixlen = rtm->rtm_dst_len;
@@ -895,14 +896,16 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
if (rtm->rtm_src_len != 0) {
flog_warn(
EC_ZEBRA_UNSUPPORTED_V4_SRCDEST,
- "unsupported IPv4 sourcedest route (dest %pFX vrf %u)",
- &p, vrf_id);
- return 0;
+ "unsupported IPv4 sourcedest route (dest %pFX table %u)",
+ &p, tableid);
+ ret = -1;
+ goto done;
}
/* Force debug below to not display anything for source */
src_p.prefixlen = 0;
} else if (rtm->rtm_family == AF_INET6) {
+ afi = AFI_IP6;
p.family = AF_INET6;
if (rtm->rtm_dst_len > IPV6_MAX_BITLEN) {
zlog_err(
@@ -920,14 +923,15 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
rtm->rtm_src_len);
return -1;
}
- memcpy(&src_p.prefix, src, 16);
+ memcpy(&src_p.u.prefix6, src, 16);
src_p.prefixlen = rtm->rtm_src_len;
} else {
/* We only handle the AFs we handle... */
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug("%s: unknown address-family %u", __func__,
rtm->rtm_family);
- return 0;
+ ret = -1;
+ goto done;
}
/*
@@ -956,6 +960,249 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
char buf2[PREFIX_STRLEN];
zlog_debug(
+ "%s %pFX%s%s nsid: %u table_id: %u metric: %d Admin Distance: %d",
+ nl_msg_type_to_str(h->nlmsg_type), &p,
+ src_p.prefixlen ? " from " : "",
+ src_p.prefixlen ? prefix2str(&src_p, buf2, sizeof(buf2))
+ : "",
+ ns_id, tableid, metric, distance);
+ }
+
+ /* Set values in ctx. Note that vrf is not set, because we can only
+ * resolve the FRR vrf info in the main pthread.
+ */
+ dplane_ctx_set_afi(ctx, afi);
+ dplane_ctx_set_safi(ctx, SAFI_UNICAST);
+ dplane_ctx_set_table(ctx, tableid);
+ dplane_ctx_set_vrf(ctx, VRF_UNKNOWN);
+ dplane_ctx_set_ns_id(ctx, ns_id);
+ dplane_ctx_set_dest(ctx, &p);
+ if (src_p.prefixlen > 0)
+ dplane_ctx_set_src(ctx, &src_p);
+ else
+ dplane_ctx_set_src(ctx, NULL);
+ dplane_ctx_set_type(ctx, proto);
+ dplane_ctx_set_flags(ctx, flags);
+ dplane_ctx_set_route_metric(ctx, metric);
+ dplane_ctx_set_route_mtu(ctx, mtu);
+ dplane_ctx_set_distance(ctx, distance);
+ dplane_ctx_set_tag(ctx, tag);
+
+ dplane_ctx_set_ifindex(ctx, index);
+ dplane_ctx_set_route_bhtype(ctx, bh_type);
+ if (prefsrc) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, prefsrc, prefsrc_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, prefsrc, prefsrc_len);
+ }
+
+ dplane_ctx_set_route_prefsrc(ctx, &addr);
+ } else {
+ dplane_ctx_set_route_prefsrc(ctx, NULL);
+ }
+
+ if (gate) {
+ /* Convert to ipaddr */
+ memset(&addr, 0, sizeof(addr));
+
+ if (afi == AFI_IP) {
+ SET_IPADDR_V4(&addr);
+ memcpy(&addr.ipaddr_v4, gate, gate_len);
+ } else {
+ SET_IPADDR_V6(&addr);
+ memcpy(&addr.ipaddr_v6, gate, gate_len);
+ }
+
+ dplane_ctx_set_route_gw(ctx, &addr);
+ }
+
+ if (nhg_id > 0)
+ dplane_ctx_set_nhg_id(ctx, nhg_id);
+
+done:
+
+ return ret;
+}
+
+/*
+ * Public api for use parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx)
+{
+ /* Use the common parser for route-level netlink message info;
+ * we expect the caller to have set the context up with the correct
+ * dplane opcode, and we expect the caller to submit the resulting ctx
+ * for processing in zebra.
+ */
+ return netlink_route_read_unicast_ctx(h, ns_id, NULL, ctx);
+}
+
+/*
+ * Parse a route update netlink message, extract and validate its data,
+ * call into zebra with an update.
+ */
+static int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
+ ns_id_t ns_id, int startup)
+{
+ int len;
+ struct rtmsg *rtm;
+ struct rtattr *tb[RTA_MAX + 1];
+ uint32_t flags = 0;
+ struct prefix p;
+ struct prefix src_p = {};
+ vrf_id_t vrf_id;
+ bool selfroute;
+
+ int proto = ZEBRA_ROUTE_KERNEL;
+ int index = 0;
+ int table;
+ int metric = 0;
+ uint32_t mtu = 0;
+ uint8_t distance = 0;
+ route_tag_t tag = 0;
+ uint32_t nhe_id = 0;
+ void *gate = NULL;
+ const struct ipaddr *gate_addr;
+ void *prefsrc = NULL; /* IPv4 preferred source host address */
+ const struct ipaddr *prefsrc_addr;
+ enum blackhole_type bh_type = BLACKHOLE_UNSPEC;
+ afi_t afi;
+ struct zebra_dplane_ctx *ctx = NULL;
+ int ret;
+
+ frrtrace(3, frr_zebra, netlink_route_change_read_unicast, h, ns_id,
+ startup);
+
+ rtm = NLMSG_DATA(h);
+
+ if (startup && h->nlmsg_type != RTM_NEWROUTE)
+ return 0;
+
+ switch (rtm->rtm_type) {
+ case RTN_UNICAST:
+ case RTN_BLACKHOLE:
+ case RTN_UNREACHABLE:
+ case RTN_PROHIBIT:
+ break;
+ default:
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route rtm_type: %s(%d) intentionally ignoring",
+ nl_rttype_to_str(rtm->rtm_type),
+ rtm->rtm_type);
+ return 0;
+ }
+
+ len = h->nlmsg_len - NLMSG_LENGTH(sizeof(struct rtmsg));
+ if (len < 0) {
+ zlog_err(
+ "%s: Message received from netlink is of a broken size %d %zu",
+ __func__, h->nlmsg_len,
+ (size_t)NLMSG_LENGTH(sizeof(struct rtmsg)));
+ return -1;
+ }
+
+ if (rtm->rtm_flags & RTM_F_CLONED)
+ return 0;
+ if (rtm->rtm_protocol == RTPROT_REDIRECT)
+ return 0;
+
+ /* We don't care about change notifications for the MPLS table. */
+ /* TODO: Revisit this. */
+ if (rtm->rtm_family == AF_MPLS)
+ return 0;
+
+ netlink_parse_rtattr(tb, RTA_MAX, RTM_RTA(rtm), len);
+
+ /*
+ * Allocate a context object and parse the core parts of the route
+ * message.
+ * After this point, note that we need to 'goto done' to exit,
+ * so that the ctx gets cleaned-up.
+ */
+ ctx = dplane_ctx_alloc();
+
+ dplane_ctx_route_init(ctx,
+ h->nlmsg_type == RTM_NEWROUTE ?
+ DPLANE_OP_ROUTE_INSTALL :
+ DPLANE_OP_ROUTE_DELETE, NULL, NULL);
+
+ /* Finish parsing the core route info */
+ ret = netlink_route_read_unicast_ctx(h, ns_id, tb, ctx);
+ if (ret < 0) {
+ ret = 0;
+ goto done;
+ }
+
+ flags = dplane_ctx_get_flags(ctx);
+
+ selfroute = CHECK_FLAG(flags, ZEBRA_FLAG_SELFROUTE);
+
+ if (!startup && selfroute && h->nlmsg_type == RTM_NEWROUTE &&
+ !zrouter.asic_offloaded) {
+ if (IS_ZEBRA_DEBUG_KERNEL)
+ zlog_debug("Route type: %d Received that we think we have originated, ignoring",
+ rtm->rtm_protocol);
+ ret = 0;
+ goto done;
+ }
+
+ /* Table corresponding to route. */
+ table = dplane_ctx_get_table(ctx);
+
+ /* Map to VRF: note that this can _only_ be done in the main pthread */
+ vrf_id = zebra_vrf_lookup_by_table(table, ns_id);
+ if (vrf_id == VRF_DEFAULT) {
+ if (!is_zebra_valid_kernel_table(table)
+ && !is_zebra_main_routing_table(table)) {
+ ret = 0;
+ goto done;
+ }
+ }
+
+ /* Route which inserted by Zebra. */
+ if (selfroute)
+ proto = dplane_ctx_get_type(ctx);
+
+ index = dplane_ctx_get_ifindex(ctx);
+
+ p = *(dplane_ctx_get_dest(ctx));
+
+ if (dplane_ctx_get_src(ctx) == NULL)
+ src_p.prefixlen = 0;
+ else
+ src_p = *(dplane_ctx_get_src(ctx));
+
+ prefsrc_addr = dplane_ctx_get_route_prefsrc(ctx);
+ if (prefsrc_addr)
+ prefsrc = (void *)&(prefsrc_addr->ip.addr);
+
+ gate_addr = dplane_ctx_get_route_gw(ctx);
+ if (!IS_IPADDR_NONE(gate_addr))
+ gate = (void *)&(gate_addr->ip.addr);
+
+ nhe_id = dplane_ctx_get_nhe_id(ctx);
+
+ metric = dplane_ctx_get_metric(ctx);
+ distance = dplane_ctx_get_distance(ctx);
+ tag = dplane_ctx_get_tag(ctx);
+ mtu = dplane_ctx_get_mtu(ctx);
+
+ afi = dplane_ctx_get_afi(ctx);
+
+ bh_type = dplane_ctx_get_route_bhtype(ctx);
+
+ if (IS_ZEBRA_DEBUG_KERNEL) {
+ char buf2[PREFIX_STRLEN];
+
+ zlog_debug(
"%s %pFX%s%s vrf %s(%u) table_id: %u metric: %d Admin Distance: %d",
nl_msg_type_to_str(h->nlmsg_type), &p,
src_p.prefixlen ? " from " : "",
@@ -965,10 +1212,6 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
distance);
}
- afi_t afi = AFI_IP;
- if (rtm->rtm_family == AF_INET6)
- afi = AFI_IP6;
-
if (h->nlmsg_type == RTM_NEWROUTE) {
struct route_entry *re;
struct nexthop_group *ng = NULL;
@@ -1018,12 +1261,11 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
}
}
if (nhe_id || ng) {
- dplane_rib_add_multipath(afi, SAFI_UNICAST, &p, &src_p,
- re, ng, startup, ctx);
+ rib_add_multipath(afi, SAFI_UNICAST, &p,
+ (struct prefix_ipv6 *)&src_p,
+ re, ng, startup);
if (ng)
nexthop_group_delete(&ng);
- if (ctx)
- zebra_rib_route_entry_free(re);
} else {
/*
* I really don't see how this is possible
@@ -1038,17 +1280,10 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
zebra_rib_route_entry_free(re);
}
} else {
- if (ctx) {
- zlog_err(
- "%s: %pFX RTM_DELROUTE received but received a context as well",
- __func__, &p);
- return 0;
- }
-
if (nhe_id) {
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0, flags,
- &p, &src_p, NULL, nhe_id, table, metric,
- distance, true);
+ &p, (struct prefix_ipv6 *)&src_p, NULL,
+ nhe_id, table, metric, distance, true);
} else {
if (!tb[RTA_MULTIPATH]) {
struct nexthop nh;
@@ -1057,26 +1292,33 @@ int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
ns_id, rtm, tb, bh_type, index, prefsrc,
gate, afi, vrf_id);
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, &nh, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, &nh, 0,
+ table, metric, distance, true);
} else {
/* XXX: need to compare the entire list of
* nexthops here for NLM_F_APPEND stupidity */
rib_delete(afi, SAFI_UNICAST, vrf_id, proto, 0,
- flags, &p, &src_p, NULL, 0, table,
- metric, distance, true);
+ flags, &p,
+ (struct prefix_ipv6 *)&src_p, NULL, 0,
+ table, metric, distance, true);
}
}
}
- return 1;
+ ret = 1;
+
+done:
+ if (ctx)
+ dplane_ctx_fini(&ctx);
+
+ return ret;
}
static int netlink_route_change_read_unicast(struct nlmsghdr *h, ns_id_t ns_id,
int startup)
{
- return netlink_route_change_read_unicast_internal(h, ns_id, startup,
- NULL);
+ return netlink_route_change_read_unicast_internal(h, ns_id, startup);
}
static struct mcast_route_data *mroute = NULL;
@@ -1615,13 +1857,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
{
char label_buf[256];
- struct vrf *vrf;
char addrstr[INET6_ADDRSTRLEN];
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1782,10 +2021,10 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf,
label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
return true;
}
@@ -1808,10 +2047,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET, &nexthop->gate.ipv4, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1832,10 +2070,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL) {
inet_ntop(AF_INET6, &nexthop->gate.ipv6, addrstr,
sizeof(addrstr));
- zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, addrstr, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
}
}
@@ -1857,9 +2094,9 @@ static bool _netlink_route_build_singlepath(const struct prefix *p,
}
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
return true;
@@ -1943,7 +2180,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
route_tag_t tag, bool fpm)
{
char label_buf[256];
- struct vrf *vrf;
struct rtnexthop *rtnh;
rtnh = nl_attr_rtnh(nlmsg, req_size);
@@ -1952,8 +2188,6 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
assert(nexthop);
- vrf = vrf_lookup_by_id(nexthop->vrf_id);
-
if (!_netlink_route_encode_label_info(nexthop, nlmsg, req_size, rtmsg,
label_buf, sizeof(label_buf)))
return false;
@@ -1976,10 +2210,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
if (IS_ZEBRA_DEBUG_KERNEL)
zlog_debug(
- "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %s(%u)",
+ "%s: 5549 (%s): %pFX nexthop via %s %s if %u vrf %u",
__func__, routedesc, p, ipv4_ll_buf, label_buf,
- nexthop->ifindex, VRF_LOGNAME(vrf),
- nexthop->vrf_id);
+ nexthop->ifindex, nexthop->vrf_id);
nl_attr_rtnh_end(nlmsg, rtnh);
return true;
}
@@ -1997,10 +2230,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI4 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv4,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
if (nexthop->type == NEXTHOP_TYPE_IPV6
|| nexthop->type == NEXTHOP_TYPE_IPV6_IFINDEX) {
@@ -2015,10 +2247,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via %pI6 %s if %u vrf %u",
__func__, routedesc, p, &nexthop->gate.ipv6,
- label_buf, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ label_buf, nexthop->ifindex, nexthop->vrf_id);
}
/*
@@ -2037,9 +2268,9 @@ static bool _netlink_route_build_multipath(const struct prefix *p,
*src = &nexthop->src;
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %s(%u)",
+ zlog_debug("%s: (%s): %pFX nexthop via if %u vrf %u",
__func__, routedesc, p, nexthop->ifindex,
- VRF_LOGNAME(vrf), nexthop->vrf_id);
+ nexthop->vrf_id);
}
if (nexthop->weight)
@@ -3057,9 +3288,8 @@ ssize_t netlink_nexthop_msg_encode(uint16_t cmd,
nexthop_done:
if (IS_ZEBRA_DEBUG_KERNEL)
- zlog_debug("%s: ID (%u): %pNHv(%d) vrf %s(%u) %s ",
+ zlog_debug("%s: ID (%u): %pNHv(%d) vrf %u %s ",
__func__, id, nh, nh->ifindex,
- vrf_id_to_name(nh->vrf_id),
nh->vrf_id, label_buf);
}
diff --git a/zebra/rt_netlink.h b/zebra/rt_netlink.h
index d51944f1a..1c113baee 100644
--- a/zebra/rt_netlink.h
+++ b/zebra/rt_netlink.h
@@ -64,6 +64,15 @@ extern ssize_t netlink_macfdb_update_ctx(struct zebra_dplane_ctx *ctx,
extern int netlink_route_change(struct nlmsghdr *h, ns_id_t ns_id, int startup);
extern int netlink_route_read(struct zebra_ns *zns);
+/*
+ * Public api for parsing a route notification message: this notification
+ * only parses the top-level route attributes, and doesn't include nexthops.
+ * FPM, for example, is a user.
+ * Returns <0 if the message should be ignored/skipped.
+ */
+int netlink_route_notify_read_ctx(struct nlmsghdr *h, ns_id_t ns_id,
+ struct zebra_dplane_ctx *ctx);
+
extern int netlink_nexthop_change(struct nlmsghdr *h, ns_id_t ns_id,
int startup);
extern int netlink_nexthop_read(struct zebra_ns *zns);
@@ -109,10 +118,6 @@ netlink_put_lsp_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
extern enum netlink_msg_status
netlink_put_pw_update_msg(struct nl_batch *bth, struct zebra_dplane_ctx *ctx);
-int netlink_route_change_read_unicast_internal(struct nlmsghdr *h,
- ns_id_t ns_id, int startup,
- struct zebra_dplane_ctx *ctx);
-
#ifdef NETLINK_DEBUG
const char *nlmsg_type2str(uint16_t type);
const char *af_type2str(int type);
diff --git a/zebra/zebra_dplane.c b/zebra/zebra_dplane.c
index 88c1a0493..b57c93015 100644
--- a/zebra/zebra_dplane.c
+++ b/zebra/zebra_dplane.c
@@ -150,6 +150,11 @@ struct dplane_route_info {
/* Optional list of extra interface info */
struct dplane_intf_extra_list_head intf_extra_list;
+
+ /* Route-level info that aligns with some netlink route data */
+ enum blackhole_type zd_bh_type;
+ struct ipaddr zd_prefsrc;
+ struct ipaddr zd_gateway;
};
/*
@@ -1906,6 +1911,12 @@ void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags)
ctx->u.rinfo.zd_flags = flags;
}
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_metric = metric;
+}
+
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1927,6 +1938,12 @@ uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_mtu;
}
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_mtu = mtu;
+}
+
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -1955,6 +1972,58 @@ uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_old_distance;
}
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return ctx->u.rinfo.zd_bh_type;
+}
+
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_bh_type = bhtype;
+}
+
+/* IP 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+
+ if (ctx->u.rinfo.zd_prefsrc.ipa_type != 0)
+ return &(ctx->u.rinfo.zd_prefsrc);
+ else
+ return NULL;
+}
+
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (addr)
+ ctx->u.rinfo.zd_prefsrc = *addr;
+ else
+ memset(&ctx->u.rinfo.zd_prefsrc, 0,
+ sizeof(ctx->u.rinfo.zd_prefsrc));
+}
+
+/* Route-level 'gateway' */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx)
+{
+ DPLANE_CTX_VALID(ctx);
+ return &(ctx->u.rinfo.zd_gateway);
+}
+
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw)
+{
+ DPLANE_CTX_VALID(ctx);
+ if (gw)
+ ctx->u.rinfo.zd_gateway = *gw;
+ else
+ memset(&ctx->u.rinfo.zd_gateway, 0, sizeof(ctx->u.rinfo.zd_gateway));
+}
+
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx)
{
DPLANE_CTX_VALID(ctx);
@@ -2179,6 +2248,12 @@ uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx)
return ctx->u.rinfo.zd_nhg_id;
}
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid)
+{
+ DPLANE_CTX_VALID(ctx);
+ ctx->u.rinfo.zd_nhg_id = nhgid;
+}
+
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx)
{
@@ -6923,20 +6998,6 @@ kernel_dplane_process_ipset_entry(struct zebra_dplane_provider *prov,
dplane_provider_enqueue_out_ctx(prov, ctx);
}
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx)
-{
- if (!ctx)
- rib_add_multipath(afi, safi, p, src_p, re, ng, startup);
- else {
- dplane_ctx_route_init_basic(ctx, dplane_ctx_get_op(ctx), re, p,
- src_p, afi, safi);
- dplane_provider_enqueue_to_zebra(ctx);
- }
-}
-
/*
* Kernel provider callback
*/
diff --git a/zebra/zebra_dplane.h b/zebra/zebra_dplane.h
index 285b00c9b..cabc70c23 100644
--- a/zebra/zebra_dplane.h
+++ b/zebra/zebra_dplane.h
@@ -524,11 +524,26 @@ uint32_t dplane_ctx_get_flags(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_flags(struct zebra_dplane_ctx *ctx, uint32_t flags);
uint32_t dplane_ctx_get_metric(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_old_metric(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_metric(struct zebra_dplane_ctx *ctx, uint32_t metric);
+void dplane_ctx_set_route_mtu(struct zebra_dplane_ctx *ctx, uint32_t mtu);
uint32_t dplane_ctx_get_mtu(const struct zebra_dplane_ctx *ctx);
uint32_t dplane_ctx_get_nh_mtu(const struct zebra_dplane_ctx *ctx);
uint8_t dplane_ctx_get_distance(const struct zebra_dplane_ctx *ctx);
void dplane_ctx_set_distance(struct zebra_dplane_ctx *ctx, uint8_t distance);
uint8_t dplane_ctx_get_old_distance(const struct zebra_dplane_ctx *ctx);
+/* Route blackhole type */
+enum blackhole_type dplane_ctx_get_route_bhtype(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_bhtype(struct zebra_dplane_ctx *ctx,
+ enum blackhole_type bhtype);
+/* IPv4 'preferred source', at route-level */
+const struct ipaddr *dplane_ctx_get_route_prefsrc(
+ const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_prefsrc(struct zebra_dplane_ctx *ctx,
+ const struct ipaddr *addr);
+/* Route 'gateway', at route-level */
+const struct ipaddr *dplane_ctx_get_route_gw(const struct zebra_dplane_ctx *ctx);
+void dplane_ctx_set_route_gw(struct zebra_dplane_ctx *ctx, const struct ipaddr *gw);
/* Accessors for traffic control context */
int dplane_ctx_tc_qdisc_get_kind(const struct zebra_dplane_ctx *ctx);
@@ -572,6 +587,7 @@ void dplane_ctx_set_nexthops(struct zebra_dplane_ctx *ctx, struct nexthop *nh);
void dplane_ctx_set_backup_nhg(struct zebra_dplane_ctx *ctx,
const struct nexthop_group *nhg);
+void dplane_ctx_set_nhg_id(struct zebra_dplane_ctx *ctx, uint32_t nhgid);
uint32_t dplane_ctx_get_nhg_id(const struct zebra_dplane_ctx *ctx);
const struct nexthop_group *dplane_ctx_get_ng(
const struct zebra_dplane_ctx *ctx);
@@ -1256,16 +1272,6 @@ void zebra_dplane_shutdown(void);
void zebra_dplane_startup_stage(struct zebra_ns *zns,
enum zebra_dplane_startup_notifications spot);
-/*
- * decision point for sending a routing update through the old
- * straight to zebra master pthread or through the dplane to
- * the master pthread for handling
- */
-void dplane_rib_add_multipath(afi_t afi, safi_t safi, struct prefix *p,
- struct prefix_ipv6 *src_p, struct route_entry *re,
- struct nexthop_group *ng, int startup,
- struct zebra_dplane_ctx *ctx);
-
enum zebra_dplane_startup_notifications
dplane_ctx_get_startup_spot(struct zebra_dplane_ctx *ctx);
diff --git a/zebra/zebra_rib.c b/zebra/zebra_rib.c
index 0226c355c..2881192eb 100644
--- a/zebra/zebra_rib.c
+++ b/zebra/zebra_rib.c
@@ -2220,8 +2220,20 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
bool fib_changed = false;
bool debug_p = IS_ZEBRA_DEBUG_DPLANE | IS_ZEBRA_DEBUG_RIB;
int start_count, end_count;
+ vrf_id_t vrf_id;
+ int tableid;
+
+ /* Locate vrf and route table - we must have one or the other */
+ tableid = dplane_ctx_get_table(ctx);
+ vrf_id = dplane_ctx_get_vrf(ctx);
+ if (vrf_id == VRF_UNKNOWN)
+ vrf_id = zebra_vrf_lookup_by_table(tableid,
+ dplane_ctx_get_ns_id(ctx));
+ else if (tableid == ZEBRA_ROUTE_TABLE_UNKNOWN)
+ tableid = zebra_vrf_lookup_tableid(vrf_id,
+ dplane_ctx_get_ns_id(ctx));
- vrf = vrf_lookup_by_id(dplane_ctx_get_vrf(ctx));
+ vrf = vrf_lookup_by_id(vrf_id);
/* Locate rn and re(s) from ctx */
rn = rib_find_rn_from_ctx(ctx);
@@ -2230,7 +2242,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"Failed to process dplane notification: no routes for %s(%u:%u):%pRN",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
goto done;
}
@@ -2240,7 +2252,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
if (debug_p)
zlog_debug("%s(%u:%u):%pRN Processing dplane notif ctx %p",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn, ctx);
+ tableid, rn, ctx);
/*
* Take a pass through the routes, look for matches with the context
@@ -2257,7 +2269,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN Unable to process dplane notification: no entry for type %s",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(dplane_ctx_get_type(ctx)));
goto done;
@@ -2293,7 +2305,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, uninstalled type %s route",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
} else {
@@ -2303,7 +2315,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN dplane notif, but type %s not selected_fib",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn,
+ tableid, rn,
zebra_route_string(
dplane_ctx_get_type(ctx)));
}
@@ -2342,7 +2354,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
zlog_debug(
"%s(%u:%u):%pRN dplane notification: rib_update returns FALSE",
VRF_LOGNAME(vrf), dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
}
/*
@@ -2361,7 +2373,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN applied nexthop changes from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Changed nexthops - update kernel/others */
dplane_route_notif_update(rn, re,
@@ -2373,7 +2385,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* We expect this to be the selected route, so we want
* to tell others about this transition.
@@ -2393,7 +2405,7 @@ static void rib_process_dplane_notify(struct zebra_dplane_ctx *ctx)
"%s(%u:%u):%pRN un-installed transition from dplane notification",
VRF_LOGNAME(vrf),
dplane_ctx_get_vrf(ctx),
- dplane_ctx_get_table(ctx), rn);
+ tableid, rn);
/* Transition from _something_ installed to _nothing_
* installed.
@@ -3973,10 +3985,10 @@ static void rib_link(struct route_node *rn, struct route_entry *re, int process)
dest = rib_dest_from_rnode(rn);
if (!dest) {
+ dest = zebra_rib_create_dest(rn);
+
if (IS_ZEBRA_DEBUG_RIB_DETAILED)
rnode_debug(rn, re->vrf_id, "rn %p adding dest", rn);
-
- dest = zebra_rib_create_dest(rn);
}
re_list_add_head(&dest->routes, re);
diff --git a/zebra/zebra_vrf.c b/zebra/zebra_vrf.c
index 2b3cfc876..c7781e86d 100644
--- a/zebra/zebra_vrf.c
+++ b/zebra/zebra_vrf.c
@@ -417,6 +417,25 @@ vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id)
return VRF_DEFAULT;
}
+/*
+ * Lookup tableid by vrfid; handle vrf-lite and vrf-netns cases
+ */
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id)
+{
+ struct zebra_vrf *zvrf;
+
+ /* Handle vrf-lite and vrf-netns */
+ if (vrf_is_backend_netns())
+ zvrf = vrf_info_lookup(ns_id);
+ else
+ zvrf = vrf_info_lookup(vrf_id);
+
+ if (zvrf)
+ return zvrf->table_id;
+ else
+ return ZEBRA_ROUTE_TABLE_UNKNOWN;
+}
+
/* Lookup VRF by identifier. */
struct zebra_vrf *zebra_vrf_lookup_by_id(vrf_id_t vrf_id)
{
diff --git a/zebra/zebra_vrf.h b/zebra/zebra_vrf.h
index f97138c81..334bb9368 100644
--- a/zebra/zebra_vrf.h
+++ b/zebra/zebra_vrf.h
@@ -24,6 +24,8 @@ FRR_CFG_DEFAULT_BOOL(ZEBRA_IP_NHT_RESOLVE_VIA_DEFAULT,
{ .val_bool = false },
);
+#define ZEBRA_ROUTE_TABLE_UNKNOWN 0
+
/* MPLS (Segment Routing) global block */
struct mpls_srgb {
uint32_t start_label;
@@ -247,6 +249,7 @@ extern struct zebra_vrf *zebra_vrf_lookup_by_name(const char *);
extern vrf_id_t zebra_vrf_lookup_by_table(uint32_t table_id, ns_id_t ns_id);
extern struct zebra_vrf *zebra_vrf_alloc(struct vrf *vrf);
extern struct route_table *zebra_vrf_table(afi_t, safi_t, vrf_id_t);
+int zebra_vrf_lookup_tableid(vrf_id_t vrf_id, ns_id_t ns_id);
/*
* API to associate a VRF with a NETNS.