diff options
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)); @@ -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 ? */ @@ -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 \ @@ -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); @@ -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. |