diff options
37 files changed, 265 insertions, 1758 deletions
diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 91bc3b1a88..985af84ea9 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -388,6 +388,9 @@ void vpn_leak_zebra_vrf_sid_update_per_af(struct bgp *bgp, afi_t afi) tovpn_sid_ls = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); *tovpn_sid_ls = *tovpn_sid; + if (bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent) + XFREE(MTYPE_BGP_SRV6_SID, + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent); bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent = tovpn_sid_ls; } @@ -435,6 +438,8 @@ void vpn_leak_zebra_vrf_sid_update_per_vrf(struct bgp *bgp) tovpn_sid_ls = XCALLOC(MTYPE_BGP_SRV6_SID, sizeof(struct in6_addr)); *tovpn_sid_ls = *tovpn_sid; + if (bgp->tovpn_zebra_vrf_sid_last_sent) + XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_zebra_vrf_sid_last_sent); bgp->tovpn_zebra_vrf_sid_last_sent = tovpn_sid_ls; } @@ -482,6 +487,7 @@ void vpn_leak_zebra_vrf_sid_withdraw_per_af(struct bgp *bgp, afi_t afi) bgp->vrf_id, ZEBRA_SEG6_LOCAL_ACTION_UNSPEC, NULL); XFREE(MTYPE_BGP_SRV6_SID, bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent); + bgp->vpn_policy[afi].tovpn_zebra_vrf_sid_last_sent = NULL; } /* @@ -508,6 +514,7 @@ void vpn_leak_zebra_vrf_sid_withdraw_per_vrf(struct bgp *bgp) bgp->vrf_id, ZEBRA_SEG6_LOCAL_ACTION_UNSPEC, NULL); XFREE(MTYPE_BGP_SRV6_SID, bgp->tovpn_zebra_vrf_sid_last_sent); + bgp->tovpn_zebra_vrf_sid_last_sent = NULL; } /* diff --git a/bgpd/bgp_nexthop.c b/bgpd/bgp_nexthop.c index d12dc22330..98eb9565bf 100644 --- a/bgpd/bgp_nexthop.c +++ b/bgpd/bgp_nexthop.c @@ -1003,6 +1003,8 @@ static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp, if (bnc->is_evpn_gwip_nexthop) json_object_boolean_true_add(json_nexthop, "isEvpnGatewayIp"); + json_object_string_addf(json, "resolvedPrefix", "%pFX", + &bnc->resolved_prefix); } else { vty_out(vty, " %s valid [IGP metric %d], #paths %d", buf, bnc->metric, bnc->path_count); @@ -1010,6 +1012,8 @@ static void bgp_show_nexthop(struct vty *vty, struct bgp *bgp, vty_out(vty, ", peer %s", peer->host); if (bnc->is_evpn_gwip_nexthop) vty_out(vty, " EVPN Gateway IP"); + vty_out(vty, "\n Resolved prefix %pFX", + &bnc->resolved_prefix); vty_out(vty, "\n"); } bgp_show_nexthops_detail(vty, bgp, bnc, json_nexthop); diff --git a/bgpd/bgp_nexthop.h b/bgpd/bgp_nexthop.h index 830883872e..430c8f17e8 100644 --- a/bgpd/bgp_nexthop.h +++ b/bgpd/bgp_nexthop.h @@ -90,6 +90,7 @@ struct bgp_nexthop_cache { struct bgp_nexthop_cache_head *tree; struct prefix prefix; + struct prefix resolved_prefix; void *nht_info; /* In BGP, peer session */ LIST_HEAD(path_list, bgp_path_info) paths; unsigned int path_count; diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c index 786d07c5a9..20a285b9d9 100644 --- a/bgpd/bgp_nht.c +++ b/bgpd/bgp_nht.c @@ -626,6 +626,8 @@ static void bgp_process_nexthop_update(struct bgp_nexthop_cache *bnc, } else if (nhr->nexthop_num) { struct peer *peer = bnc->nht_info; + prefix_copy(&bnc->resolved_prefix, &nhr->prefix); + /* notify bgp fsm if nbr ip goes from invalid->valid */ if (!bnc->nexthop_num) UNSET_FLAG(bnc->flags, BGP_NEXTHOP_PEER_NOTIFIED); @@ -731,6 +733,7 @@ static void bgp_process_nexthop_update(struct bgp_nexthop_cache *bnc, } } } else { + memset(&bnc->resolved_prefix, 0, sizeof(bnc->resolved_prefix)); bnc->flags &= ~BGP_NEXTHOP_EVPN_INCOMPLETE; bnc->flags &= ~BGP_NEXTHOP_VALID; bnc->flags &= ~BGP_NEXTHOP_LABELED_VALID; diff --git a/bgpd/bgp_open.c b/bgpd/bgp_open.c index 15738e673b..4037fd8aef 100644 --- a/bgpd/bgp_open.c +++ b/bgpd/bgp_open.c @@ -1975,7 +1975,7 @@ uint16_t bgp_open_capability(struct stream *s, struct peer *peer, } /* Dynamic capability. */ - if (CHECK_FLAG(peer->flags, PEER_FLAG_DYNAMIC_CAPABILITY)) { + if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) { SET_FLAG(peer->cap, PEER_CAP_DYNAMIC_ADV); stream_putc(s, BGP_OPEN_OPT_CAP); ext_opt_params diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index cd0d6def7d..0a1cf3362b 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -126,6 +126,10 @@ FRR_CFG_DEFAULT_BOOL(BGP_SOFT_VERSION_CAPABILITY, { .val_bool = true, .match_profile = "datacenter", }, { .val_bool = false }, ); +FRR_CFG_DEFAULT_BOOL(BGP_DYNAMIC_CAPABILITY, + { .val_bool = true, .match_profile = "datacenter", }, + { .val_bool = false }, +); FRR_CFG_DEFAULT_BOOL(BGP_ENFORCE_FIRST_AS, { .val_bool = false, .match_version = "< 9.1", }, { .val_bool = true }, @@ -623,6 +627,9 @@ int bgp_get_vty(struct bgp **bgp, as_t *as, const char *name, if (DFLT_BGP_SOFT_VERSION_CAPABILITY) SET_FLAG((*bgp)->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY); + if (DFLT_BGP_DYNAMIC_CAPABILITY) + SET_FLAG((*bgp)->flags, + BGP_FLAG_DYNAMIC_CAPABILITY); if (DFLT_BGP_ENFORCE_FIRST_AS) SET_FLAG((*bgp)->flags, BGP_FLAG_ENFORCE_FIRST_AS); @@ -4298,6 +4305,24 @@ DEFPY (bgp_default_software_version_capability, return CMD_SUCCESS; } +DEFPY (bgp_default_dynamic_capability, + bgp_default_dynamic_capability_cmd, + "[no] bgp default dynamic-capability", + NO_STR + BGP_STR + "Configure BGP defaults\n" + "Advertise dynamic capability for all neighbors\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + + if (no) + UNSET_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY); + else + SET_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY); + + return CMD_SUCCESS; +} + /* "bgp network import-check" configuration. */ DEFUN (bgp_network_import_check, bgp_network_import_check_cmd, @@ -18383,9 +18408,15 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp, vty_out(vty, " neighbor %s timers delayopen %u\n", addr, peer->bgp->default_delayopen); - /* capability dynamic */ - if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) - vty_out(vty, " neighbor %s capability dynamic\n", addr); + /* capability software-version */ + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY)) { + if (!peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) + vty_out(vty, " no neighbor %s capability dynamic\n", + addr); + } else { + if (peergroup_flag_check(peer, PEER_FLAG_DYNAMIC_CAPABILITY)) + vty_out(vty, " neighbor %s capability dynamic\n", addr); + } /* capability extended-nexthop */ if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_ENHE)) { @@ -19076,6 +19107,15 @@ int bgp_config_write(struct vty *vty) ? "" : "no "); + if (!!CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY) != + SAVE_BGP_DYNAMIC_CAPABILITY) + vty_out(vty, + " %sbgp default dynamic-capability\n", + CHECK_FLAG(bgp->flags, + BGP_FLAG_DYNAMIC_CAPABILITY) + ? "" + : "no "); + /* BGP default subgroup-pkt-queue-max. */ if (bgp->default_subgroup_pkt_queue_max != BGP_DEFAULT_SUBGROUP_PKT_QUEUE_MAX) @@ -20124,6 +20164,9 @@ void bgp_vty_init(void) /* bgp default software-version-capability */ install_element(BGP_NODE, &bgp_default_software_version_capability_cmd); + /* bgp default dynamic-capability */ + install_element(BGP_NODE, &bgp_default_dynamic_capability_cmd); + /* "bgp default subgroup-pkt-queue-max" commands. */ install_element(BGP_NODE, &bgp_default_subgroup_pkt_queue_max_cmd); install_element(BGP_NODE, &no_bgp_default_subgroup_pkt_queue_max_cmd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 5d6561abea..d98df754ef 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -1544,6 +1544,9 @@ struct peer *peer_new(struct bgp *bgp) if (CHECK_FLAG(bgp->flags, BGP_FLAG_SOFT_VERSION_CAPABILITY)) SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_SOFT_VERSION); + if (CHECK_FLAG(bgp->flags, BGP_FLAG_DYNAMIC_CAPABILITY)) + SET_FLAG(peer->flags, PEER_FLAG_DYNAMIC_CAPABILITY); + SET_FLAG(peer->flags_invert, PEER_FLAG_CAPABILITY_FQDN); SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_FQDN); @@ -2919,6 +2922,13 @@ static void peer_group2peer_config_copy(struct peer_group *group, SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_SOFT_VERSION); + /* capability dynamic apply */ + if (!CHECK_FLAG(peer->flags_override, + PEER_FLAG_DYNAMIC_CAPABILITY)) + if (CHECK_FLAG(conf->flags, PEER_FLAG_DYNAMIC_CAPABILITY)) + SET_FLAG(peer->flags, + PEER_FLAG_DYNAMIC_CAPABILITY); + /* password apply */ if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_PASSWORD)) PEER_STR_ATTR_INHERIT(peer, group, password, diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 1130a285fb..c0fefd53ba 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -529,6 +529,7 @@ struct bgp { #define BGP_FLAG_LU_IPV6_EXPLICIT_NULL (1ULL << 34) #define BGP_FLAG_SOFT_VERSION_CAPABILITY (1ULL << 35) #define BGP_FLAG_ENFORCE_FIRST_AS (1ULL << 36) +#define BGP_FLAG_DYNAMIC_CAPABILITY (1ULL << 37) /* BGP default address-families. * New peers inherit enabled afi/safis from bgp instance. diff --git a/configure.ac b/configure.ac index 9213bf0105..f11b345cf6 100644 --- a/configure.ac +++ b/configure.ac @@ -751,8 +751,6 @@ AC_ARG_ENABLE([snmp], AS_HELP_STRING([--enable-snmp], [enable SNMP support for agentx])) AC_ARG_ENABLE([config_rollbacks], AS_HELP_STRING([--enable-config-rollbacks], [enable configuration rollbacks (requires sqlite3)])) -AC_ARG_ENABLE([confd], - AS_HELP_STRING([--enable-confd=ARG], [enable confd integration])) AC_ARG_ENABLE([sysrepo], AS_HELP_STRING([--enable-sysrepo], [enable sysrepo integration])) AC_ARG_ENABLE([grpc], @@ -2082,22 +2080,6 @@ if test "$enable_config_rollbacks" = "yes"; then fi dnl --------------- -dnl confd -dnl --------------- -if test "$enable_confd" != "" -a "$enable_confd" != "no"; then - AC_CHECK_PROG([CONFD], [confd], [confd], [/bin/false], "${enable_confd}/bin") - if test "$CONFD" = "/bin/false"; then - AC_MSG_ERROR([confd was not found on your system.])] - fi - AC_CHECK_PROG([CONFDC], [confdc], [confdc], [/bin/false], "${enable_confd}/bin") - CONFD_CFLAGS="-I${enable_confd}/include -L${enable_confd}/lib" - AC_SUBST([CONFD_CFLAGS]) - CONFD_LIBS="-lconfd" - AC_SUBST([CONFD_LIBS]) - AC_DEFINE([HAVE_CONFD], [1], [Enable confd integration]) -fi - -dnl --------------- dnl sysrepo dnl --------------- if test "$enable_sysrepo" = "yes"; then @@ -2766,7 +2748,6 @@ AM_CONDITIONAL([ENABLE_BGP_VNC], [test "$enable_bgp_vnc" != "no"]) AM_CONDITIONAL([BGP_BMP], [$bgpd_bmp]) dnl northbound AM_CONDITIONAL([SQLITE3], [$SQLITE3]) -AM_CONDITIONAL([CONFD], [test "$enable_confd" != ""]) AM_CONDITIONAL([SYSREPO], [test "$enable_sysrepo" = "yes"]) AM_CONDITIONAL([GRPC], [test "$enable_grpc" = "yes"]) AM_CONDITIONAL([ZEROMQ], [test "$ZEROMQ" = "true"]) diff --git a/debian/changelog b/debian/changelog index b3b086e019..96c66db89b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,8 +1,14 @@ -frr (9.2~dev-1) UNRELEASED; urgency=medium +frr (10.1~dev-1) UNRELEASED; urgency=medium - * FRR Dev 9.2 + * FRR Dev 10.1 - -- Donatas Abraitis <donatas@opensourcerouting.org> Tue, 10 Oct 2023 12:00:00 -0600 + -- Jafar Al-Gharaibeh <jafar@atcorp.com> Tue, 26 Mar 2024 02:00:00 -0600 + +frr (10.0-0) unstable; urgency=medium + + * New upstream release FRR 10.0 + + -- Jafar Al-Gharaibeh <jafar@atcorp.com> Mon, 25 Mar 2024 02:00:00 -0600 frr (9.1-0) unstable; urgency=medium diff --git a/doc/developer/northbound/advanced-topics.rst b/doc/developer/northbound/advanced-topics.rst index edfc10bcbd..eb756026e0 100644 --- a/doc/developer/northbound/advanced-topics.rst +++ b/doc/developer/northbound/advanced-topics.rst @@ -12,8 +12,7 @@ Auto-generated CLI commands In order to have less code to maintain, it should be possible to write a tool that auto-generates CLI commands based on the FRR YANG models. As a matter of fact, there are already a number of NETCONF-based CLIs that do -exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__, -ConfD’s CLI). +exactly that (e.g. `Clixon <https://github.com/clicon/clixon>`__). The problem however is that there isn’t an exact one-to-one mapping between the existing CLI commands and the corresponding YANG nodes from @@ -27,11 +26,6 @@ command for each YANG leaf, (leaf-)list and presence-container. The ripd’s ``timers basic`` command, for instance, would become three different commands, which would be undesirable. - This Tail-f’s® - `document <http://info.tail-f.com/hubfs/Whitepapers/Tail-f_ConfD-CLI__Cfg_Mode_App_Note_Rev%20C.pdf>`__ - shows how to customize ConfD auto-generated CLI commands using YANG - annotations. - The good news is that *libyang* allows users to create plugins to implement their own YANG extensions, which can be used to implement CLI annotations. If done properly, a CLI generator can save FRR developers @@ -76,8 +70,8 @@ Example of how this feature could be provided in the CLI: ``commit confirmed [minutes <1-60>]``. The ability to do confirmed commits should also be exposed in the northbound API so that the northbound plugins can also take advantage of it (in the case of the -Sysrepo and ConfD plugins, confirmed commits are implemented externally -in the *netopeer2-server* and *confd* daemons, respectively). +Sysrepo plugin, confirmed commit is implemented externally in the +*netopeer2-server* daemon). Proposed feature: enable/disable configuration commands/sections ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/doc/developer/northbound/architecture.rst b/doc/developer/northbound/architecture.rst index 5fd89c32f1..4e84f1d6a4 100644 --- a/doc/developer/northbound/architecture.rst +++ b/doc/developer/northbound/architecture.rst @@ -116,7 +116,7 @@ multitude of different management APIs, all of them connected to the northbound layer of the FRR daemons. By default, only the CLI interface is compiled built-in in the FRR daemons. The other management interfaces are provided as optional plugins and need to be loaded during the daemon -initialization (e.g. *zebra -M confd*). This design makes it possible to +initialization (e.g. *zebra -M grpc*). This design makes it possible to integrate FRR with different NETCONF solutions without introducing vendor lock-in. The [[Plugins - Writing Your Own]] page explains how to write custom northbound plugins that can be tailored to all needs diff --git a/doc/developer/northbound/demos.rst b/doc/developer/northbound/demos.rst index 8a0f6ad6b2..7c5ae0c229 100644 --- a/doc/developer/northbound/demos.rst +++ b/doc/developer/northbound/demos.rst @@ -8,23 +8,3 @@ This short demo shows some of the capabilities of the new transactional CLI: |asciicast1| - -ConfD + NETCONF + Cisco YDK ---------------------------- - -This is a very simple demo of *ripd* being configured by a python -script. The script uses NETCONF to communicate with *ripd*, which has -the ConfD plugin loaded. The most interesting part, however, is the fact -that the python script is not using handcrafted XML payloads to -configure *ripd*. Instead, the script is using python bindings generated -using Cisco’s YANG Development Kit (YDK). - -- Script used in the demo: - https://gist.github.com/rwestphal/defa9bd1ccf216ab082d4711ae402f95 - -|asciicast2| - -.. |asciicast1| image:: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1.png - :target: https://asciinema.org/a/jL0BS5HfP2kS6N1HfgsZvfZk1 -.. |asciicast2| image:: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv.png - :target: https://asciinema.org/a/VfMElNxsjLcdvV7484E6ChxWv diff --git a/doc/developer/northbound/links.rst b/doc/developer/northbound/links.rst index 6cec176f8b..e8fb327238 100644 --- a/doc/developer/northbound/links.rst +++ b/doc/developer/northbound/links.rst @@ -195,14 +195,6 @@ pyangbind - GitHub page: https://github.com/robshakir/pyangbind - Documentation: http://pynms.io/pyangbind/ -ConfD -^^^^^ - -- Official webpage (for ConfD Basic): - http://www.tail-f.com/confd-basic/ -- Training Videos: http://www.tail-f.com/confd-training-videos/ -- Forum: http://discuss.tail-f.com/ - Sysrepo ^^^^^^^ diff --git a/doc/developer/northbound/operational-data-rpcs-and-notifications.rst b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst index 5cb70ca6f1..07f92c2ca0 100644 --- a/doc/developer/northbound/operational-data-rpcs-and-notifications.rst +++ b/doc/developer/northbound/operational-data-rpcs-and-notifications.rst @@ -10,10 +10,10 @@ Operational data ~~~~~~~~~~~~~~~~ Writing API-agnostic code for YANG-modeled operational data is -challenging. ConfD and Sysrepo, for instance, have completely different -APIs to fetch operational data. So how can we write API-agnostic -callbacks that can be used by both the ConfD and Sysrepo plugins, and -any other northbound client that might be written in the future? +challenging. Sysrepo, for instance, has completely different API to +fetch operational data. So how can we write API-agnostic callbacks +that can be used by both the Sysrepo plugin, and any other northbound +client that might be written in the future? As an additional requirement, the callbacks must be designed in a way that makes in-place XPath filtering possible. As an example, a @@ -94,27 +94,18 @@ in the northbound architecture: */ void *(*lookup_entry)(struct yang_list_keys *keys); -These callbacks were designed to provide maximum flexibility, and borrow -a lot of ideas from the ConfD API. Each callback does one and only one -task, they are indivisible primitives that can be combined in several -different ways to iterate over operational data. The extra flexibility -certainly has a performance cost, but it’s the price to pay if we want -to expose FRR operational data using several different management -interfaces (e.g. NETCONF via either ConfD or Sysrepo+Netopeer2). In the +These callbacks were designed to provide maximum flexibility. Each +callback does one and only one task, they are indivisible primitives +that can be combined in several different ways to iterate over operational +data. The extra flexibility certainly has a performance cost, but it’s the +price to pay if we want to expose FRR operational data using several +different management interfaces (e.g. Sysrepo+Netopeer2). In the future it might be possible to introduce optional callbacks that do things like returning multiple objects at once. They would provide enhanced performance when iterating over large lists, but their use would be limited by the northbound plugins that can be integrated with them. - NOTE: using the northbound callbacks as a base, the ConfD plugin can - provide up to 100 objects between each round trip between FRR and the - *confd* daemon. Preliminary tests showed FRR taking ~7 seconds - (asynchronously, without blocking the main pthread) to return a RIP - table containing 100k routes to a NETCONF client connected to *confd* - (JSON was used as the encoding format). Work needs to be done to find - the bottlenecks and optimize this operation. - The [[Plugins - Writing Your Own]] page explains how the northbound plugins can fetch operational data using the aforementioned northbound callbacks, and how in-place XPath filtering can be implemented. @@ -351,10 +342,10 @@ are being iterated over. If that is not done, the list entry returned by this callback can become a dangling pointer when used in another callback. -Currently the ConfD and Sysrepo plugins run only in the main pthread. -The plan in the short-term is to introduce a separate pthread only for -handling operational data, and use the main pthread only for handling -configuration changes, RPCs and notifications. +Currently the Sysrepo plugin runs only in the main pthread. The plan in the +short-term is to introduce a separate pthread only for handling operational +data, and use the main pthread only for handling configuration changes, +RPCs and notifications. RPCs and Actions ~~~~~~~~~~~~~~~~ @@ -396,8 +387,8 @@ some EXEC-level commands using YANG so that their functionality is exposed to other management interfaces other than the CLI. As an example, if the ``clear bgp`` command is modeled using a YANG RPC, and a corresponding ``rpc`` callback is written, then it should be possible to -clear BGP neighbors using NETCONF and RESTCONF with that RPC (the ConfD -and Sysrepo plugins have full support for YANG RPCs and actions). +clear BGP neighbors using NETCONF and RESTCONF with that RPC (the Sysrepo +plugin has full support for YANG RPCs and actions). Here’s an example of a very simple RPC modeled using YANG: @@ -568,8 +559,7 @@ Now sending the *authentication-failure* YANG notification should be as simple as calling the above function and provide the appropriate interface name. The notification will be processed by all northbound plugins that subscribed a callback to the ``nb_notification_send`` hook. -The ConfD and Sysrepo plugins, for instance, use this hook to relay the -notifications to the *confd*/*sysrepod* daemons, which can generate -NETCONF notifications to subscribed clients. When no northbound plugin -is loaded, ``nb_notification_send()`` doesn’t do anything and the -notifications are ignored. +The Sysrepo plugin, for instance, uses this hook to relay the notifications +to the *sysrepod* daemon, which can generate NETCONF notifications to subscribed +clients. When no northbound plugin is loaded, ``nb_notification_send()`` doesn’t +do anything and the notifications are ignored. diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index 9ae9508b02..3b8591fd52 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -1816,7 +1816,7 @@ Configuring Peers This includes changing graceful-restart (LLGR also) timers, enabling/disabling add-path, and other supported capabilities. -.. clicmd:: neighbor PEER capability fqdn +.. clicmd:: neighbor PEER capability fqdn Allow BGP to negotiate the FQDN Capability with its peers. @@ -1825,7 +1825,7 @@ Configuring Peers This capability is activated by default. The ``no neighbor PEER capability fqdn`` avoid negotiation of that capability. This is useful for peers who - are not supporting this capability or supporting BGP Capabilities + are not supporting this capability or supporting BGP Capabilities Negotiation RFC 2842. .. clicmd:: neighbor <A.B.C.D|X:X::X:X|WORD> accept-own @@ -1949,6 +1949,13 @@ Configuring Peers outputs. It's easier to troubleshoot if you have a number of BGP peers and a number of routes to check. +.. clicmd:: bgp default dynamic-capability + + This command enables dynamic capability advertisement by default + for all the neighbors. + + For ``datacenter`` profile, this is enabled by default. + .. clicmd:: bgp default software-version-capability This command enables software version capability advertisement by default @@ -3219,7 +3226,7 @@ that the 2001:db8:2:2:: prefix is valid. .. code-block:: frr - r1# show bgp nexthop detail + r1# show bgp nexthop detail Current BGP nexthop cache: 2001:db8:2:2:: valid [IGP metric 0], #paths 4 gate 2001:db8:12::2, if eth0 diff --git a/doc/user/installation.rst b/doc/user/installation.rst index f07bade52c..d17112d8aa 100644 --- a/doc/user/installation.rst +++ b/doc/user/installation.rst @@ -326,11 +326,6 @@ options from the list below. Build with configuration rollback support. Requires SQLite3. -.. option:: --enable-confd=<dir> - - Build the ConfD northbound plugin. Look for the libconfd libs and headers - in `dir`. - .. option:: --enable-sysrepo Build the Sysrepo northbound plugin. diff --git a/doc/user/isisd.rst b/doc/user/isisd.rst index d442faf0c1..23495b3754 100644 --- a/doc/user/isisd.rst +++ b/doc/user/isisd.rst @@ -123,7 +123,7 @@ ISIS Timer Set LSP refresh interval in seconds, globally, for an area (level-1) or a domain (level-2). -.. clicmd:: max-lsp-lifetime [level-1 | level-2] (360-65535) +.. clicmd:: max-lsp-lifetime [level-1 | level-2] (350-65535) Set LSP maximum LSP lifetime in seconds, globally, for an area (level-1) or a domain (level-2). diff --git a/lib/lib_errors.c b/lib/lib_errors.c index a96fac9cd4..9d6c04325c 100644 --- a/lib/lib_errors.c +++ b/lib/lib_errors.c @@ -308,24 +308,6 @@ static struct log_ref ferr_lib_err[] = { .suggestion = "Check if the FRR libyang plugins were installed correctly in the system", }, { - .code = EC_LIB_CONFD_INIT, - .title = "ConfD initialization error", - .description = "Upon startup FRR failed to properly initialize and startup the ConfD northbound plugin", - .suggestion = "Check if ConfD is installed correctly in the system. Also, check if the confd daemon is running.", - }, - { - .code = EC_LIB_CONFD_DATA_CONVERT, - .title = "ConfD data conversion error", - .description = "An error has occurred while converting a ConfD data value (binary) to a string", - .suggestion = "Open an Issue with all relevant log files and restart FRR" - }, - { - .code = EC_LIB_LIBCONFD, - .title = "libconfd error", - .description = "The northbound subsystem has detected that the libconfd library returned an error", - .suggestion = "Open an Issue with all relevant log files and restart FRR" - }, - { .code = EC_LIB_SYSREPO_INIT, .title = "Sysrepo initialization error", .description = "Upon startup FRR failed to properly initialize and startup the Sysrepo northbound plugin", diff --git a/lib/lib_errors.h b/lib/lib_errors.h index 8cdfb166c7..9e0d539599 100644 --- a/lib/lib_errors.h +++ b/lib/lib_errors.h @@ -65,9 +65,6 @@ enum lib_log_refs { EC_LIB_NB_TRANSACTION_RECORD_FAILED, EC_LIB_LIBYANG, EC_LIB_LIBYANG_PLUGIN_LOAD, - EC_LIB_CONFD_INIT, - EC_LIB_CONFD_DATA_CONVERT, - EC_LIB_LIBCONFD, EC_LIB_SYSREPO_INIT, EC_LIB_SYSREPO_DATA_CONVERT, EC_LIB_LIBSYSREPO, diff --git a/lib/northbound.c b/lib/northbound.c index 487f225913..11dca732ba 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -2403,8 +2403,6 @@ const char *nb_client_name(enum nb_client client) switch (client) { case NB_CLIENT_CLI: return "CLI"; - case NB_CLIENT_CONFD: - return "ConfD"; case NB_CLIENT_SYSREPO: return "Sysrepo"; case NB_CLIENT_GRPC: diff --git a/lib/northbound.h b/lib/northbound.h index 5be111cf0a..01e23c8160 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -621,11 +621,6 @@ struct nb_node { /* Flags. */ uint8_t flags; - -#ifdef HAVE_CONFD - /* ConfD hash value corresponding to this YANG path. */ - int confd_hash; -#endif }; /* The YANG container or list contains only config data. */ #define F_NB_NODE_CONFIG_ONLY 0x01 @@ -700,7 +695,6 @@ enum nb_error { enum nb_client { NB_CLIENT_NONE = 0, NB_CLIENT_CLI, - NB_CLIENT_CONFD, NB_CLIENT_SYSREPO, NB_CLIENT_GRPC, NB_CLIENT_PCEP, diff --git a/lib/northbound_confd.c b/lib/northbound_confd.c deleted file mode 100644 index 8503d18002..0000000000 --- a/lib/northbound_confd.c +++ /dev/null @@ -1,1494 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -/* - * Copyright (C) 2018 NetDEF, Inc. - * Renato Westphal - */ - -#include <zebra.h> - -#include "log.h" -#include "lib_errors.h" -#include "command.h" -#include "debug.h" -#include "libfrr.h" -#include "lib/version.h" -#include "northbound.h" - -#include <confd_lib.h> -#include <confd_cdb.h> -#include <confd_dp.h> -#include <confd_maapi.h> - -DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module"); - -static struct debug nb_dbg_client_confd = {0, "Northbound client: ConfD"}; - -static struct event_loop *master; -static struct sockaddr confd_addr; -static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock; -static struct event *t_cdb_sub, *t_dp_ctl, *t_dp_worker; -static struct confd_daemon_ctx *dctx; -static struct confd_notification_ctx *live_ctx; -static bool confd_connected; -static struct list *confd_spoints; -static struct nb_transaction *transaction; - -static void frr_confd_finish_cdb(void); -static void frr_confd_finish_dp(void); -static int frr_confd_finish(void); - -#define flog_err_confd(funcname) \ - flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \ - (funcname), confd_strerror(confd_errno), confd_errno, \ - confd_lasterr()) - - -/* ------------ Utils ------------ */ - -/* Get XPath string from ConfD hashed keypath. */ -static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath, - size_t len) -{ - char *p; - - confd_xpath_pp_kpath(xpath, len, 0, kp); - - /* - * Replace double quotes by single quotes (the format accepted by the - * northbound API). - */ - p = xpath; - while ((p = strchr(p, '"')) != NULL) - *p++ = '\''; -} - -/* Convert ConfD binary value to a string. */ -static int frr_confd_val2str(const char *xpath, const confd_value_t *value, - char *string, size_t string_size) -{ - struct confd_cs_node *csp; - - csp = confd_cs_node_cd(NULL, xpath); - if (!csp) { - flog_err_confd("confd_cs_node_cd"); - return -1; - } - if (confd_val2str(csp->info.type, value, string, string_size) - == CONFD_ERR) { - flog_err_confd("confd_val2str"); - return -1; - } - - return 0; -} - -/* Obtain list entry from ConfD hashed keypath. */ -static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp, - struct nb_node *nb_node, - const void **list_entry) -{ - struct nb_node *nb_node_list; - int parent_lists = 0; - int curr_list = 0; - - *list_entry = NULL; - - /* - * Count the number of YANG lists in the path, disconsidering the - * last element. - */ - nb_node_list = nb_node; - while (nb_node_list->parent_list) { - nb_node_list = nb_node_list->parent_list; - parent_lists++; - } - if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0) - return 0; - - /* Start from the beginning and move down the tree. */ - for (int i = kp->len; i >= 0; i--) { - struct yang_list_keys keys; - - /* Not a YANG list. */ - if (kp->v[i][0].type != C_BUF) - continue; - - /* Obtain list keys. */ - memset(&keys, 0, sizeof(keys)); - for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) { - strlcpy(keys.key[keys.num], - (char *)kp->v[i][j].val.buf.ptr, - sizeof(keys.key[keys.num])); - keys.num++; - } - - /* Obtain northbound node associated to the YANG list. */ - nb_node_list = nb_node; - for (int j = curr_list; j < parent_lists; j++) - nb_node_list = nb_node_list->parent_list; - - /* Obtain list entry. */ - if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) { - *list_entry = nb_callback_lookup_entry( - nb_node, *list_entry, &keys); - if (*list_entry == NULL) - return -1; - } else { - unsigned long ptr_ulong; - - /* Retrieve list entry from pseudo-key (string). */ - if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1) - return -1; - *list_entry = (const void *)ptr_ulong; - } - - curr_list++; - } - - return 0; -} - -/* Fill the current date and time into a confd_datetime structure. */ -static void getdatetime(struct confd_datetime *datetime) -{ - struct tm tm; - struct timeval tv; - - gettimeofday(&tv, NULL); - gmtime_r(&tv.tv_sec, &tm); - - memset(datetime, 0, sizeof(*datetime)); - datetime->year = 1900 + tm.tm_year; - datetime->month = tm.tm_mon + 1; - datetime->day = tm.tm_mday; - datetime->sec = tm.tm_sec; - datetime->micro = tv.tv_usec; - datetime->timezone = 0; - datetime->timezone_minutes = 0; - datetime->hour = tm.tm_hour; - datetime->min = tm.tm_min; -} - -/* ------------ CDB code ------------ */ - -struct cdb_iter_args { - struct nb_config *candidate; - bool error; -}; - -static enum cdb_iter_ret -frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op, - confd_value_t *oldv, confd_value_t *newv, void *args) -{ - char xpath[XPATH_MAXLEN]; - struct nb_node *nb_node; - enum nb_operation nb_op; - struct cdb_iter_args *iter_args = args; - char value_str[YANG_VALUE_MAXLEN]; - struct yang_data *data; - char *sb1, *sb2; - int ret; - - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - - /* - * HACK: obtain value of leaf-list elements from the XPath due to - * a bug in the ConfD API. - */ - value_str[0] = '\0'; - sb1 = strrchr(xpath, '['); - sb2 = strrchr(xpath, ']'); - if (sb1 && sb2 && !strchr(sb1, '=')) { - *sb2 = '\0'; - strlcpy(value_str, sb1 + 1, sizeof(value_str)); - *sb1 = '\0'; - } - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - iter_args->error = true; - return ITER_STOP; - } - - /* Map operation values. */ - switch (cdb_op) { - case MOP_CREATED: - nb_op = NB_OP_CREATE; - break; - case MOP_DELETED: - nb_op = NB_OP_DESTROY; - break; - case MOP_VALUE_SET: - if (nb_is_operation_allowed(nb_node, NB_OP_MODIFY)) - nb_op = NB_OP_MODIFY; - else - /* Ignore list keys modifications. */ - return ITER_RECURSE; - break; - case MOP_MOVED_AFTER: - nb_op = NB_OP_MOVE; - break; - case MOP_MODIFIED: - /* We're not interested on this. */ - return ITER_RECURSE; - default: - flog_err(EC_LIB_DEVELOPMENT, - "%s: unexpected operation %u [xpath %s]", __func__, - cdb_op, xpath); - iter_args->error = true; - return ITER_STOP; - } - - /* Convert ConfD value to a string. */ - if (nb_node->snode->nodetype != LYS_LEAFLIST && newv - && frr_confd_val2str(nb_node->xpath, newv, value_str, - sizeof(value_str)) - != 0) { - flog_err(EC_LIB_CONFD_DATA_CONVERT, - "%s: failed to convert ConfD value to a string", - __func__); - iter_args->error = true; - return ITER_STOP; - } - - /* Edit the candidate configuration. */ - data = yang_data_new(xpath, value_str); - ret = nb_candidate_edit(iter_args->candidate, nb_node, nb_op, xpath, - NULL, data); - yang_data_free(data); - if (ret != NB_OK) { - flog_warn( - EC_LIB_NB_CANDIDATE_EDIT_ERROR, - "%s: failed to edit candidate configuration: operation [%s] xpath [%s]", - __func__, nb_operation_name(nb_op), xpath); - iter_args->error = true; - return ITER_STOP; - } - - return ITER_RECURSE; -} - -static void frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen) -{ - struct nb_context context = {}; - struct nb_config *candidate; - struct cdb_iter_args iter_args; - char errmsg[BUFSIZ] = {0}; - int ret; - - candidate = nb_config_dup(running_config); - - /* Iterate over all configuration changes. */ - iter_args.candidate = candidate; - iter_args.error = false; - for (int i = 0; i < reslen; i++) { - if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter, - ITER_WANT_PREV, &iter_args) - != CONFD_OK) { - flog_err_confd("cdb_diff_iterate"); - } - } - free(subp); - - if (iter_args.error) { - nb_config_free(candidate); - - if (cdb_sub_abort_trans( - cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0, - 0, "Couldn't apply configuration changes") - != CONFD_OK) { - flog_err_confd("cdb_sub_abort_trans"); - return; - } - return; - } - - /* - * Validate the configuration changes and allocate all resources - * required to apply them. - */ - transaction = NULL; - context.client = NB_CLIENT_CONFD; - ret = nb_candidate_commit_prepare(context, candidate, NULL, - &transaction, false, false, errmsg, - sizeof(errmsg)); - if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { - enum confd_errcode errcode; - - switch (ret) { - case NB_ERR_LOCKED: - errcode = CONFD_ERRCODE_IN_USE; - break; - case NB_ERR_RESOURCE: - errcode = CONFD_ERRCODE_RESOURCE_DENIED; - break; - default: - errcode = CONFD_ERRCODE_APPLICATION; - break; - } - - /* Reject the configuration changes. */ - if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s", - errmsg) - != CONFD_OK) { - flog_err_confd("cdb_sub_abort_trans"); - return; - } - } else { - /* Acknowledge the notification. */ - if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) - != CONFD_OK) { - flog_err_confd("cdb_sync_subscription_socket"); - return; - } - - /* No configuration changes. */ - if (!transaction) - nb_config_free(candidate); - } -} - -static void frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen) -{ - /* - * No need to process the configuration changes again as we're already - * keeping track of them in the "transaction" variable. - */ - free(subp); - - /* Apply the transaction. */ - if (transaction) { - struct nb_config *candidate = transaction->config; - char errmsg[BUFSIZ] = {0}; - - nb_candidate_commit_apply(transaction, true, NULL, errmsg, - sizeof(errmsg)); - nb_config_free(candidate); - } - - /* Acknowledge the notification. */ - if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { - flog_err_confd("cdb_sync_subscription_socket"); - return; - } -} - -static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen) -{ - /* - * No need to process the configuration changes again as we're already - * keeping track of them in the "transaction" variable. - */ - free(subp); - - /* Abort the transaction. */ - if (transaction) { - struct nb_config *candidate = transaction->config; - char errmsg[BUFSIZ] = {0}; - - nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg)); - nb_config_free(candidate); - } - - /* Acknowledge the notification. */ - if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { - flog_err_confd("cdb_sync_subscription_socket"); - return -1; - } - - return 0; -} - -static void frr_confd_cdb_read_cb(struct event *thread) -{ - int fd = EVENT_FD(thread); - enum cdb_sub_notification cdb_ev; - int flags; - int *subp = NULL; - int reslen = 0; - - event_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &t_cdb_sub); - - if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen) - != CONFD_OK) { - flog_err_confd("cdb_read_subscription_socket2"); - return; - } - - switch (cdb_ev) { - case CDB_SUB_PREPARE: - frr_confd_cdb_read_cb_prepare(fd, subp, reslen); - break; - case CDB_SUB_COMMIT: - frr_confd_cdb_read_cb_commit(fd, subp, reslen); - break; - case CDB_SUB_ABORT: - frr_confd_cdb_read_cb_abort(fd, subp, reslen); - break; - default: - flog_err_confd("unknown CDB event"); - break; - } -} - -/* Trigger CDB subscriptions to read the startup configuration. */ -static void *thread_cdb_trigger_subscriptions(void *data) -{ - int sock; - int *sub_points = NULL, len = 0; - struct listnode *node; - int *spoint; - int i = 0; - - /* Create CDB data socket. */ - sock = socket(PF_INET, SOCK_STREAM, 0); - if (sock < 0) { - flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", - __func__, safe_strerror(errno)); - return NULL; - } - - if (cdb_connect(sock, CDB_DATA_SOCKET, &confd_addr, - sizeof(struct sockaddr_in)) - != CONFD_OK) { - flog_err_confd("cdb_connect"); - return NULL; - } - - /* - * Fill array containing the subscription point of all loaded YANG - * modules. - */ - len = listcount(confd_spoints); - sub_points = XCALLOC(MTYPE_CONFD, len * sizeof(int)); - for (ALL_LIST_ELEMENTS_RO(confd_spoints, node, spoint)) - sub_points[i++] = *spoint; - - if (cdb_trigger_subscriptions(sock, sub_points, len) != CONFD_OK) { - flog_err_confd("cdb_trigger_subscriptions"); - return NULL; - } - - /* Cleanup and exit thread. */ - XFREE(MTYPE_CONFD, sub_points); - cdb_close(sock); - - return NULL; -} - -static int frr_confd_subscribe(const struct lysc_node *snode, void *arg) -{ - struct yang_module *module = arg; - struct nb_node *nb_node; - int *spoint; - int ret; - - switch (snode->nodetype) { - case LYS_CONTAINER: - case LYS_LEAF: - case LYS_LEAFLIST: - case LYS_LIST: - break; - default: - return YANG_ITER_CONTINUE; - } - - if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) - return YANG_ITER_CONTINUE; - - nb_node = snode->priv; - if (!nb_node) - return YANG_ITER_CONTINUE; - - DEBUGD(&nb_dbg_client_confd, "%s: subscribing to '%s'", __func__, - nb_node->xpath); - - spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint)); - ret = cdb_subscribe2(cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE, - CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint, - module->confd_hash, nb_node->xpath); - if (ret != CONFD_OK) { - flog_err_confd("cdb_subscribe2"); - XFREE(MTYPE_CONFD, spoint); - return YANG_ITER_CONTINUE; - } - - listnode_add(confd_spoints, spoint); - return YANG_ITER_CONTINUE; -} - -static int frr_confd_init_cdb(void) -{ - struct yang_module *module; - pthread_t cdb_trigger_thread; - - /* Create CDB subscription socket. */ - cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0); - if (cdb_sub_sock < 0) { - flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", - __func__, safe_strerror(errno)); - return -1; - } - - if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr, - sizeof(struct sockaddr_in)) - != CONFD_OK) { - flog_err_confd("cdb_connect"); - goto error; - } - - /* Subscribe to all loaded YANG data modules. */ - confd_spoints = list_new(); - RB_FOREACH (module, yang_modules, &yang_modules) { - module->confd_hash = confd_str2hash(module->info->ns); - if (module->confd_hash == 0) { - flog_err( - EC_LIB_LIBCONFD, - "%s: failed to find hash value for namespace %s", - __func__, module->info->ns); - goto error; - } - - /* - * The CDB API doesn't provide a mechanism to subscribe to an - * entire YANG module. So we have to find the top level - * nodes ourselves and subscribe to their paths. - */ - yang_snodes_iterate(module->info, frr_confd_subscribe, 0, - module); - } - - if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) { - flog_err_confd("cdb_subscribe_done"); - goto error; - } - - /* Create short lived pthread to trigger the CDB subscriptions. */ - if (pthread_create(&cdb_trigger_thread, NULL, - thread_cdb_trigger_subscriptions, NULL)) { - flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s", - __func__, safe_strerror(errno)); - goto error; - } - pthread_detach(cdb_trigger_thread); - - event_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock, - &t_cdb_sub); - - return 0; - -error: - frr_confd_finish_cdb(); - - return -1; -} - -static void frr_confd_finish_cdb(void) -{ - if (cdb_sub_sock > 0) { - EVENT_OFF(t_cdb_sub); - cdb_close(cdb_sub_sock); - } -} - -/* ------------ DP code ------------ */ - -static int frr_confd_transaction_init(struct confd_trans_ctx *tctx) -{ - confd_trans_set_fd(tctx, dp_worker_sock); - - return CONFD_OK; -} - -#define CONFD_MAX_CHILD_NODES 32 - -static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx, - confd_hkeypath_t *kp) -{ - struct nb_node *nb_node; - char xpath[XPATH_MAXLEN]; - struct yang_data *data; - confd_value_t v; - const void *list_entry = NULL; - - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - confd_data_reply_not_found(tctx); - return CONFD_OK; - } - - if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { - confd_data_reply_not_found(tctx); - return CONFD_OK; - } - - data = nb_callback_get_elem(nb_node, xpath, list_entry); - if (data) { - if (data->value) { - CONFD_SET_STR(&v, data->value); - confd_data_reply_value(tctx, &v); - } else - confd_data_reply_found(tctx); - yang_data_free(data); - } else - confd_data_reply_not_found(tctx); - - return CONFD_OK; -} - -static int frr_confd_data_get_next(struct confd_trans_ctx *tctx, - confd_hkeypath_t *kp, long next) -{ - struct nb_node *nb_node; - char xpath[XPATH_MAXLEN]; - struct yang_data *data; - const void *parent_list_entry, *nb_next; - confd_value_t v[LIST_MAXKEYS]; - - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - confd_data_reply_next_key(tctx, NULL, -1, -1); - return CONFD_OK; - } - - if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) - != 0) { - /* List entry doesn't exist anymore. */ - confd_data_reply_next_key(tctx, NULL, -1, -1); - return CONFD_OK; - } - - nb_next = nb_callback_get_next(nb_node, parent_list_entry, - (next == -1) ? NULL : (void *)next); - if (!nb_next) { - /* End of the list or leaf-list. */ - confd_data_reply_next_key(tctx, NULL, -1, -1); - return CONFD_OK; - } - - switch (nb_node->snode->nodetype) { - case LYS_LIST: - if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { - struct yang_list_keys keys; - - memset(&keys, 0, sizeof(keys)); - if (nb_callback_get_keys(nb_node, nb_next, &keys) - != NB_OK) { - flog_warn(EC_LIB_NB_CB_STATE, - "%s: failed to get list keys", - __func__); - confd_data_reply_next_key(tctx, NULL, -1, -1); - return CONFD_OK; - } - - /* Feed keys to ConfD. */ - for (size_t i = 0; i < keys.num; i++) - CONFD_SET_STR(&v[i], keys.key[i]); - confd_data_reply_next_key(tctx, v, keys.num, - (long)nb_next); - } else { - char pointer_str[32]; - - /* - * ConfD 6.6 user guide, chapter 6.11 (Operational data - * lists without keys): - * "To support this without having completely separate - * APIs, we use a "pseudo" key in the ConfD APIs for - * this type of list. This key is not part of the data - * model, and completely hidden in the northbound agent - * interfaces, but is used with e.g. the get_next() and - * get_elem() callbacks as if it were a normal key. This - * "pseudo" key is always a single signed 64-bit - * integer, i.e. the confd_value_t type is C_INT64. The - * values can be chosen arbitrarily by the application, - * as long as a key value returned by get_next() can be - * used to get the data for the corresponding list entry - * with get_elem() or get_object() as usual. It could - * e.g. be an index into an array that holds the data, - * or even a memory address in integer form". - * - * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY - * option, we must convert our pseudo-key (a void - * pointer) to a string before sending it to confd. - */ - snprintf(pointer_str, sizeof(pointer_str), "%lu", - (unsigned long)nb_next); - CONFD_SET_STR(&v[0], pointer_str); - confd_data_reply_next_key(tctx, v, 1, (long)nb_next); - } - break; - case LYS_LEAFLIST: - data = nb_callback_get_elem(nb_node, xpath, nb_next); - if (data) { - if (data->value) { - CONFD_SET_STR(&v[0], data->value); - confd_data_reply_next_key(tctx, v, 1, - (long)nb_next); - } - yang_data_free(data); - } else - confd_data_reply_next_key(tctx, NULL, -1, -1); - break; - default: - break; - } - - return CONFD_OK; -} - -/* - * Optional callback - implemented for performance reasons. - */ -static int frr_confd_data_get_object(struct confd_trans_ctx *tctx, - confd_hkeypath_t *kp) -{ - struct nb_node *nb_node; - const struct lysc_node *child; - char xpath[XPATH_MAXLEN]; - char xpath_child[XPATH_MAXLEN * 2]; - struct list *elements; - struct yang_data *data; - const void *list_entry; - confd_value_t values[CONFD_MAX_CHILD_NODES]; - size_t nvalues = 0; - - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - confd_data_reply_not_found(tctx); - return CONFD_ERR; - } - - if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { - confd_data_reply_not_found(tctx); - return CONFD_OK; - } - - elements = yang_data_list_new(); - - /* Loop through list child nodes. */ - LY_LIST_FOR (lysc_node_child(nb_node->snode), child) { - struct nb_node *nb_node_child = child->priv; - confd_value_t *v; - - if (nvalues > CONFD_MAX_CHILD_NODES) - break; - - v = &values[nvalues++]; - - /* Non-presence containers, lists and leaf-lists. */ - if (!nb_node_child->cbs.get_elem) { - CONFD_SET_NOEXISTS(v); - continue; - } - - snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath, - child->name); - data = nb_callback_get_elem(nb_node_child, xpath_child, - list_entry); - if (data) { - if (data->value) - CONFD_SET_STR(v, data->value); - else { - /* Presence containers and empty leafs. */ - CONFD_SET_XMLTAG( - v, nb_node_child->confd_hash, - confd_str2hash(nb_node_child->snode - ->module->ns)); - } - listnode_add(elements, data); - } else - CONFD_SET_NOEXISTS(v); - } - - confd_data_reply_value_array(tctx, values, nvalues); - - /* Release memory. */ - list_delete(&elements); - - return CONFD_OK; -} - -/* - * Optional callback - implemented for performance reasons. - */ -static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx, - confd_hkeypath_t *kp, long next) -{ - char xpath[XPATH_MAXLEN]; - struct nb_node *nb_node; - struct list *elements; - const void *parent_list_entry; - const void *nb_next; -#define CONFD_OBJECTS_PER_TIME 100 - struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1]; - char pseudo_keys[CONFD_OBJECTS_PER_TIME][32]; - int nobjects = 0; - - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - confd_data_reply_next_object_array(tctx, NULL, 0, 0); - return CONFD_OK; - } - - if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) - != 0) { - confd_data_reply_next_object_array(tctx, NULL, 0, 0); - return CONFD_OK; - } - - elements = yang_data_list_new(); - nb_next = (next == -1) ? NULL : (void *)next; - - memset(objects, 0, sizeof(objects)); - for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) { - struct confd_next_object *object; - const struct lysc_node *child; - struct yang_data *data; - size_t nvalues = 0; - - object = &objects[j]; - - nb_next = nb_callback_get_next(nb_node, parent_list_entry, - nb_next); - if (!nb_next) - /* End of the list. */ - break; - - object->next = (long)nb_next; - - /* Leaf-lists require special handling. */ - if (nb_node->snode->nodetype == LYS_LEAFLIST) { - object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t)); - data = nb_callback_get_elem(nb_node, xpath, nb_next); - assert(data && data->value); - CONFD_SET_STR(object->v, data->value); - nvalues++; - listnode_add(elements, data); - goto next; - } - - object->v = - XMALLOC(MTYPE_CONFD, - CONFD_MAX_CHILD_NODES * sizeof(confd_value_t)); - - /* - * ConfD 6.6 user guide, chapter 6.11 (Operational data lists - * without keys): - * "In the response to the get_next_object() callback, the data - * provider is expected to provide the key values along with the - * other leafs in an array that is populated according to the - * data model. This must be done also for this type of list, - * even though the key isn't actually in the data model. The - * "pseudo" key must always be the first element in the array". - */ - if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { - confd_value_t *v; - - snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu", - (unsigned long)nb_next); - - v = &object->v[nvalues++]; - CONFD_SET_STR(v, pseudo_keys[j]); - } - - /* Loop through list child nodes. */ - LY_LIST_FOR (lysc_node_child(nb_node->snode), child) { - struct nb_node *nb_node_child = child->priv; - char xpath_child[XPATH_MAXLEN * 2]; - confd_value_t *v; - - if (nvalues > CONFD_MAX_CHILD_NODES) - break; - - v = &object->v[nvalues++]; - - /* Non-presence containers, lists and leaf-lists. */ - if (!nb_node_child->cbs.get_elem) { - CONFD_SET_NOEXISTS(v); - continue; - } - - snprintf(xpath_child, sizeof(xpath_child), "%s/%s", - xpath, child->name); - data = nb_callback_get_elem(nb_node_child, xpath_child, - nb_next); - if (data) { - if (data->value) - CONFD_SET_STR(v, data->value); - else { - /* - * Presence containers and empty leafs. - */ - CONFD_SET_XMLTAG( - v, nb_node_child->confd_hash, - confd_str2hash( - nb_node_child->snode - ->module->ns)); - } - listnode_add(elements, data); - } else - CONFD_SET_NOEXISTS(v); - } - next: - object->n = nvalues; - nobjects++; - } - - if (nobjects == 0) { - confd_data_reply_next_object_array(tctx, NULL, 0, 0); - list_delete(&elements); - return CONFD_OK; - } - - /* Detect end of the list. */ - if (!nb_next) { - nobjects++; - objects[nobjects].v = NULL; - } - - /* Reply to ConfD. */ - confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0); - if (!nb_next) - nobjects--; - - /* Release memory. */ - list_delete(&elements); - for (int j = 0; j < nobjects; j++) { - struct confd_next_object *object; - - object = &objects[j]; - XFREE(MTYPE_CONFD, object->v); - } - - return CONFD_OK; -} - -static int frr_confd_notification_send(const char *xpath, - struct list *arguments) -{ - struct nb_node *nb_node; - struct yang_module *module; - struct confd_datetime now; - confd_tag_value_t *values; - int nvalues; - int i = 0; - struct yang_data *data; - struct listnode *node; - int ret; - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - return -1; - } - module = yang_module_find(nb_node->snode->module->name); - assert(module); - - nvalues = 2; - if (arguments) - nvalues += listcount(arguments); - - values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values)); - - CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash, - module->confd_hash); - for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { - struct nb_node *nb_node_arg; - - nb_node_arg = nb_node_find(data->xpath); - if (!nb_node_arg) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, - data->xpath); - XFREE(MTYPE_CONFD, values); - return NB_ERR; - } - - CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash, - data->value); - } - CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash, - module->confd_hash); - - getdatetime(&now); - ret = confd_notification_send(live_ctx, &now, values, nvalues); - - /* Release memory. */ - XFREE(MTYPE_CONFD, values); - - /* Map ConfD return code to northbound return code. */ - switch (ret) { - case CONFD_OK: - return NB_OK; - default: - return NB_ERR; - } -} - -static int frr_confd_action_init(struct confd_user_info *uinfo) -{ - confd_action_set_fd(uinfo, dp_worker_sock); - - return CONFD_OK; -} - -static int frr_confd_action_execute(struct confd_user_info *uinfo, - struct xml_tag *name, confd_hkeypath_t *kp, - confd_tag_value_t *params, int nparams) -{ - char xpath[XPATH_MAXLEN]; - struct nb_node *nb_node; - struct list *input; - struct list *output; - struct yang_data *data; - confd_tag_value_t *reply; - int ret = CONFD_OK; - char errmsg[BUFSIZ] = {0}; - - /* Getting the XPath is tricky. */ - if (kp) { - /* This is a YANG RPC. */ - frr_confd_get_xpath(kp, xpath, sizeof(xpath)); - strlcat(xpath, "/", sizeof(xpath)); - strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath)); - } else { - /* This is a YANG action. */ - snprintf(xpath, sizeof(xpath), "/%s:%s", - confd_ns2prefix(name->ns), confd_hash2str(name->tag)); - } - - nb_node = nb_node_find(xpath); - if (!nb_node) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, xpath); - return CONFD_ERR; - } - - input = yang_data_list_new(); - output = yang_data_list_new(); - - /* Process input nodes. */ - for (int i = 0; i < nparams; i++) { - char xpath_input[XPATH_MAXLEN * 2]; - char value_str[YANG_VALUE_MAXLEN]; - - snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath, - confd_hash2str(params[i].tag.tag)); - - if (frr_confd_val2str(xpath_input, ¶ms[i].v, value_str, - sizeof(value_str)) - != 0) { - flog_err( - EC_LIB_CONFD_DATA_CONVERT, - "%s: failed to convert ConfD value to a string", - __func__); - ret = CONFD_ERR; - goto exit; - } - - data = yang_data_new(xpath_input, value_str); - listnode_add(input, data); - } - - /* Execute callback registered for this XPath. */ - if (nb_callback_rpc(nb_node, xpath, input, output, errmsg, - sizeof(errmsg)) - != NB_OK) { - flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", - __func__, xpath); - ret = CONFD_ERR; - goto exit; - } - - /* Process output nodes. */ - if (listcount(output) > 0) { - struct listnode *node; - int i = 0; - - reply = XMALLOC(MTYPE_CONFD, - listcount(output) * sizeof(*reply)); - - for (ALL_LIST_ELEMENTS_RO(output, node, data)) { - struct nb_node *nb_node_output; - int hash; - - nb_node_output = nb_node_find(data->xpath); - if (!nb_node_output) { - flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, - "%s: unknown data path: %s", __func__, - data->xpath); - goto exit; - } - - hash = confd_str2hash(nb_node_output->snode->name); - CONFD_SET_TAG_STR(&reply[i++], hash, data->value); - } - confd_action_reply_values(uinfo, reply, listcount(output)); - XFREE(MTYPE_CONFD, reply); - } - -exit: - /* Release memory. */ - list_delete(&input); - list_delete(&output); - - return ret; -} - - -static int frr_confd_dp_read(struct confd_daemon_ctx *dctx, int fd) -{ - int ret; - - ret = confd_fd_ready(dctx, fd); - if (ret == CONFD_EOF) { - flog_err_confd("confd_fd_ready"); - frr_confd_finish(); - return -1; - } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) { - flog_err_confd("confd_fd_ready"); - frr_confd_finish(); - return -1; - } - - return 0; -} - -static void frr_confd_dp_ctl_read(struct event *thread) -{ - struct confd_daemon_ctx *dctx = EVENT_ARG(thread); - int fd = EVENT_FD(thread); - - event_add_read(master, frr_confd_dp_ctl_read, dctx, fd, &t_dp_ctl); - - frr_confd_dp_read(dctx, fd); -} - -static void frr_confd_dp_worker_read(struct event *thread) -{ - struct confd_daemon_ctx *dctx = EVENT_ARG(thread); - int fd = EVENT_FD(thread); - - event_add_read(master, frr_confd_dp_worker_read, dctx, fd, - &t_dp_worker); - - frr_confd_dp_read(dctx, fd); -} - -static int frr_confd_subscribe_state(const struct lysc_node *snode, void *arg) -{ - struct nb_node *nb_node = snode->priv; - struct confd_data_cbs *data_cbs = arg; - - if (!nb_node || !CHECK_FLAG(snode->flags, LYS_CONFIG_R)) - return YANG_ITER_CONTINUE; - /* We only need to subscribe to the root of the state subtrees. */ - if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) - return YANG_ITER_CONTINUE; - - DEBUGD(&nb_dbg_client_confd, - "%s: providing data to '%s' (callpoint %s)", __func__, - nb_node->xpath, snode->name); - - strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint)); - if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK) - flog_err_confd("confd_register_data_cb"); - - return YANG_ITER_CONTINUE; -} - -static int frr_confd_init_dp(const char *program_name) -{ - struct confd_trans_cbs trans_cbs; - struct confd_data_cbs data_cbs; - struct confd_notification_stream_cbs ncbs; - struct confd_action_cbs acbs; - - /* Initialize daemon context. */ - dctx = confd_init_daemon(program_name); - if (!dctx) { - flog_err_confd("confd_init_daemon"); - goto error; - } - - /* - * Inform we want to receive YANG values as raw strings, and that we - * want to provide only strings in the reply functions, regardless of - * the YANG type. - */ - confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY); - - /* Establish a control socket. */ - dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0); - if (dp_ctl_sock < 0) { - flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", - __func__, safe_strerror(errno)); - goto error; - } - - if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr, - sizeof(struct sockaddr_in)) - != CONFD_OK) { - flog_err_confd("confd_connect"); - goto error; - } - - /* - * Establish a worker socket (only one since this plugin runs on a - * single thread). - */ - dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0); - if (dp_worker_sock < 0) { - flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", - __func__, safe_strerror(errno)); - goto error; - } - if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr, - sizeof(struct sockaddr_in)) - != CONFD_OK) { - flog_err_confd("confd_connect"); - goto error; - } - - /* Register transaction callback functions. */ - memset(&trans_cbs, 0, sizeof(trans_cbs)); - trans_cbs.init = frr_confd_transaction_init; - confd_register_trans_cb(dctx, &trans_cbs); - - /* Register our read/write callbacks. */ - memset(&data_cbs, 0, sizeof(data_cbs)); - data_cbs.get_elem = frr_confd_data_get_elem; - data_cbs.exists_optional = frr_confd_data_get_elem; - data_cbs.get_next = frr_confd_data_get_next; - data_cbs.get_object = frr_confd_data_get_object; - data_cbs.get_next_object = frr_confd_data_get_next_object; - - /* - * Iterate over all loaded YANG modules and subscribe to the paths - * referent to state data. - */ - yang_snodes_iterate(NULL, frr_confd_subscribe_state, 0, &data_cbs); - - /* Register notification stream. */ - memset(&ncbs, 0, sizeof(ncbs)); - ncbs.fd = dp_worker_sock; - /* - * RFC 5277 - Section 3.2.3: - * A NETCONF server implementation supporting the notification - * capability MUST support the "NETCONF" notification event - * stream. This stream contains all NETCONF XML event notifications - * supported by the NETCONF server. - */ - strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname)); - if (confd_register_notification_stream(dctx, &ncbs, &live_ctx) - != CONFD_OK) { - flog_err_confd("confd_register_notification_stream"); - goto error; - } - - /* Register the action handler callback. */ - memset(&acbs, 0, sizeof(acbs)); - strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint)); - acbs.init = frr_confd_action_init; - acbs.action = frr_confd_action_execute; - if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) { - flog_err_confd("confd_register_action_cbs"); - goto error; - } - - /* Notify we registered all callbacks we wanted. */ - if (confd_register_done(dctx) != CONFD_OK) { - flog_err_confd("confd_register_done"); - goto error; - } - - event_add_read(master, frr_confd_dp_ctl_read, dctx, dp_ctl_sock, - &t_dp_ctl); - event_add_read(master, frr_confd_dp_worker_read, dctx, dp_worker_sock, - &t_dp_worker); - - return 0; - -error: - frr_confd_finish_dp(); - - return -1; -} - -static void frr_confd_finish_dp(void) -{ - if (dp_worker_sock > 0) { - EVENT_OFF(t_dp_worker); - close(dp_worker_sock); - } - if (dp_ctl_sock > 0) { - EVENT_OFF(t_dp_ctl); - close(dp_ctl_sock); - } - if (dctx != NULL) - confd_release_daemon(dctx); -} - -/* ------------ CLI ------------ */ - -DEFUN (debug_nb_confd, - debug_nb_confd_cmd, - "[no] debug northbound client confd", - NO_STR - DEBUG_STR - "Northbound debugging\n" - "Client\n" - "ConfD\n") -{ - uint32_t mode = DEBUG_NODE2MODE(vty->node); - bool no = strmatch(argv[0]->text, "no"); - - DEBUG_MODE_SET(&nb_dbg_client_confd, mode, !no); - - return CMD_SUCCESS; -} - -static int frr_confd_debug_config_write(struct vty *vty) -{ - if (DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_CONF)) - vty_out(vty, "debug northbound client confd\n"); - - return 0; -} - -static int frr_confd_debug_set_all(uint32_t flags, bool set) -{ - DEBUG_FLAGS_SET(&nb_dbg_client_confd, flags, set); - - /* If all modes have been turned off, don't preserve options. */ - if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_ALL)) - DEBUG_CLEAR(&nb_dbg_client_confd); - - return 0; -} - -static void frr_confd_cli_init(void) -{ - hook_register(nb_client_debug_config_write, - frr_confd_debug_config_write); - hook_register(nb_client_debug_set_all, frr_confd_debug_set_all); - - install_element(ENABLE_NODE, &debug_nb_confd_cmd); - install_element(CONFIG_NODE, &debug_nb_confd_cmd); -} - -/* ------------ Main ------------ */ - -static int frr_confd_calculate_snode_hash(const struct lysc_node *snode, - void *arg) -{ - struct nb_node *nb_node = snode->priv; - - if (nb_node) - nb_node->confd_hash = confd_str2hash(snode->name); - - return YANG_ITER_CONTINUE; -} - -static int frr_confd_init(const char *program_name) -{ - struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr; - int debuglevel = CONFD_SILENT; - int ret = -1; - - /* Initialize ConfD library. */ - confd_init(program_name, stderr, debuglevel); - - confd_addr4->sin_family = AF_INET; - confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1"); - confd_addr4->sin_port = htons(CONFD_PORT); - if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in)) - != CONFD_OK) { - flog_err_confd("confd_load_schemas"); - return -1; - } - - ret = frr_confd_init_cdb(); - if (ret != 0) - goto error; - - ret = frr_confd_init_dp(program_name); - if (ret != 0) { - frr_confd_finish_cdb(); - goto error; - } - - yang_snodes_iterate(NULL, frr_confd_calculate_snode_hash, 0, NULL); - - hook_register(nb_notification_send, frr_confd_notification_send); - - confd_connected = true; - return 0; - -error: - confd_free_schemas(); - - return ret; -} - -static int frr_confd_finish(void) -{ - if (!confd_connected) - return 0; - - frr_confd_finish_cdb(); - frr_confd_finish_dp(); - - confd_free_schemas(); - - confd_connected = false; - - return 0; -} - -static int frr_confd_module_late_init(struct event_loop *tm) -{ - master = tm; - - if (frr_confd_init(frr_get_progname()) < 0) { - flog_err(EC_LIB_CONFD_INIT, - "failed to initialize the ConfD module"); - return -1; - } - - hook_register(frr_fini, frr_confd_finish); - frr_confd_cli_init(); - - return 0; -} - -static int frr_confd_module_init(void) -{ - hook_register(frr_late_init, frr_confd_module_late_init); - - return 0; -} - -FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION, - .description = "FRR ConfD integration module", - .init = frr_confd_module_init, -); diff --git a/lib/subdir.am b/lib/subdir.am index c621ad0658..5ec6adf4c0 100644 --- a/lib/subdir.am +++ b/lib/subdir.am @@ -410,18 +410,6 @@ lib_libfrrzmq_la_SOURCES = \ #end # -# Tail-f's ConfD support -# -if CONFD -module_LTLIBRARIES += lib/confd.la -endif - -lib_confd_la_CFLAGS = $(AM_CFLAGS) $(CONFD_CFLAGS) -lib_confd_la_LDFLAGS = $(MODULE_LDFLAGS) -lib_confd_la_LIBADD = lib/libfrr.la $(CONFD_LIBS) -lib_confd_la_SOURCES = lib/northbound_confd.c - -# # Sysrepo support # if SYSREPO diff --git a/lib/yang.h b/lib/yang.h index 65f6a73e0b..85be38bf81 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -45,9 +45,6 @@ struct yang_module { RB_ENTRY(yang_module) entry; const char *name; const struct lys_module *info; -#ifdef HAVE_CONFD - int confd_hash; -#endif #ifdef HAVE_SYSREPO sr_subscription_ctx_t *sr_subscription; struct event *sr_thread; diff --git a/redhat/frr.spec.in b/redhat/frr.spec.in index 433aeacebb..b1157c3ad9 100644 --- a/redhat/frr.spec.in +++ b/redhat/frr.spec.in @@ -805,7 +805,21 @@ sed -i 's/ -M rpki//' %{_sysconfdir}/frr/daemons %changelog -* Tue Oct 10 2023 Donatas Abraitis <donatas@opensourcerouting.org> - %{version} +* Mon Mar 25 2024 Jafar Al-Gharaibeh <jafar@atcorp.com> - %{version} + +* Mon Mar 25 2024 Jafar Al-Gharaibeh <jafar@atcorp.com> - 10.0 +- Major highlights: +- Introduce local host routes +- Require libyang 2.1.128 +- Add suport to configire a log file per daemon +- BGP BMP Loc-RIB (RFC9069) support +- eBGP-OAD (One Administrative Domain) support +- BGP RPKI VRF support +- BGP SNMP traps for BGP4-MIBV2 +- Management (mgmtd) daemon "replace" operation support +- BGP dynamic capabilities for addpath, fqdn, orf capabilities +- SRv6 encapsulation source address feature +- OSPFv3 Point-To-Multipoint mode * Mon Oct 09 2023 Donatas Abraitis <donatas@opensourcerouting.org> - 9.1 - Major highlights: diff --git a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py index c98d837069..489c59fa40 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py +++ b/tests/topotests/bgp_l3vpn_to_bgp_direct/scripts/add_routes.py @@ -1,13 +1,13 @@ from lib.lutil import luCommand luCommand( - "r1", 'vtysh -c "show bgp next"', "99.0.0.. valid", "wait", "See CE static NH" + "r1", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" ) luCommand( - "r3", 'vtysh -c "show bgp next"', "99.0.0.. valid", "wait", "See CE static NH" + "r3", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" ) luCommand( - "r4", 'vtysh -c "show bgp next"', "99.0.0.. valid", "wait", "See CE static NH" + "r4", 'vtysh -c "show bgp nexthop"', "99.0.0.. valid", "wait", "See CE static NH" ) luCommand("r1", 'vtysh -c "show bgp ipv4 uni"', "i 5.*i 5", "wait", "See CE routes") luCommand("r3", 'vtysh -c "show bgp ipv4 uni"', "i 5.*i 5", "wait", "See CE routes") diff --git a/tests/topotests/bgp_peer_group/r1/bgpd.conf b/tests/topotests/bgp_peer_group/r1/bgpd.conf index 19b490a359..68d8e61a59 100644 --- a/tests/topotests/bgp_peer_group/r1/bgpd.conf +++ b/tests/topotests/bgp_peer_group/r1/bgpd.conf @@ -5,4 +5,8 @@ router bgp 65001 neighbor PG timers 3 10 neighbor 192.168.255.3 peer-group PG neighbor r1-eth0 interface peer-group PG + neighbor PG1 peer-group + neighbor PG1 remote-as external + neighbor PG1 timers 3 20 + neighbor 192.168.251.2 peer-group PG1 ! diff --git a/tests/topotests/bgp_peer_group/r1/zebra.conf b/tests/topotests/bgp_peer_group/r1/zebra.conf index e2c399e536..16fd8c538c 100644 --- a/tests/topotests/bgp_peer_group/r1/zebra.conf +++ b/tests/topotests/bgp_peer_group/r1/zebra.conf @@ -2,5 +2,8 @@ interface r1-eth0 ip address 192.168.255.1/24 ! +interface r1-eth1 + ip address 192.168.251.1/30 +! ip forwarding ! diff --git a/tests/topotests/bgp_peer_group/r2/bgpd.conf b/tests/topotests/bgp_peer_group/r2/bgpd.conf index 0880ee9fae..d0e8f017d1 100644 --- a/tests/topotests/bgp_peer_group/r2/bgpd.conf +++ b/tests/topotests/bgp_peer_group/r2/bgpd.conf @@ -4,4 +4,8 @@ router bgp 65002 neighbor PG remote-as external neighbor PG timers 3 10 neighbor r2-eth0 interface peer-group PG + neighbor PG1 peer-group + neighbor PG1 remote-as external + neighbor PG1 timers 3 20 + neighbor 192.168.251.1 peer-group PG1 ! diff --git a/tests/topotests/bgp_peer_group/r2/zebra.conf b/tests/topotests/bgp_peer_group/r2/zebra.conf index 606c17bec9..c2ad956c9c 100644 --- a/tests/topotests/bgp_peer_group/r2/zebra.conf +++ b/tests/topotests/bgp_peer_group/r2/zebra.conf @@ -2,5 +2,8 @@ interface r2-eth0 ip address 192.168.255.2/24 ! +interface r2-eth1 + ip address 192.168.251.2/30 +! ip forwarding ! diff --git a/tests/topotests/bgp_peer_group/test_bgp_peer-group.py b/tests/topotests/bgp_peer_group/test_bgp_peer-group.py index a91fade049..5cbcd19be9 100644 --- a/tests/topotests/bgp_peer_group/test_bgp_peer-group.py +++ b/tests/topotests/bgp_peer_group/test_bgp_peer-group.py @@ -22,7 +22,7 @@ sys.path.append(os.path.join(CWD, "../")) # pylint: disable=C0413 from lib import topotest from lib.topogen import Topogen, TopoRouter, get_topogen - +from lib.topolog import logger pytestmark = [pytest.mark.bgpd] @@ -36,6 +36,10 @@ def build_topo(tgen): switch.add_link(tgen.gears["r2"]) switch.add_link(tgen.gears["r3"]) + switch = tgen.add_switch("s2") + switch.add_link(tgen.gears["r1"]) + switch.add_link(tgen.gears["r2"]) + def setup_module(mod): tgen = Topogen(build_topo, mod.__name__) @@ -70,6 +74,7 @@ def test_bgp_peer_group(): expected = { "r1-eth0": {"peerGroup": "PG", "bgpState": "Established"}, "192.168.255.3": {"peerGroup": "PG", "bgpState": "Established"}, + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Established"}, } return topotest.json_cmp(output, expected) @@ -96,6 +101,48 @@ def test_bgp_peer_group(): assert result is None, "Failed checking advertised routes from r3" +def test_bgp_peer_group_remote_as_del_readd(): + tgen = get_topogen() + + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + r1 = tgen.gears["r1"] + logger.info("Remove bgp peer-group PG1 remote-as neighbor should be retained") + r1.cmd( + 'vtysh -c "config t" -c "router bgp 65001" ' + + ' -c "no neighbor PG1 remote-as external" ' + ) + + def _bgp_peer_group_remoteas_del(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Active"}, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_remoteas_del) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence in r1" + + logger.info("Re-add bgp peer-group PG1 remote-as neighbor should be established") + r1.cmd( + 'vtysh -c "config t" -c "router bgp 65001" ' + + ' -c "neighbor PG1 remote-as external" ' + ) + + def _bgp_peer_group_remoteas_add(): + output = json.loads(tgen.gears["r1"].vtysh_cmd("show bgp neighbor json")) + expected = { + "192.168.251.2": {"peerGroup": "PG1", "bgpState": "Established"}, + } + return topotest.json_cmp(output, expected) + + test_func = functools.partial(_bgp_peer_group_remoteas_add) + _, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5) + assert result is None, "Failed bgp convergence in r1" + + if __name__ == "__main__": args = ["-s"] + sys.argv[1:] sys.exit(pytest.main(args)) diff --git a/yang/confd/confd.frr-ripd.yang b/yang/confd/confd.frr-ripd.yang deleted file mode 100644 index 7bbe54cca9..0000000000 --- a/yang/confd/confd.frr-ripd.yang +++ /dev/null @@ -1,24 +0,0 @@ -module confd.frr-ripd { - namespace "urn:dummy"; - prefix "dummy"; - - import tailf-common { - prefix tailf; - } - import frr-ripd { - prefix frr-ripd; - } - - tailf:annotate-module "frr-ripd" { - tailf:annotate-statement "container[name='ripd']" { - tailf:annotate-statement "list[name='instance']" { - tailf:annotate-statement "container[name='state']" { - tailf:callpoint "state"; - } - } - } - tailf:annotate-statement "rpc[name='clear-rip-route']" { - tailf:actionpoint "actionpoint"; - } - } -} diff --git a/yang/confd/confd.frr-ripngd.yang b/yang/confd/confd.frr-ripngd.yang deleted file mode 100644 index 83383fb454..0000000000 --- a/yang/confd/confd.frr-ripngd.yang +++ /dev/null @@ -1,24 +0,0 @@ -module confd.frr-ripngd { - namespace "urn:dummy"; - prefix "dummy"; - - import tailf-common { - prefix tailf; - } - import frr-ripngd { - prefix frr-ripngd; - } - - tailf:annotate-module "frr-ripngd" { - tailf:annotate-statement "container[name='ripngd']" { - tailf:annotate-statement "list[name='instance']" { - tailf:annotate-statement "container[name='state']" { - tailf:callpoint "state"; - } - } - } - tailf:annotate-statement "rpc[name='clear-ripng-route']" { - tailf:actionpoint "actionpoint"; - } - } -} diff --git a/yang/subdir.am b/yang/subdir.am index 6745d53712..71aa040878 100644 --- a/yang/subdir.am +++ b/yang/subdir.am @@ -106,31 +106,4 @@ endif CLEANFILES += \ yang/*.c \ yang/ietf/*.c \ - yang/confd/*.c \ # - -if CONFD - -SUBMODULES = $(shell cd $(top_srcdir); grep -l belongs-to $(dist_yangmodels_DATA)) -EXCLUDED_MODULES = $(SUBMODULES) yang/frr-module-translator.yang -YANG_MODULES = $(filter-out $(EXCLUDED_MODULES),$(dist_yangmodels_DATA)) - -fxsdir = $(sysconfdir)/confd -fxs_DATA = $(YANG_MODULES:.yang=.fxs) - -SUFFIXES += .fxs -CLEANFILES += $(fxs_DATA) - -AM_V_CONFDC = $(AM_V_CONFDC_@AM_V@) -AM_V_CONFDC_ = $(AM_V_CONFDC_@AM_DEFAULT_V@) -AM_V_CONFDC_0 = @echo " CONFDC " $@; - -CONFDC_FLAGS = --yangpath $(srcdir)/yang --yangpath $(srcdir)/yang/ietf - -yang/%.fxs: yang/%.yang yang/confd/confd.%.yang - $(AM_V_CONFDC)$(CONFDC) $(CONFDC_FLAGS) -c -o $@ -a $(srcdir)/yang/confd/confd.$*.yang -- $< - -yang/%.fxs: yang/%.yang - $(AM_V_CONFDC)$(CONFDC) $(CONFDC_FLAGS) -c -o $@ -- $< - -endif diff --git a/zebra/interface.c b/zebra/interface.c index 8111863558..b824977f9e 100644 --- a/zebra/interface.c +++ b/zebra/interface.c @@ -176,6 +176,10 @@ static void if_nhg_dependents_release(const struct interface *ifp) frr_each(nhg_connected_tree, &zif->nhg_dependents, rb_node_dep) { rb_node_dep->nhe->ifp = NULL; /* Null it out */ zebra_nhg_check_valid(rb_node_dep->nhe); + if (CHECK_FLAG(rb_node_dep->nhe->flags, + NEXTHOP_GROUP_KEEP_AROUND) && + rb_node_dep->nhe->refcnt == 1) + zebra_nhg_decrement_ref(rb_node_dep->nhe); } } @@ -1741,6 +1745,9 @@ interface_bridge_vxlan_vlan_vni_map_update(struct zebra_dplane_ctx *ctx, vlanid_t vid; int i; + if (vniarray == NULL) + return; + memset(&vni_start, 0, sizeof(vni_start)); memset(&vni_end, 0, sizeof(vni_end)); diff --git a/zebra/zebra_vty.c b/zebra/zebra_vty.c index da6e2069ff..2fba5ebd9d 100644 --- a/zebra/zebra_vty.c +++ b/zebra/zebra_vty.c @@ -60,8 +60,8 @@ struct route_show_ctx { }; static int do_show_ip_route(struct vty *vty, const char *vrf_name, afi_t afi, - safi_t safi, bool use_fib, bool use_json, - route_tag_t tag, + safi_t safi, bool use_fib, json_object *vrf_json, + bool use_json, route_tag_t tag, const struct prefix *longer_prefix_p, bool supernets_only, int type, unsigned short ospf_instance_id, uint32_t tableid, @@ -148,8 +148,8 @@ DEFPY (show_ip_rpf, }; return do_show_ip_route(vty, VRF_DEFAULT_NAME, ip ? AFI_IP : AFI_IP6, - SAFI_MULTICAST, false, uj, 0, NULL, false, 0, 0, - 0, false, &ctx); + SAFI_MULTICAST, false, NULL, uj, 0, NULL, false, + 0, 0, 0, false, &ctx); } DEFPY (show_ip_rpf_addr, @@ -856,14 +856,13 @@ static void vty_show_ip_route_detail_json(struct vty *vty, vty_json(vty, json); } -static void do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, - struct route_table *table, afi_t afi, - bool use_fib, route_tag_t tag, - const struct prefix *longer_prefix_p, - bool supernets_only, int type, - unsigned short ospf_instance_id, bool use_json, - uint32_t tableid, bool show_ng, - struct route_show_ctx *ctx) +static void +do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, + struct route_table *table, afi_t afi, bool use_fib, + json_object *vrf_json, route_tag_t tag, + const struct prefix *longer_prefix_p, bool supernets_only, + int type, unsigned short ospf_instance_id, bool use_json, + uint32_t tableid, bool show_ng, struct route_show_ctx *ctx) { struct route_node *rn; struct route_entry *re; @@ -885,7 +884,7 @@ static void do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, * => display the VRF and table if specific */ - if (use_json) + if (use_json && !vrf_json) json = json_object_new_object(); /* Show all routes. */ @@ -960,7 +959,11 @@ static void do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, if (json_prefix) { prefix2str(&rn->p, buf, sizeof(buf)); - json_object_object_add(json, buf, json_prefix); + if (!vrf_json) + json_object_object_add(json, buf, json_prefix); + else + json_object_object_add(vrf_json, buf, + json_prefix); json_prefix = NULL; } } @@ -969,13 +972,15 @@ static void do_show_route_helper(struct vty *vty, struct zebra_vrf *zvrf, * This is an extremely expensive operation at scale * and non-pretty reduces memory footprint significantly. */ - if (use_json) + if (use_json && !vrf_json) { vty_json_no_pretty(vty, json); + json = NULL; + } } static void do_show_ip_route_all(struct vty *vty, struct zebra_vrf *zvrf, - afi_t afi, bool use_fib, bool use_json, - route_tag_t tag, + afi_t afi, bool use_fib, json_object *vrf_json, + bool use_json, route_tag_t tag, const struct prefix *longer_prefix_p, bool supernets_only, int type, unsigned short ospf_instance_id, bool show_ng, @@ -995,15 +1000,15 @@ static void do_show_ip_route_all(struct vty *vty, struct zebra_vrf *zvrf, continue; do_show_ip_route(vty, zvrf_name(zvrf), afi, SAFI_UNICAST, - use_fib, use_json, tag, longer_prefix_p, - supernets_only, type, ospf_instance_id, - zrt->tableid, show_ng, ctx); + use_fib, vrf_json, use_json, tag, + longer_prefix_p, supernets_only, type, + ospf_instance_id, zrt->tableid, show_ng, ctx); } } static int do_show_ip_route(struct vty *vty, const char *vrf_name, afi_t afi, - safi_t safi, bool use_fib, bool use_json, - route_tag_t tag, + safi_t safi, bool use_fib, json_object *vrf_json, + bool use_json, route_tag_t tag, const struct prefix *longer_prefix_p, bool supernets_only, int type, unsigned short ospf_instance_id, uint32_t tableid, @@ -1038,7 +1043,7 @@ static int do_show_ip_route(struct vty *vty, const char *vrf_name, afi_t afi, return CMD_SUCCESS; } - do_show_route_helper(vty, zvrf, table, afi, use_fib, tag, + do_show_route_helper(vty, zvrf, table, afi, use_fib, vrf_json, tag, longer_prefix_p, supernets_only, type, ospf_instance_id, use_json, tableid, show_ng, ctx); @@ -1741,6 +1746,7 @@ DEFPY (show_route, struct route_show_ctx ctx = { .multi = vrf_all || table_all, }; + json_object *root_json = NULL; if (!vrf_is_backend_netns()) { if ((vrf_all || vrf_name) && (table || table_all)) { @@ -1762,24 +1768,42 @@ DEFPY (show_route, } if (vrf_all) { + if (!!json) + root_json = json_object_new_object(); RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) { + json_object *vrf_json = NULL; + if ((zvrf = vrf->info) == NULL || (zvrf->table[afi][SAFI_UNICAST] == NULL)) continue; + if (!!json) + vrf_json = json_object_new_object(); + if (table_all) - do_show_ip_route_all( - vty, zvrf, afi, !!fib, !!json, tag, - prefix_str ? prefix : NULL, - !!supernets_only, type, - ospf_instance_id, !!ng, &ctx); + do_show_ip_route_all(vty, zvrf, afi, !!fib, + vrf_json, !!json, tag, + prefix_str ? prefix : NULL, + !!supernets_only, type, + ospf_instance_id, !!ng, + &ctx); else - do_show_ip_route( - vty, zvrf_name(zvrf), afi, SAFI_UNICAST, - !!fib, !!json, tag, - prefix_str ? prefix : NULL, - !!supernets_only, type, - ospf_instance_id, table, !!ng, &ctx); + do_show_ip_route(vty, zvrf_name(zvrf), afi, + SAFI_UNICAST, !!fib, vrf_json, + !!json, tag, + prefix_str ? prefix : NULL, + !!supernets_only, type, + ospf_instance_id, table, !!ng, + &ctx); + + if (!!json) + json_object_object_add(root_json, + zvrf_name(zvrf), + vrf_json); + } + if (!!json) { + vty_json_no_pretty(vty, root_json); + root_json = NULL; } } else { vrf_id_t vrf_id = VRF_DEFAULT; @@ -1795,13 +1819,13 @@ DEFPY (show_route, return CMD_SUCCESS; if (table_all) - do_show_ip_route_all(vty, zvrf, afi, !!fib, !!json, tag, - prefix_str ? prefix : NULL, + do_show_ip_route_all(vty, zvrf, afi, !!fib, NULL, !!json, + tag, prefix_str ? prefix : NULL, !!supernets_only, type, ospf_instance_id, !!ng, &ctx); else do_show_ip_route(vty, vrf->name, afi, SAFI_UNICAST, - !!fib, !!json, tag, + !!fib, NULL, !!json, tag, prefix_str ? prefix : NULL, !!supernets_only, type, ospf_instance_id, table, !!ng, &ctx); |
