diff options
68 files changed, 3308 insertions, 351 deletions
diff --git a/.gitignore b/.gitignore index a66e3ccd3c..07cdb11a21 100644 --- a/.gitignore +++ b/.gitignore @@ -117,3 +117,4 @@ refix /test-suite.log pceplib/test/*.log pceplib/test/*.trs +/tests/topotests/lib/mgmt_pb2.py diff --git a/bgpd/bgp_attr.c b/bgpd/bgp_attr.c index 75aa2ac7cc..edfbc6c835 100644 --- a/bgpd/bgp_attr.c +++ b/bgpd/bgp_attr.c @@ -864,6 +864,7 @@ bool attrhash_cmp(const void *p1, const void *p2) attr1->df_alg == attr2->df_alg && attr1->nh_ifindex == attr2->nh_ifindex && attr1->nh_lla_ifindex == attr2->nh_lla_ifindex && + attr1->nh_flags == attr2->nh_flags && attr1->distance == attr2->distance && srv6_l3vpn_same(attr1->srv6_l3vpn, attr2->srv6_l3vpn) && srv6_vpn_same(attr1->srv6_vpn, attr2->srv6_vpn) && diff --git a/bgpd/bgp_attr.h b/bgpd/bgp_attr.h index d30155e6db..942de57112 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -155,10 +155,52 @@ struct attr { uint32_t med; uint32_t local_pref; ifindex_t nh_ifindex; + uint8_t nh_flags; + +#define BGP_ATTR_NH_VALID 0x01 /* Path origin attribute */ uint8_t origin; + /* ES info */ + uint8_t es_flags; + /* Path is not "locally-active" on the advertising VTEP. This is + * translated into an ARP-ND ECOM. + */ +#define ATTR_ES_PROXY_ADVERT (1 << 0) + /* Destination ES is present locally. This flag is set on local + * paths and sync paths + */ +#define ATTR_ES_IS_LOCAL (1 << 1) + /* There are one or more non-best paths from ES peers. Note that + * this flag is only set on the local MAC-IP paths in the VNI + * route table (not set in the global routing table). And only + * non-proxy advertisements from an ES peer can result in this + * flag being set. + */ +#define ATTR_ES_PEER_ACTIVE (1 << 2) + /* There are one or more non-best proxy paths from ES peers */ +#define ATTR_ES_PEER_PROXY (1 << 3) + /* An ES peer has router bit set - only applicable if + * ATTR_ES_PEER_ACTIVE is set + */ +#define ATTR_ES_PEER_ROUTER (1 << 4) + + /* These two flags are only set on L3 routes installed in a + * VRF as a result of EVPN MAC-IP route + * XXX - while splitting up per-family attrs these need to be + * classified as non-EVPN + */ +#define ATTR_ES_L3_NHG_USE (1 << 5) +#define ATTR_ES_L3_NHG_ACTIVE (1 << 6) +#define ATTR_ES_L3_NHG (ATTR_ES_L3_NHG_USE | ATTR_ES_L3_NHG_ACTIVE) + + /* NA router flag (R-bit) support in EVPN */ + uint8_t router_flag; + + /* Distance as applied by Route map */ + uint8_t distance; + /* PMSI tunnel type (RFC 6514). */ enum pta_type pmsi_tnl_type; @@ -214,42 +256,6 @@ struct attr { /* Flag for default gateway extended community in EVPN */ uint8_t default_gw; - /* NA router flag (R-bit) support in EVPN */ - uint8_t router_flag; - - /* ES info */ - uint8_t es_flags; - /* Path is not "locally-active" on the advertising VTEP. This is - * translated into an ARP-ND ECOM. - */ -#define ATTR_ES_PROXY_ADVERT (1 << 0) - /* Destination ES is present locally. This flag is set on local - * paths and sync paths - */ -#define ATTR_ES_IS_LOCAL (1 << 1) - /* There are one or more non-best paths from ES peers. Note that - * this flag is only set on the local MAC-IP paths in the VNI - * route table (not set in the global routing table). And only - * non-proxy advertisements from an ES peer can result in this - * flag being set. - */ -#define ATTR_ES_PEER_ACTIVE (1 << 2) - /* There are one or more non-best proxy paths from ES peers */ -#define ATTR_ES_PEER_PROXY (1 << 3) - /* An ES peer has router bit set - only applicable if - * ATTR_ES_PEER_ACTIVE is set - */ -#define ATTR_ES_PEER_ROUTER (1 << 4) - - /* These two flags are only set on L3 routes installed in a - * VRF as a result of EVPN MAC-IP route - * XXX - while splitting up per-family attrs these need to be - * classified as non-EVPN - */ -#define ATTR_ES_L3_NHG_USE (1 << 5) -#define ATTR_ES_L3_NHG_ACTIVE (1 << 6) -#define ATTR_ES_L3_NHG (ATTR_ES_L3_NHG_USE | ATTR_ES_L3_NHG_ACTIVE) - /* route tag */ route_tag_t tag; @@ -259,13 +265,16 @@ struct attr { /* MPLS label */ mpls_label_t label; + /* EVPN DF preference for DF election on local ESs */ + uint16_t df_pref; + uint8_t df_alg; + /* SRv6 VPN SID */ struct bgp_attr_srv6_vpn *srv6_vpn; /* SRv6 L3VPN SID */ struct bgp_attr_srv6_l3vpn *srv6_l3vpn; - uint16_t encap_tunneltype; /* grr */ struct bgp_attr_encap_subtlv *encap_subtlvs; /* rfc5512 */ #ifdef ENABLE_BGP_VNC @@ -287,8 +296,7 @@ struct attr { /* EVPN local router-mac */ struct ethaddr rmac; - /* Distance as applied by Route map */ - uint8_t distance; + uint16_t encap_tunneltype; /* rmap set table */ uint32_t rmap_table_id; @@ -302,10 +310,6 @@ struct attr { /* SR-TE Color */ uint32_t srte_color; - /* EVPN DF preference and algorithm for DF election on local ESs */ - uint16_t df_pref; - uint8_t df_alg; - /* Nexthop type */ enum nexthop_types_t nh_type; diff --git a/bgpd/bgp_damp.h b/bgpd/bgp_damp.h index 4aff52c09c..6033c34bfd 100644 --- a/bgpd/bgp_damp.h +++ b/bgpd/bgp_damp.h @@ -74,8 +74,8 @@ struct bgp_damp_config { unsigned int ceiling; /* Max value a penalty can attain */ unsigned int decay_rate_per_tick; /* Calculated from half-life */ unsigned int decay_array_size; /* Calculated using config parameters */ - double scale_factor; unsigned int reuse_scale_factor; + double scale_factor; /* Decay array per-set based. */ double *decay_array; @@ -86,6 +86,7 @@ struct bgp_damp_config { /* Reuse list array per-set based. */ struct bgp_damp_info **reuse_list; int reuse_offset; + safi_t safi; /* All dampening information which is not on reuse list. */ struct bgp_damp_info *no_reuse_list; @@ -94,7 +95,6 @@ struct bgp_damp_config { struct event *t_reuse; afi_t afi; - safi_t safi; }; #define BGP_DAMP_NONE 0 diff --git a/bgpd/bgp_mplsvpn.c b/bgpd/bgp_mplsvpn.c index 5aa752d6e7..32436861f4 100644 --- a/bgpd/bgp_mplsvpn.c +++ b/bgpd/bgp_mplsvpn.c @@ -1034,13 +1034,19 @@ static bool leak_update_nexthop_valid(struct bgp *to_bgp, struct bgp_dest *bn, else if (bpi_ultimate->type == ZEBRA_ROUTE_BGP && bpi_ultimate->sub_type == BGP_ROUTE_STATIC && table && (table->safi == SAFI_UNICAST || - table->safi == SAFI_LABELED_UNICAST) && - !CHECK_FLAG(bgp_nexthop->flags, BGP_FLAG_IMPORT_CHECK)) { - /* if the route is defined with the "network <prefix>" command - * and "no bgp network import-check" is set, - * then mark the nexthop as valid. - */ - nh_valid = true; + table->safi == SAFI_LABELED_UNICAST)) { + /* the route is defined with the "network <prefix>" command */ + + if (CHECK_FLAG(bgp_nexthop->flags, BGP_FLAG_IMPORT_CHECK)) + nh_valid = bgp_find_or_add_nexthop(to_bgp, bgp_nexthop, + afi, SAFI_UNICAST, + bpi_ultimate, NULL, + 0, p); + else + /* if "no bgp network import-check" is set, + * then mark the nexthop as valid. + */ + nh_valid = true; } else /* * TBD do we need to do anything about the @@ -2084,6 +2090,7 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ uint32_t num_labels = 0; int nexthop_self_flag = 1; struct bgp_path_info *bpi_ultimate = NULL; + struct bgp_path_info *bpi; int origin_local = 0; struct bgp *src_vrf; struct interface *ifp; @@ -2173,6 +2180,20 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ community_strip_accept_own(&static_attr); + bn = bgp_afi_node_get(to_bgp->rib[afi][safi], afi, safi, p, NULL); + + for (bpi = bgp_dest_get_bgp_path_info(bn); bpi; bpi = bpi->next) { + if (bpi->extra && bpi->extra->vrfleak && + bpi->extra->vrfleak->parent == path_vpn) + break; + } + + if (bpi && leak_update_nexthop_valid(to_bgp, bn, &static_attr, afi, safi, + path_vpn, bpi, src_vrf, p, debug)) + SET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_VALID); + else + UNSET_FLAG(static_attr.nh_flags, BGP_ATTR_NH_VALID); + /* * Nexthop: stash and clear * @@ -2265,8 +2286,6 @@ static void vpn_leak_to_vrf_update_onevrf(struct bgp *to_bgp, /* to */ new_attr = bgp_attr_intern(&static_attr); bgp_attr_flush(&static_attr); - bn = bgp_afi_node_get(to_bgp->rib[afi][safi], afi, safi, p, NULL); - /* * ensure labels are copied * diff --git a/bgpd/bgp_nht.c b/bgpd/bgp_nht.c index 05fd0dc4e7..e2c103bb52 100644 --- a/bgpd/bgp_nht.c +++ b/bgpd/bgp_nht.c @@ -406,7 +406,7 @@ int bgp_find_or_add_nexthop(struct bgp *bgp_route, struct bgp *bgp_nexthop, if (pi && is_route_parent_evpn(pi)) bnc->is_evpn_gwip_nexthop = true; - if (is_bgp_static_route) { + if (is_bgp_static_route && !CHECK_FLAG(bnc->flags, BGP_STATIC_ROUTE)) { SET_FLAG(bnc->flags, BGP_STATIC_ROUTE); /* If we're toggling the type, re-register */ @@ -903,7 +903,10 @@ void bgp_nexthop_update(struct vrf *vrf, struct prefix *match, { struct bgp_nexthop_cache_head *tree = NULL; struct bgp_nexthop_cache *bnc_nhc, *bnc_import; - struct bgp *bgp; + struct bgp *bgp, *bgp_default; + struct bgp_path_info *pi; + struct bgp_dest *dest; + safi_t safi; afi_t afi; if (!vrf->info) { @@ -918,25 +921,38 @@ void bgp_nexthop_update(struct vrf *vrf, struct prefix *match, tree = &bgp->nexthop_cache_table[afi]; bnc_nhc = bnc_find(tree, match, nhr->srte_color, 0); - if (!bnc_nhc) { - if (BGP_DEBUG(nht, NHT)) - zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for nexthop cache", - &nhr->prefix, nhr->srte_color, - bgp->name_pretty); - } else + if (bnc_nhc) bgp_process_nexthop_update(bnc_nhc, nhr, false); + else if (BGP_DEBUG(nht, NHT)) + zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for nexthop cache", + &nhr->prefix, nhr->srte_color, + bgp->name_pretty); tree = &bgp->import_check_table[afi]; bnc_import = bnc_find(tree, match, nhr->srte_color, 0); - if (!bnc_import) { - if (BGP_DEBUG(nht, NHT)) - zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for import check", - &nhr->prefix, nhr->srte_color, - bgp->name_pretty); - } else + if (bnc_import) { bgp_process_nexthop_update(bnc_import, nhr, true); + bgp_default = bgp_get_default(); + safi = nhr->safi; + if (bgp != bgp_default && bgp->rib[afi][safi]) { + dest = bgp_afi_node_get(bgp->rib[afi][safi], afi, safi, + match, NULL); + + for (pi = bgp_dest_get_bgp_path_info(dest); pi; + pi = pi->next) + if (pi->peer == bgp->peer_self && + pi->type == ZEBRA_ROUTE_BGP && + pi->sub_type == BGP_ROUTE_STATIC) + vpn_leak_from_vrf_update(bgp_default, + bgp, pi); + } + } else if (BGP_DEBUG(nht, NHT)) + zlog_debug("parse nexthop update %pFX(%u)(%s): bnc info not found for import check", + &nhr->prefix, nhr->srte_color, + bgp->name_pretty); + /* * HACK: if any BGP route is dependant on an SR-policy that doesn't * exist, zebra will never send NH updates relative to that policy. In diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c index 8bcbd3dd8c..518e848258 100644 --- a/bgpd/bgp_route.c +++ b/bgpd/bgp_route.c @@ -7369,8 +7369,9 @@ static void bgp_aggregate_install( * If the aggregate information has not changed * no need to re-install it again. */ - if (pi && bgp_aggregate_info_same(pi, origin, aspath, community, - ecommunity, lcommunity)) { + if (pi && (!aggregate->rmap.changed && + bgp_aggregate_info_same(pi, origin, aspath, community, + ecommunity, lcommunity))) { bgp_dest_unlock_node(dest); if (aspath) @@ -8377,6 +8378,7 @@ static int bgp_aggregate_set(struct vty *vty, const char *prefix_str, afi_t afi, aggregate->rmap.name = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmap); aggregate->rmap.map = route_map_lookup_by_name(rmap); + aggregate->rmap.changed = true; route_map_counter_increment(aggregate->rmap.map); } diff --git a/bgpd/bgp_route.h b/bgpd/bgp_route.h index 0599e8dce1..18a60341f7 100644 --- a/bgpd/bgp_route.h +++ b/bgpd/bgp_route.h @@ -369,6 +369,8 @@ struct bgp_static { /* Import check status. */ uint8_t valid; + uint16_t encap_tunneltype; + /* IGP metric. */ uint32_t igpmetric; @@ -394,7 +396,6 @@ struct bgp_static { /* EVPN */ esi_t *eth_s_id; struct ethaddr *router_mac; - uint16_t encap_tunneltype; struct prefix gatewayIp; }; @@ -415,10 +416,22 @@ struct bgp_aggregate { /* AS set generation. */ uint8_t as_set; + /* Optional modify flag to override ORIGIN */ + uint8_t origin; + + /** Are there MED mismatches? */ + bool med_mismatched; + /* MED matching state. */ + /** Did we get the first MED value? */ + bool med_initialized; + /** Match only equal MED. */ + bool match_med; + /* Route-map for aggregated route. */ struct { char *name; struct route_map *map; + bool changed; } rmap; /* Suppress-count. */ @@ -430,9 +443,6 @@ struct bgp_aggregate { /* Count of routes of origin type egp under this aggregate. */ unsigned long egp_origin_count; - /* Optional modify flag to override ORIGIN */ - uint8_t origin; - /* Hash containing the communities of all the * routes under this aggregate. */ @@ -468,13 +478,6 @@ struct bgp_aggregate { /* SAFI configuration. */ safi_t safi; - /** Match only equal MED. */ - bool match_med; - /* MED matching state. */ - /** Did we get the first MED value? */ - bool med_initialized; - /** Are there MED mismatches? */ - bool med_mismatched; /** MED value found in current group. */ uint32_t med_matched_value; diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c index 382e8ae409..36e04c5e68 100644 --- a/bgpd/bgp_routemap.c +++ b/bgpd/bgp_routemap.c @@ -4596,6 +4596,7 @@ static void bgp_route_map_process_update(struct bgp *bgp, const char *rmap_name, route_map_counter_increment(map); aggregate->rmap.map = map; + aggregate->rmap.changed = true; matched = true; } diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index a1593421be..385a2f01e8 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -888,10 +888,10 @@ struct peer_group { struct bgp_notify { uint8_t code; uint8_t subcode; - char *data; bgp_size_t length; - uint8_t *raw_data; bool hard_reset; + char *data; + uint8_t *raw_data; }; /* Next hop self address. */ @@ -1890,11 +1890,11 @@ struct bgp_nlri { /* SAFI. */ uint8_t safi; /* iana_safi_t */ - /* Pointer to NLRI byte stream. */ - uint8_t *nlri; - /* Length of whole NLRI. */ bgp_size_t length; + + /* Pointer to NLRI byte stream. */ + uint8_t *nlri; }; /* BGP versions. */ diff --git a/configure.ac b/configure.ac index dbfae537b1..d9fd920c7c 100644 --- a/configure.ac +++ b/configure.ac @@ -701,6 +701,8 @@ AC_ARG_ENABLE([mgmtd], AS_HELP_STRING([--disable-mgmtd], [do not build mgmtd])) AC_ARG_ENABLE([mgmtd_local_validations], AS_HELP_STRING([--enable-mgmtd-local-validations], [dev: unimplemented local validation])) +AC_ARG_ENABLE([mgmtd_test_be_client], + AS_HELP_STRING([--enable-mgmtd-test-be-client], [build test backend client])) AC_ARG_ENABLE([ripd], AS_HELP_STRING([--disable-ripd], [do not build ripd])) AC_ARG_ENABLE([ripngd], @@ -1811,6 +1813,10 @@ AS_IF([test "$enable_mgmtd" != "no"], [ ]) ]) +AS_IF([test "$enable_mgmtd_test_be_client" = "yes"], [ + AC_DEFINE([HAVE_MGMTD_TESTC], [1], [mgmtd_testc]) +]) + AS_IF([test "$enable_ripd" != "no"], [ AC_DEFINE([HAVE_RIPD], [1], [ripd]) ]) @@ -2772,6 +2778,7 @@ AM_CONDITIONAL([VTYSH], [test "$VTYSH" = "vtysh"]) AM_CONDITIONAL([ZEBRA], [test "$enable_zebra" != "no"]) AM_CONDITIONAL([BGPD], [test "$enable_bgpd" != "no"]) AM_CONDITIONAL([MGMTD], [test "$enable_mgmtd" != "no"]) +AM_CONDITIONAL([MGMTD_TESTC], [test "$enable_mgmtd_test_be_client" = "yes"]) AM_CONDITIONAL([RIPD], [test "$enable_ripd" != "no"]) AM_CONDITIONAL([OSPFD], [test "$enable_ospfd" != "no"]) AM_CONDITIONAL([LDPD], [test "$enable_ldpd" != "no"]) diff --git a/debian/frr.pam b/debian/frr.pam index 737b88953b..1077243a12 100644 --- a/debian/frr.pam +++ b/debian/frr.pam @@ -1,4 +1,4 @@ # Any user may call vtysh but only those belonging to the group frrvty can # actually connect to the socket and use the program. auth sufficient pam_permit.so -account sufficient pam_rootok.so +account sufficient pam_permit.so diff --git a/doc/developer/northbound/retrofitting-configuration-commands.rst b/doc/developer/northbound/retrofitting-configuration-commands.rst index 4140848005..d328be9506 100644 --- a/doc/developer/northbound/retrofitting-configuration-commands.rst +++ b/doc/developer/northbound/retrofitting-configuration-commands.rst @@ -982,7 +982,7 @@ future. For libfrr commands, it’s not possible to centralize all commands in a single file because the *extract.pl* script from *vtysh* treats commands differently depending on the file in which they are defined (e.g. DEFUNs -from *lib/routemap.c* are installed using the ``VTYSH_RMAP`` constant, +from *lib/routemap.c* are installed using the ``VTYSH_RMAP_SHOW`` constant, which identifies the daemons that support route-maps). In this case, the CLI commands should be rewritten but maintained in the same file. diff --git a/doc/developer/topotests.rst b/doc/developer/topotests.rst index 7c65164b0e..3e3bd2dd21 100644 --- a/doc/developer/topotests.rst +++ b/doc/developer/topotests.rst @@ -33,6 +33,7 @@ Installing Topotest Requirements tshark \ valgrind python3 -m pip install wheel + python3 -m pip install protobuf python3 -m pip install 'pytest>=6.2.4' python3 -m pip install 'pytest-xdist>=2.3.0' python3 -m pip install 'scapy>=2.4.5' diff --git a/doc/user/basic.rst b/doc/user/basic.rst index 55b836e3b8..7f9027679f 100644 --- a/doc/user/basic.rst +++ b/doc/user/basic.rst @@ -682,6 +682,11 @@ Terminal Mode Commands This command displays FRR's timer data for timers that will pop in the future. +.. clicmd:: show configuration running [<json|xml> [translate WORD]] [with-defaults] DAEMON + + This command displays the northbound/YANG configuration data for a + daemon in text/vty, json, or xml format. + .. clicmd:: show yang operational-data XPATH [{format <json|xml>|translate TRANSLATOR|with-config}] DAEMON Display the YANG operational data starting from XPATH. The default diff --git a/lib/bitfield.h b/lib/bitfield.h index cc8c311416..3fda627b74 100644 --- a/lib/bitfield.h +++ b/lib/bitfield.h @@ -116,6 +116,7 @@ DECLARE_MTYPE(BITFIELD); (v).m = (v).m + 1; \ (v).data = XREALLOC(MTYPE_BITFIELD, (v).data, \ (v).m * sizeof(word_t)); \ + (v).data[(v).m - 1] = 0; \ } \ } while (0) diff --git a/lib/mgmt.proto b/lib/mgmt.proto index 5d83fca347..01a99ab63b 100644 --- a/lib/mgmt.proto +++ b/lib/mgmt.proto @@ -76,8 +76,9 @@ message YangGetDataReq { // message BeSubscribeReq { required string client_name = 1; - required bool subscribe_xpaths = 2; - repeated string xpath_reg = 3; + repeated string config_xpaths = 2; + repeated string oper_xpaths = 3; + repeated string notif_xpaths = 4; } message BeSubscribeReply { diff --git a/lib/mgmt_be_client.c b/lib/mgmt_be_client.c index 463aefdf25..b217ce40ed 100644 --- a/lib/mgmt_be_client.c +++ b/lib/mgmt_be_client.c @@ -311,6 +311,90 @@ static int be_client_send_error(struct mgmt_be_client *client, uint64_t txn_id, return ret; } +void mgmt_be_send_notification(struct lyd_node *tree) +{ + struct mgmt_be_client *client = __be_client; + struct mgmt_msg_notify_data *msg = NULL; + LYD_FORMAT format = LYD_JSON; + uint8_t **darrp; + LY_ERR err; + + assert(tree); + + MGMTD_BE_CLIENT_DBG("%s: sending YANG notification: %s", __func__, + tree->schema->name); + /* + * Allocate a message and append the data to it using `format` + */ + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_notify_data, 0, + MTYPE_MSG_NATIVE_NOTIFY); + msg->code = MGMT_MSG_CODE_NOTIFY; + msg->result_type = format; + + darrp = mgmt_msg_native_get_darrp(msg); + err = yang_print_tree_append(darrp, tree, format, + (LYD_PRINT_SHRINK | LYD_PRINT_WD_EXPLICIT | + LYD_PRINT_WITHSIBLINGS)); + if (err) { + flog_err(EC_LIB_LIBYANG, + "%s: error creating notification data: %s", __func__, + ly_strerrcode(err)); + goto done; + } + + (void)be_client_send_native_msg(client, msg, + mgmt_msg_native_get_msg_len(msg), false); +done: + mgmt_msg_native_free_msg(msg); + lyd_free_all(tree); +} + +/* + * Convert old style NB notification data into new MGMTD YANG tree and send. + */ +static int mgmt_be_notification_send(void *arg, const char *xpath, + struct list *args) +{ + struct lyd_node *root = NULL; + struct lyd_node *dnode; + struct yang_data *data; + struct listnode *ln; + LY_ERR err; + + MGMTD_BE_CLIENT_DBG("%s: sending notification: %s", __func__, xpath); + + /* + * Convert yang data args list to a libyang data tree + */ + for (ALL_LIST_ELEMENTS_RO(args, ln, data)) { + err = lyd_new_path(root, ly_native_ctx, data->xpath, + data->value, LYD_NEW_PATH_UPDATE, &dnode); + if (err != LY_SUCCESS) { +lyerr: + flog_err(EC_LIB_LIBYANG, + "%s: error creating notification data: %s", + __func__, ly_strerrcode(err)); + if (root) + lyd_free_all(root); + return 1; + } + if (!root) { + root = dnode; + while (root->parent) + root = lyd_parent(root); + } + } + + if (!root) { + err = lyd_new_path(NULL, ly_native_ctx, xpath, "", 0, &root); + if (err) + goto lyerr; + } + + mgmt_be_send_notification(root); + return 0; +} + static int mgmt_be_send_txn_reply(struct mgmt_be_client *client_ctx, uint64_t txn_id, bool create) { @@ -738,6 +822,12 @@ static int mgmt_be_client_handle_msg(struct mgmt_be_client *client_ctx, case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REPLY: MGMTD_BE_CLIENT_DBG("Got SUBSCR_REPLY success %u", be_msg->subscr_reply->success); + + if (client_ctx->cbs.subscr_done) + (*client_ctx->cbs.subscr_done)(client_ctx, + client_ctx->user_data, + be_msg->subscr_reply + ->success); break; case MGMTD__BE_MESSAGE__MESSAGE_TXN_REQ: MGMTD_BE_CLIENT_DBG("Got TXN_REQ %s txn-id: %" PRIu64, @@ -824,7 +914,7 @@ static enum nb_error be_client_send_tree_data_batch(const struct lyd_node *tree, darrp = mgmt_msg_native_get_darrp(tree_msg); err = yang_print_tree_append(darrp, tree, args->result_type, - (LYD_PRINT_WD_EXPLICIT | + (LYD_PRINT_SHRINK | LYD_PRINT_WD_EXPLICIT | LYD_PRINT_WITHSIBLINGS)); if (err) { ret = NB_ERR; @@ -874,6 +964,31 @@ static void be_client_handle_get_tree(struct mgmt_be_client *client, } /* + * Process the notification. + */ +static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf, + size_t msg_len) +{ + struct mgmt_msg_notify_data *notif_msg = msgbuf; + struct mgmt_be_client_notification_cb *cb; + const char *notif; + uint i; + + MGMTD_BE_CLIENT_DBG("Received notification for client %s", client->name); + + /* "{\"modname:notification-name\": ...}" */ + notif = (const char *)notif_msg->result + 2; + + for (i = 0; i < client->cbs.nnotify_cbs; i++) { + cb = &client->cbs.notify_cbs[i]; + if (strncmp(cb->xpath, notif, strlen(cb->xpath))) + continue; + cb->callback(client, client->user_data, cb, + (const char *)notif_msg->result); + } +} + +/* * Handle a native encoded message * * We don't create transactions with native messaging. @@ -888,12 +1003,16 @@ static void be_client_handle_native_msg(struct mgmt_be_client *client, case MGMT_MSG_CODE_GET_TREE: be_client_handle_get_tree(client, txn_id, msg, msg_len); break; + case MGMT_MSG_CODE_NOTIFY: + be_client_handle_notify(client, msg, msg_len); + break; default: MGMTD_BE_CLIENT_ERR("unknown native message txn-id %" PRIu64 " req-id %" PRIu64 " code %u to client %s", txn_id, msg->req_id, msg->code, client->name); - be_client_send_error(client, msg->refer_id, msg->req_id, false, -1, + be_client_send_error(client, msg->refer_id, msg->req_id, false, + -1, "BE cilent %s recv msg unknown txn-id %" PRIu64, client->name, txn_id); break; @@ -927,38 +1046,51 @@ static void mgmt_be_client_process_msg(uint8_t version, uint8_t *data, len); return; } - MGMTD_BE_CLIENT_DBG( - "Decoded %zu bytes of message(msg: %u/%u) from server", len, - be_msg->message_case, be_msg->message_case); + MGMTD_BE_CLIENT_DBG("Decoded %zu bytes of message(msg: %u/%u) from server", + len, be_msg->message_case, be_msg->message_case); (void)mgmt_be_client_handle_msg(client_ctx, be_msg); mgmtd__be_message__free_unpacked(be_msg, NULL); } int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, - bool subscr_xpaths, int num_xpaths, - char **reg_xpaths) + int n_config_xpaths, char **config_xpaths, + int n_oper_xpaths, char **oper_xpaths) { Mgmtd__BeMessage be_msg; Mgmtd__BeSubscribeReq subscr_req; + const char **notif_xpaths = NULL; + int ret; mgmtd__be_subscribe_req__init(&subscr_req); subscr_req.client_name = client_ctx->name; - subscr_req.n_xpath_reg = num_xpaths; - if (num_xpaths) - subscr_req.xpath_reg = reg_xpaths; - else - subscr_req.xpath_reg = NULL; - subscr_req.subscribe_xpaths = subscr_xpaths; + subscr_req.n_config_xpaths = n_config_xpaths; + subscr_req.config_xpaths = config_xpaths; + subscr_req.n_oper_xpaths = n_oper_xpaths; + subscr_req.oper_xpaths = oper_xpaths; + + /* See if we should register for notifications */ + subscr_req.n_notif_xpaths = client_ctx->cbs.nnotify_cbs; + if (client_ctx->cbs.nnotify_cbs) { + struct mgmt_be_client_notification_cb *cb, *ecb; + + cb = client_ctx->cbs.notify_cbs; + ecb = cb + client_ctx->cbs.nnotify_cbs; + for (; cb < ecb; cb++) + *darr_append(notif_xpaths) = cb->xpath; + } + subscr_req.notif_xpaths = (char **)notif_xpaths; mgmtd__be_message__init(&be_msg); be_msg.message_case = MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ; be_msg.subscr_req = &subscr_req; - MGMTD_BE_CLIENT_DBG("Sending SUBSCR_REQ name: %s subscr_xpaths: %u num_xpaths: %zu", - subscr_req.client_name, subscr_req.subscribe_xpaths, - subscr_req.n_xpath_reg); + MGMTD_BE_CLIENT_DBG("Sending SUBSCR_REQ name: %s xpaths: config %zu oper: %zu notif: %zu", + subscr_req.client_name, subscr_req.n_config_xpaths, + subscr_req.n_oper_xpaths, subscr_req.n_notif_xpaths); - return mgmt_be_client_send_msg(client_ctx, &be_msg); + ret = mgmt_be_client_send_msg(client_ctx, &be_msg); + darr_free(notif_xpaths); + return ret; } static int _notify_conenct_disconnect(struct msg_client *msg_client, @@ -970,15 +1102,16 @@ static int _notify_conenct_disconnect(struct msg_client *msg_client, if (connected) { assert(msg_client->conn.fd != -1); - ret = mgmt_be_send_subscr_req(client, false, 0, NULL); + ret = mgmt_be_send_subscr_req(client, 0, NULL, 0, NULL); if (ret) return ret; } /* Notify BE client through registered callback (if any) */ if (client->cbs.client_connect_notify) - (void)(*client->cbs.client_connect_notify)( - client, client->user_data, connected); + (void)(*client->cbs.client_connect_notify)(client, + client->user_data, + connected); /* Cleanup any in-progress TXN on disconnect */ if (!connected) @@ -1016,9 +1149,8 @@ static void mgmt_debug_client_be_set(uint32_t flags, bool set) DEFPY(debug_mgmt_client_be, debug_mgmt_client_be_cmd, "[no] debug mgmt client backend", - NO_STR DEBUG_STR MGMTD_STR - "client\n" - "backend\n") + NO_STR DEBUG_STR MGMTD_STR "client\n" + "backend\n") { mgmt_debug_client_be_set(DEBUG_NODE2MODE(vty->node), !no); @@ -1083,6 +1215,10 @@ struct mgmt_be_client *mgmt_be_client_create(const char *client_name, MGMTD_BE_MAX_NUM_MSG_WRITE, MGMTD_BE_MAX_MSG_LEN, false, "BE-client", MGMTD_DBG_BE_CLIENT_CHECK()); + /* Hook to receive notifications */ + hook_register_arg(nb_notification_send, mgmt_be_notification_send, + client); + MGMTD_BE_CLIENT_DBG("Initialized client '%s'", client_name); return client; diff --git a/lib/mgmt_be_client.h b/lib/mgmt_be_client.h index 8ad482cacf..32a717c496 100644 --- a/lib/mgmt_be_client.h +++ b/lib/mgmt_be_client.h @@ -60,14 +60,29 @@ struct mgmt_be_client_txn_ctx { * Callbacks: * client_connect_notify: called when connection is made/lost to mgmtd. * txn_notify: called when a txn has been created + * notify_cbs: callbacks for notifications. + * nnotify_cbs: number of notification callbacks. + * */ struct mgmt_be_client_cbs { void (*client_connect_notify)(struct mgmt_be_client *client, uintptr_t usr_data, bool connected); - + void (*subscr_done)(struct mgmt_be_client *client, uintptr_t usr_data, + bool success); void (*txn_notify)(struct mgmt_be_client *client, uintptr_t usr_data, struct mgmt_be_client_txn_ctx *txn_ctx, bool destroyed); + + struct mgmt_be_client_notification_cb *notify_cbs; + uint nnotify_cbs; +}; + +struct mgmt_be_client_notification_cb { + const char *xpath; /* the notification */ + uint8_t format; /* currently only LYD_JSON supported */ + void (*callback)(struct mgmt_be_client *client, uintptr_t usr_data, + struct mgmt_be_client_notification_cb *this, + const char *notif_data); }; /*************************************************************** @@ -124,7 +139,7 @@ extern void mgmt_debug_be_client_show_debug(struct vty *vty); * The client object. * * reg_yang_xpaths - * Yang xpath(s) that needs to be [un]-subscribed from/to + * Yang xpath(s) that needs to be subscribed to * * num_xpaths * Number of xpaths @@ -132,9 +147,18 @@ extern void mgmt_debug_be_client_show_debug(struct vty *vty); * Returns: * MGMTD_SUCCESS on success, MGMTD_* otherwise. */ -extern int mgmt_be_send_subscr_req(struct mgmt_be_client *client, - bool subscr_xpaths, int num_xpaths, - char **reg_xpaths); +extern int mgmt_be_send_subscr_req(struct mgmt_be_client *client_ctx, + int n_config_xpaths, char **config_xpaths, + int n_oper_xpaths, char **oper_xpaths); + +/** + * mgmt_be_notification_send() - send a YANG notification to FE clients. + * @tree: libyang tree for the notification. The tree will be freed by + * this function. + * + */ +extern void mgmt_be_send_notification(struct lyd_node *tree); + /* * Destroy backend client and cleanup everything. diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c index 92619f4f7f..c841821117 100644 --- a/lib/mgmt_fe_client.c +++ b/lib/mgmt_fe_client.c @@ -308,9 +308,10 @@ int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, /* * Send get-data request. */ -int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, - uint64_t req_id, LYD_FORMAT result_type, - uint8_t flags, const char *xpath) +int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, + uint64_t session_id, uint64_t req_id, + uint8_t datastore, LYD_FORMAT result_type, + uint8_t flags, uint8_t defaults, const char *xpath) { struct mgmt_msg_get_data *msg; size_t xplen = strlen(xpath); @@ -323,6 +324,8 @@ int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id msg->code = MGMT_MSG_CODE_GET_DATA; msg->result_type = result_type; msg->flags = flags; + msg->defaults = defaults; + msg->datastore = datastore; strlcpy(msg->xpath, xpath, xplen + 1); MGMTD_FE_CLIENT_DBG("Sending GET_DATA_REQ session-id %" PRIu64 @@ -507,19 +510,24 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, struct mgmt_msg_header *msg, size_t msg_len) { - struct mgmt_fe_client_session *session; + struct mgmt_fe_client_session *session = NULL; + struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; struct mgmt_msg_error *err_msg; + char *notify_data = NULL; - MGMTD_FE_CLIENT_DBG("Got GET_TREE reply for session-id %" PRIu64, + MGMTD_FE_CLIENT_DBG("Got native message for session-id %" PRIu64, msg->refer_id); - session = mgmt_fe_find_session_by_session_id(client, msg->refer_id); - - if (!session || !session->client) { - MGMTD_FE_CLIENT_ERR("No session for received native msg session-id %" PRIu64, - msg->refer_id); - return; + if (msg->code != MGMT_MSG_CODE_NOTIFY) { + session = mgmt_fe_find_session_by_session_id(client, + msg->refer_id); + if (!session || !session->client) { + MGMTD_FE_CLIENT_ERR( + "No session for received native msg session-id %" PRIu64, + msg->refer_id); + return; + } } switch (msg->code) { @@ -559,6 +567,44 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client, msg_len - sizeof(*tree_msg), tree_msg->partial_error); break; + case MGMT_MSG_CODE_NOTIFY: + notify_msg = (typeof(notify_msg))msg; + if (msg_len < sizeof(*notify_msg)) { + MGMTD_FE_CLIENT_ERR("Corrupt notify-data msg recv"); + return; + } + + if (notify_msg->result_type != LYD_LYB && + !MGMT_MSG_VALIDATE_NUL_TERM(notify_msg, msg_len)) { + MGMTD_FE_CLIENT_ERR("Corrupt error msg recv"); + return; + } + if (notify_msg->result_type == LYD_JSON) + notify_data = (char *)notify_msg->result; + else + notify_data = + yang_convert_lyd_format(notify_msg->result, + msg_len, + notify_msg->result_type, + LYD_JSON, true); + if (!notify_data) { + MGMTD_FE_CLIENT_ERR("Can't convert format %d to JSON", + notify_msg->result_type); + return; + } + FOREACH_SESSION_IN_LIST (client, session) { + if (!session->client->cbs.async_notification) + continue; + + session->client->cbs + .async_notification(client, client->user_data, + session->client_id, + session->user_ctx, + notify_data); + } + if (notify_msg->result_type != LYD_JSON) + darr_free(notify_data); + break; default: MGMTD_FE_CLIENT_ERR("unknown native message session-id %" PRIu64 " req-id %" PRIu64 " code %u", diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h index 3abe29b1cf..50ebb80149 100644 --- a/lib/mgmt_fe_client.h +++ b/lib/mgmt_fe_client.h @@ -114,6 +114,11 @@ struct mgmt_fe_client_cbs { LYD_FORMAT result_type, void *result, size_t len, int partial_error); + /* Called with asynchronous notifications from backends */ + int (*async_notification)(struct mgmt_fe_client *client, + uintptr_t user_data, uint64_t client_id, + uintptr_t session_ctx, const char *result); + /* Called when new native error is returned */ int (*error_notify)(struct mgmt_fe_client *client, uintptr_t user_data, uint64_t client_id, uint64_t session_id, @@ -379,12 +384,18 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, * req_id * Client request ID. * + * datastore + * Datastore for getting data. + * * result_type * The LYD_FORMAT of the result. * * flags * Flags to control the behavior of the request. * + * defaults + * Options to control the reporting of default values. + * * xpath * the xpath to get. * @@ -393,7 +404,8 @@ extern int mgmt_fe_send_regnotify_req(struct mgmt_fe_client *client, */ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client, uint64_t session_id, uint64_t req_id, - LYD_FORMAT result_type, uint8_t flags, + uint8_t datastore, LYD_FORMAT result_type, + uint8_t flags, uint8_t defaults, const char *xpath); /* diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c index a9b26718db..d27c5d3a29 100644 --- a/lib/mgmt_msg_native.c +++ b/lib/mgmt_msg_native.c @@ -14,6 +14,7 @@ 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"); int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id, uint64_t req_id, bool short_circuit_ok, diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h index 069cb9b150..7273170a13 100644 --- a/lib/mgmt_msg_native.h +++ b/lib/mgmt_msg_native.h @@ -143,6 +143,7 @@ DECLARE_MTYPE(MSG_NATIVE_ERROR); DECLARE_MTYPE(MSG_NATIVE_GET_TREE); DECLARE_MTYPE(MSG_NATIVE_TREE_DATA); DECLARE_MTYPE(MSG_NATIVE_GET_DATA); +DECLARE_MTYPE(MSG_NATIVE_NOTIFY); /* * Native message codes @@ -151,6 +152,31 @@ DECLARE_MTYPE(MSG_NATIVE_GET_DATA); #define MGMT_MSG_CODE_GET_TREE 1 #define MGMT_MSG_CODE_TREE_DATA 2 #define MGMT_MSG_CODE_GET_DATA 3 +#define MGMT_MSG_CODE_NOTIFY 4 + +/* + * Datastores + */ +#define MGMT_MSG_DATASTORE_STARTUP 0 +#define MGMT_MSG_DATASTORE_CANDIDATE 1 +#define MGMT_MSG_DATASTORE_RUNNING 2 +#define MGMT_MSG_DATASTORE_OPERATIONAL 3 + +/* + * Formats + */ +#define MGMT_MSG_FORMAT_XML 1 +#define MGMT_MSG_FORMAT_JSON 2 +#define MGMT_MSG_FORMAT_BINARY 3 /* non-standard libyang internal format */ + +/* + * Now we're using LYD_FORMAT directly to avoid mapping code, but having our + * own definitions allows us to create such a mapping in the future if libyang + * makes a backwards incompatible change. + */ +_Static_assert(MGMT_MSG_FORMAT_XML == LYD_XML, "Format mismatch"); +_Static_assert(MGMT_MSG_FORMAT_JSON == LYD_JSON, "Format mismatch"); +_Static_assert(MGMT_MSG_FORMAT_BINARY == LYD_LYB, "Format mismatch"); /** * struct mgmt_msg_header - Header common to all native messages. @@ -234,22 +260,34 @@ _Static_assert(sizeof(struct mgmt_msg_tree_data) == "Size mismatch"); /* Flags for get-data request */ -#define GET_DATA_FLAG_STATE 0x01 /* get only "config false" data */ -#define GET_DATA_FLAG_CONFIG 0x02 /* get only "config true" data */ +#define GET_DATA_FLAG_STATE 0x01 /* include "config false" data */ +#define GET_DATA_FLAG_CONFIG 0x02 /* include "config true" data */ #define GET_DATA_FLAG_EXACT 0x04 /* get exact data node instead of the full tree */ +/* + * Modes of reporting default values. Non-default values are always reported. + * These options reflect "with-defaults" modes as defined in RFC 6243. + */ +#define GET_DATA_DEFAULTS_EXPLICIT 0 /* "explicit" */ +#define GET_DATA_DEFAULTS_TRIM 1 /* "trim" */ +#define GET_DATA_DEFAULTS_ALL 2 /* "report-all" */ +#define GET_DATA_DEFAULTS_ALL_ADD_TAG 3 /* "report-all-tagged" */ + /** * struct mgmt_msg_get_data - frontend get-data request. * * @result_type: ``LYD_FORMAT`` for the returned result. * @flags: combination of ``GET_DATA_FLAG_*`` flags. + * @defaults: one of ``GET_DATA_DEFAULTS_*`` values. * @xpath: the query for the data to return. */ struct mgmt_msg_get_data { struct mgmt_msg_header; uint8_t result_type; uint8_t flags; - uint8_t resv2[6]; + uint8_t defaults; + uint8_t datastore; + uint8_t resv2[4]; alignas(8) char xpath[]; }; @@ -257,8 +295,29 @@ _Static_assert(sizeof(struct mgmt_msg_get_data) == offsetof(struct mgmt_msg_get_data, xpath), "Size mismatch"); +/** + * struct mgmt_msg_notify_data - Message carrying notification data. + * + * @result_type: ``LYD_FORMAT`` for format of the @result value. + * @result: The tree data in @result_type format. + * + */ +struct mgmt_msg_notify_data { + struct mgmt_msg_header; + uint8_t result_type; + uint8_t resv2[7]; + + alignas(8) uint8_t result[]; +}; +_Static_assert(sizeof(struct mgmt_msg_notify_data) == + offsetof(struct mgmt_msg_notify_data, result), + "Size mismatch"); + +/* + * Validate that the message ends in a NUL terminating byte + */ #define MGMT_MSG_VALIDATE_NUL_TERM(msgp, len) \ - ((len) >= sizeof(*msg) + 1 && ((char *)msgp)[(len)-1] == 0) + ((len) >= sizeof(*msgp) + 1 && ((char *)msgp)[(len)-1] == 0) /** diff --git a/lib/northbound.c b/lib/northbound.c index b1da3315d0..a0b1bd18c5 100644 --- a/lib/northbound.c +++ b/lib/northbound.c @@ -2068,6 +2068,23 @@ int nb_notification_send(const char *xpath, struct list *arguments) return ret; } +DEFINE_HOOK(nb_notification_tree_send, (struct lyd_node *tree), (tree)); + +int nb_notification_tree_send(struct lyd_node *tree) +{ + int ret; + + assert(tree); + + DEBUGD(&nb_dbg_notif, "northbound tree notification: %s", + tree->schema->name); + + ret = hook_call(nb_notification_tree_send, tree); + lyd_free_all(tree); + + return ret; +} + /* Running configuration user pointers management. */ struct nb_config_entry { char xpath[XPATH_MAXLEN]; diff --git a/lib/northbound.h b/lib/northbound.h index 2d9643e7b4..9279122deb 100644 --- a/lib/northbound.h +++ b/lib/northbound.h @@ -1441,6 +1441,10 @@ extern bool nb_cb_operation_is_valid(enum nb_cb_operation operation, const struct lysc_node *snode); /* + * DEPRECATED: This call and infra should no longer be used. Instead, + * the mgmtd supported tree based call `nb_notification_tree_send` should be + * used instead + * * Send a YANG notification. This is a no-op unless the 'nb_notification_send' * hook was registered by a northbound plugin. * @@ -1457,6 +1461,19 @@ extern bool nb_cb_operation_is_valid(enum nb_cb_operation operation, extern int nb_notification_send(const char *xpath, struct list *arguments); /* + * Send a YANG notification from a backend . This is a no-op unless th + * 'nb_notification_tree_send' hook was registered by a northbound plugin. + * + * tree + * The libyang tree for the notification. The tree will be freed by + * this call. + * + * Returns: + * NB_OK on success, NB_ERR otherwise. + */ +extern int nb_notification_tree_send(struct lyd_node *tree); + +/* * Associate a user pointer to a configuration node. * * This should be called by northbound 'create' callbacks in the NB_EV_APPLY @@ -4101,16 +4101,17 @@ int vty_mgmt_send_get_req(struct vty *vty, bool is_config, return 0; } -int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, - uint8_t flags, const char *xpath) +int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT result_type, uint8_t flags, + uint8_t defaults, const char *xpath) { LYD_FORMAT intern_format = result_type; vty->mgmt_req_id++; if (mgmt_fe_send_get_data_req(mgmt_fe_client, vty->mgmt_session_id, - vty->mgmt_req_id, intern_format, flags, - xpath)) { + vty->mgmt_req_id, datastore, + intern_format, flags, defaults, xpath)) { zlog_err("Failed to send GET-DATA to MGMTD session-id: %" PRIu64 " req-id %" PRIu64 ".", vty->mgmt_session_id, vty->mgmt_req_id); @@ -420,8 +420,9 @@ extern int vty_mgmt_send_commit_config(struct vty *vty, bool validate_only, extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config, Mgmtd__DatastoreId datastore, const char **xpath_list, int num_req); -extern int vty_mgmt_send_get_data_req(struct vty *vty, LYD_FORMAT result_type, - uint8_t flags, const char *xpath); +extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore, + LYD_FORMAT result_type, uint8_t flags, + uint8_t defaults, const char *xpath); extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id, bool lock, bool scok); extern void vty_mgmt_resume_response(struct vty *vty, int ret); diff --git a/lib/yang.c b/lib/yang.c index 3dd2513a4b..2b360376d3 100644 --- a/lib/yang.c +++ b/lib/yang.c @@ -744,6 +744,34 @@ uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, return darr; } +char *yang_convert_lyd_format(const uint8_t *data, size_t data_len, + LYD_FORMAT in_format, + LYD_FORMAT out_format, bool shrink) +{ + struct lyd_node *tree = NULL; + uint8_t *result = NULL; + uint32_t options = LYD_PRINT_WD_EXPLICIT | LYD_PRINT_WITHSIBLINGS; + + assert(out_format != LYD_LYB); + + if (!MGMT_MSG_VALIDATE_NUL_TERM(data, data_len)) + return NULL; + + if (in_format == out_format) + return darr_strdup((const char *)data); + + if (shrink) + options |= LYD_PRINT_SHRINK; + + /* Take a guess at the initial capacity based on input data size */ + darr_ensure_cap(result, data_len); + if (yang_print_tree_append(&result, tree, out_format, options)) { + darr_free(result); + return NULL; + } + return (char *)result; +} + const char *yang_print_errors(struct ly_ctx *ly_ctx, char *buf, size_t buf_len) { struct ly_err_item *ei; diff --git a/lib/yang.h b/lib/yang.h index 431b2eee48..4ed0a39ba4 100644 --- a/lib/yang.h +++ b/lib/yang.h @@ -622,6 +622,22 @@ extern void yang_debugging_set(bool enable); extern uint8_t *yang_print_tree(const struct lyd_node *root, LYD_FORMAT format, uint32_t options); + +/** + * yang_convert_lyd_format() - convert one libyang format to darr string. + * @data: data to convert. + * @data_len: length of the data. + * @in_format: format of the data. + * @out_format: format to return. + * @shrink: true to avoid pretty printing. + * + * Return: + * A darr based string or NULL for error. + */ +extern char *yang_convert_lyd_format(const uint8_t *data, size_t msg_len, + LYD_FORMAT in_format, + LYD_FORMAT out_format, bool shrink); + /* * "Print" the yang tree in `root` into an existing dynamic sized array. * diff --git a/mgmtd/mgmt_be_adapter.c b/mgmtd/mgmt_be_adapter.c index 8d7ae88555..66e622b326 100644 --- a/mgmtd/mgmt_be_adapter.c +++ b/mgmtd/mgmt_be_adapter.c @@ -35,6 +35,7 @@ /* ---------- */ const char *mgmt_be_client_names[MGMTD_BE_CLIENT_ID_MAX + 1] = { + [MGMTD_BE_CLIENT_ID_TESTC] = "mgmtd-testc", /* always first */ [MGMTD_BE_CLIENT_ID_ZEBRA] = "zebra", #ifdef HAVE_RIPD [MGMTD_BE_CLIENT_ID_RIPD] = "ripd", @@ -155,6 +156,7 @@ static const char *const *be_client_oper_xpaths[MGMTD_BE_CLIENT_ID_MAX] = { static struct mgmt_be_xpath_map *be_cfg_xpath_map; static struct mgmt_be_xpath_map *be_oper_xpath_map; +static struct mgmt_be_xpath_map *be_notif_xpath_map; static struct event_loop *mgmt_loop; static struct msg_server mgmt_be_server = {.fd = -1}; @@ -219,11 +221,16 @@ mgmt_be_find_adapter_by_name(const char *name) } static void mgmt_register_client_xpath(enum mgmt_be_client_id id, - const char *xpath, bool config) + const char *xpath, bool config, bool oper) { struct mgmt_be_xpath_map **maps, *map; - maps = config ? &be_cfg_xpath_map : &be_oper_xpath_map; + if (config) + maps = &be_cfg_xpath_map; + else if (oper) + maps = &be_oper_xpath_map; + else + maps = &be_notif_xpath_map; darr_foreach_p (*maps, map) { if (!strcmp(xpath, map->xpath_prefix)) { @@ -251,13 +258,13 @@ static void mgmt_be_xpath_map_init(void) /* Initialize the common config init map */ for (init = be_client_config_xpaths[id]; init && *init; init++) { MGMTD_BE_ADAPTER_DBG(" - CFG XPATH: '%s'", *init); - mgmt_register_client_xpath(id, *init, true); + mgmt_register_client_xpath(id, *init, true, false); } /* Initialize the common oper init map */ for (init = be_client_oper_xpaths[id]; init && *init; init++) { MGMTD_BE_ADAPTER_DBG(" - OPER XPATH: '%s'", *init); - mgmt_register_client_xpath(id, *init, false); + mgmt_register_client_xpath(id, *init, false, true); } } @@ -278,6 +285,10 @@ static void mgmt_be_xpath_map_cleanup(void) darr_foreach_p (be_oper_xpath_map, map) XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); darr_free(be_oper_xpath_map); + + darr_foreach_p (be_notif_xpath_map, map) + XFREE(MTYPE_MGMTD_XPATH, map->xpath_prefix); + darr_free(be_notif_xpath_map); } @@ -388,20 +399,20 @@ static int mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, Mgmtd__BeMessage *be_msg) { + const char *xpath; + uint i, num; + /* * protobuf-c adds a max size enum with an internal, and changing by * version, name; cast to an int to avoid unhandled enum warnings */ switch ((int)be_msg->message_case) { case MGMTD__BE_MESSAGE__MESSAGE_SUBSCR_REQ: - MGMTD_BE_ADAPTER_DBG( - "Got SUBSCR_REQ from '%s' to %sregister %zu xpaths", - be_msg->subscr_req->client_name, - !be_msg->subscr_req->subscribe_xpaths && - be_msg->subscr_req->n_xpath_reg - ? "de" - : "", - be_msg->subscr_req->n_xpath_reg); + MGMTD_BE_ADAPTER_DBG("Got SUBSCR_REQ from '%s' to register xpaths config: %zu oper: %zu notif: %zu", + be_msg->subscr_req->client_name, + be_msg->subscr_req->n_config_xpaths, + be_msg->subscr_req->n_oper_xpaths, + be_msg->subscr_req->n_notif_xpaths); if (strlen(be_msg->subscr_req->client_name)) { strlcpy(adapter->name, be_msg->subscr_req->client_name, @@ -413,7 +424,6 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, adapter->name); /* this will/should delete old */ msg_conn_disconnect(adapter->conn, false); - zlog_err("XXX different from original code"); break; } mgmt_be_adapters_by_id[adapter->id] = adapter; @@ -423,11 +433,28 @@ mgmt_be_adapter_handle_msg(struct mgmt_be_client_adapter *adapter, mgmt_be_adapter_sched_init_event(adapter); } - if (be_msg->subscr_req->n_xpath_reg) - /* we aren't handling dynamic xpaths yet */ - mgmt_be_send_subscr_reply(adapter, false); - else - mgmt_be_send_subscr_reply(adapter, true); + num = be_msg->subscr_req->n_config_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->config_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, true, + false); + } + + num = be_msg->subscr_req->n_oper_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->oper_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, false, + true); + } + + num = be_msg->subscr_req->n_notif_xpaths; + for (i = 0; i < num; i++) { + xpath = be_msg->subscr_req->notif_xpaths[i]; + mgmt_register_client_xpath(adapter->id, xpath, false, + false); + } + + mgmt_be_send_subscr_reply(adapter, true); break; case MGMTD__BE_MESSAGE__MESSAGE_TXN_REPLY: MGMTD_BE_ADAPTER_DBG( @@ -575,6 +602,34 @@ int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg) return mgmt_msg_native_send_msg(adapter->conn, msg, false); } +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; + const char *notif; + uint id; + + if (!darr_len(be_notif_xpath_map)) + return; + + /* "{\"modname:notification-name\": ...}" */ + notif = (const char *)msg->result + 2; + + darr_foreach_p (be_notif_xpath_map, map) { + if (strncmp(map->xpath_prefix, notif, strlen(map->xpath_prefix))) + 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 */ @@ -582,6 +637,7 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, struct mgmt_msg_header *msg, size_t msg_len) { + struct mgmt_msg_notify_data *notify_msg; struct mgmt_msg_tree_data *tree_msg; struct mgmt_msg_error *error_msg; @@ -607,6 +663,12 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter, /* Forward the reply to the txn module */ mgmt_txn_notify_tree_data_reply(adapter, tree_msg, msg_len); break; + case MGMT_MSG_CODE_NOTIFY: + notify_msg = (typeof(notify_msg))msg; + MGMTD_BE_ADAPTER_DBG("Got NOTIFY from '%s'", adapter->name); + mgmt_be_adapter_send_notify(notify_msg, msg_len); + mgmt_fe_adapter_send_notify(notify_msg, msg_len); + break; default: MGMTD_BE_ADAPTER_ERR("unknown native message txn-id %" PRIu64 " req-id %" PRIu64 diff --git a/mgmtd/mgmt_be_adapter.h b/mgmtd/mgmt_be_adapter.h index 955291b7c8..491410aa15 100644 --- a/mgmtd/mgmt_be_adapter.h +++ b/mgmtd/mgmt_be_adapter.h @@ -27,6 +27,8 @@ * #ifdef HAVE_COMPONENT */ enum mgmt_be_client_id { + MGMTD_BE_CLIENT_ID_TESTC, /* always first */ + MGMTD_BE_CLIENT_ID_ZEBRA, #ifdef HAVE_RIPD MGMTD_BE_CLIENT_ID_RIPD, #endif @@ -36,7 +38,6 @@ enum mgmt_be_client_id { #ifdef HAVE_STATICD MGMTD_BE_CLIENT_ID_STATICD, #endif - MGMTD_BE_CLIENT_ID_ZEBRA, MGMTD_BE_CLIENT_ID_MAX }; #define MGMTD_BE_CLIENT_ID_MIN 0 @@ -244,6 +245,13 @@ extern int mgmt_be_send_native(enum mgmt_be_client_id id, void *msg); */ extern uint64_t mgmt_be_interested_clients(const char *xpath, bool config); +/** + * mgmt_fe_adapter_send_notify() - notify FE clients of a notification. + * @msg: the notify message from the backend client. + * @msglen: the length of the notify message. + */ +extern void mgmt_fe_adapter_send_notify(struct mgmt_msg_notify_data *msg, + size_t msglen); /* * Dump backend client information for a given xpath to vty. */ diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c index a99d92d2b6..23f2e5368e 100644 --- a/mgmtd/mgmt_fe_adapter.c +++ b/mgmtd/mgmt_fe_adapter.c @@ -1080,36 +1080,26 @@ mgmt_fe_adapter_handle_msg(struct mgmt_fe_client_adapter *adapter, */ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, uint64_t req_id, bool short_circuit_ok, - uint8_t result_type, + uint8_t result_type, uint32_t wd_options, const struct lyd_node *tree, int partial_error) { struct mgmt_msg_tree_data *msg; - struct lyd_node *empty = NULL; - uint8_t *buf = NULL; + uint8_t **darrp = NULL; int ret = 0; - darr_append_n(buf, sizeof(*msg)); - msg = (typeof(msg))buf; + msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_tree_data, 0, + MTYPE_MSG_NATIVE_TREE_DATA); msg->refer_id = session->session_id; msg->req_id = req_id; msg->code = MGMT_MSG_CODE_TREE_DATA; msg->partial_error = partial_error; msg->result_type = result_type; - if (!tree) { - empty = yang_dnode_new(ly_native_ctx, false); - tree = empty; - } - - ret = yang_print_tree_append(&buf, tree, result_type, - (LYD_PRINT_WD_EXPLICIT | - LYD_PRINT_WITHSIBLINGS)); - /* buf may have been reallocated and moved */ - msg = (typeof(msg))buf; - (void)msg; /* suppress clang-SA unused warning on safety code */ - + darrp = mgmt_msg_native_get_darrp(msg); + ret = yang_print_tree_append(darrp, tree, result_type, + (wd_options | LYD_PRINT_WITHSIBLINGS)); if (ret != LY_SUCCESS) { MGMTD_FE_ADAPTER_ERR("Error building get-tree result for client %s session-id %" PRIu64 " req-id %" PRIu64 @@ -1121,15 +1111,15 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session, MGMTD_FE_ADAPTER_DBG("Sending get-tree result from adapter %s to session-id %" PRIu64 " req-id %" PRIu64 " scok %d result type %u len %u", - session->adapter->name, session->session_id, req_id, - short_circuit_ok, result_type, darr_len(buf)); + session->adapter->name, session->session_id, + req_id, short_circuit_ok, result_type, + mgmt_msg_native_get_msg_len(msg)); - ret = fe_adapter_send_native_msg(session->adapter, buf, darr_len(buf), + ret = fe_adapter_send_native_msg(session->adapter, msg, + mgmt_msg_native_get_msg_len(msg), short_circuit_ok); done: - if (empty) - yang_dnode_free(empty); - darr_free(buf); + mgmt_msg_native_free_msg(msg); return ret; } @@ -1147,7 +1137,9 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, struct lysc_node **snodes = NULL; char *xpath_resolved = NULL; uint64_t req_id = msg->req_id; + Mgmtd__DatastoreId ds_id; uint64_t clients; + uint32_t wd_options; bool simple_xpath; LY_ERR err; int ret; @@ -1172,6 +1164,43 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, goto done; } + switch (msg->defaults) { + case GET_DATA_DEFAULTS_EXPLICIT: + wd_options = LYD_PRINT_WD_EXPLICIT; + break; + case GET_DATA_DEFAULTS_TRIM: + wd_options = LYD_PRINT_WD_TRIM; + break; + case GET_DATA_DEFAULTS_ALL: + wd_options = LYD_PRINT_WD_ALL; + break; + case GET_DATA_DEFAULTS_ALL_ADD_TAG: + wd_options = LYD_PRINT_WD_IMPL_TAG; + break; + default: + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Invalid defaults value %u for session-id: %" PRIu64, + msg->defaults, session->session_id); + goto done; + } + + switch (msg->datastore) { + case MGMT_MSG_DATASTORE_CANDIDATE: + ds_id = MGMTD_DS_CANDIDATE; + break; + case MGMT_MSG_DATASTORE_RUNNING: + ds_id = MGMTD_DS_RUNNING; + break; + case MGMT_MSG_DATASTORE_OPERATIONAL: + ds_id = MGMTD_DS_OPERATIONAL; + break; + default: + fe_adapter_send_error(session, req_id, false, -EINVAL, + "Unsupported datastore %" PRIu8 + " requested from session-id: %" PRIu64, + msg->datastore, session->session_id); + goto done; + } err = yang_resolve_snode_xpath(ly_native_ctx, msg->xpath, &snodes, &simple_xpath); @@ -1191,7 +1220,7 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, session->session_id); fe_adapter_send_tree_data(session, req_id, false, - msg->result_type, NULL, 0); + msg->result_type, wd_options, NULL, 0); goto done; } @@ -1210,8 +1239,8 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session, /* Create a GET-TREE request under the transaction */ ret = mgmt_txn_send_get_tree_oper(session->txn_id, req_id, clients, - msg->result_type, msg->flags, - simple_xpath, msg->xpath); + ds_id, msg->result_type, msg->flags, + wd_options, simple_xpath, msg->xpath); if (ret) { /* destroy the just created txn */ mgmt_destroy_txn(&session->txn_id); @@ -1286,6 +1315,23 @@ static void mgmt_fe_adapter_process_msg(uint8_t version, uint8_t *data, mgmtd__fe_message__free_unpacked(fe_msg, NULL); } +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; + + assert(msg->refer_id == 0); + + FOREACH_ADAPTER_IN_LIST (adapter) { + FOREACH_SESSION_IN_LIST (adapter, session) { + msg->refer_id = session->session_id; + (void)fe_adapter_send_native_msg(adapter, msg, msglen, + false); + } + } + msg->refer_id = 0; +} + void mgmt_fe_adapter_lock(struct mgmt_fe_client_adapter *adapter) { adapter->refcount++; @@ -1453,6 +1499,7 @@ int mgmt_fe_send_get_reply(uint64_t session_id, uint64_t txn_id, int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, uint64_t req_id, LYD_FORMAT result_type, + uint32_t wd_options, const struct lyd_node *tree, int partial_error, bool short_circuit_ok) { @@ -1464,7 +1511,8 @@ int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, return -1; ret = fe_adapter_send_tree_data(session, req_id, short_circuit_ok, - result_type, tree, partial_error); + result_type, wd_options, tree, + partial_error); mgmt_destroy_txn(&session->txn_id); diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h index 09d64415bc..2150f864d9 100644 --- a/mgmtd/mgmt_fe_adapter.h +++ b/mgmtd/mgmt_fe_adapter.h @@ -148,6 +148,7 @@ extern int mgmt_fe_send_get_reply(uint64_t session_id, uint64_t txn_id, * txn_id: the txn_id this data pertains to * req_id: the req id for the get_tree message * result_type: the format of the result data. + * wd_options: with-defaults options. * tree: the results. * partial_error: if there were errors while gather results. * short_circuit_ok: True if OK to short-circuit the call. @@ -156,12 +157,11 @@ extern int mgmt_fe_send_get_reply(uint64_t session_id, uint64_t txn_id, * the return value from the underlying send function. * */ -extern int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, - uint64_t req_id, - LYD_FORMAT result_type, - const struct lyd_node *tree, - int partial_error, - bool short_circuit_ok); +extern int +mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id, + uint64_t req_id, LYD_FORMAT result_type, + uint32_t wd_options, const struct lyd_node *tree, + int partial_error, bool short_circuit_ok); /** * Send an error back to the FE client using native messaging. diff --git a/mgmtd/mgmt_main.c b/mgmtd/mgmt_main.c index 6dbd1f2e52..5be849b63c 100644 --- a/mgmtd/mgmt_main.c +++ b/mgmtd/mgmt_main.c @@ -145,6 +145,16 @@ extern const struct frr_yang_module_info frr_staticd_cli_info; #endif /* + * These are modules that are only needed by mgmtd and hence not included into + * the lib and backend daemons. + */ +const struct frr_yang_module_info ietf_netconf_with_defaults_info = { + .name = "ietf-netconf-with-defaults", + .ignore_cfg_cbs = true, + .nodes = { { .xpath = NULL } }, +}; + +/* * These are stub info structs that are used to load the modules used by backend * clients into mgmtd. The modules are used by libyang in order to support * parsing binary data returns from the backend. @@ -167,6 +177,9 @@ static const struct frr_yang_module_info *const mgmt_yang_modules[] = { &frr_vrf_info, &frr_affinity_map_cli_info, + /* mgmtd-only modules */ + &ietf_netconf_with_defaults_info, + /* * YANG module info used by backend clients get added here. */ diff --git a/mgmtd/mgmt_testc.c b/mgmtd/mgmt_testc.c new file mode 100644 index 0000000000..70cd2bb0cd --- /dev/null +++ b/mgmtd/mgmt_testc.c @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * January 29 2024, Christian Hopps <chopps@labn.net> + * + * Copyright (c) 2024, LabN Consulting, L.L.C. + * + */ + +#include <zebra.h> +#include <lib/version.h> +#include "libfrr.h" +#include "mgmt_be_client.h" + +/* ---------------- */ +/* Local Prototypes */ +/* ---------------- */ + +static void ripd_notification(struct mgmt_be_client *client, uintptr_t usr_data, + struct mgmt_be_client_notification_cb *this, + const char *notif_data); + +static void sigusr1(void); +static void sigint(void); + +/* ----------- */ +/* Global Data */ +/* ----------- */ + +/* privileges */ +static zebra_capabilities_t _caps_p[] = {}; + +struct zebra_privs_t __privs = { +#if defined(FRR_USER) && defined(FRR_GROUP) + .user = FRR_USER, + .group = FRR_GROUP, +#endif +#ifdef VTY_GROUP + .vty_group = VTY_GROUP, +#endif + .caps_p = _caps_p, + .cap_num_p = array_size(_caps_p), + .cap_num_i = 0, +}; + +struct option longopts[] = {{0}}; + +/* Master of threads. */ +struct event_loop *master; + +struct mgmt_be_client *mgmt_be_client; + +static struct frr_daemon_info mgmtd_testc_di; + +struct frr_signal_t __signals[] = { + { + .signal = SIGUSR1, + .handler = &sigusr1, + }, + { + .signal = SIGINT, + .handler = &sigint, + }, + { + .signal = SIGTERM, + .handler = &sigint, + }, +}; + +#define MGMTD_TESTC_VTY_PORT 2624 + +/* clang-format off */ +FRR_DAEMON_INFO(mgmtd_testc, MGMTD_TESTC, + .proghelp = "FRR Management Daemon Test Client.", + + .signals = __signals, + .n_signals = array_size(__signals), + + .privs = &__privs, + + // .yang_modules = mgmt_yang_modules, + // .n_yang_modules = array_size(mgmt_yang_modules), + + /* avoid libfrr trying to read our config file for us */ + .flags = FRR_MANUAL_VTY_START, + ); +/* clang-format on */ + +struct mgmt_be_client_notification_cb __notify_cbs[] = { { + .xpath = "frr-ripd", + .format = LYD_JSON, + .callback = ripd_notification, +} }; + +struct mgmt_be_client_cbs __client_cbs = { + .notify_cbs = __notify_cbs, + .nnotify_cbs = array_size(__notify_cbs), +}; + + +/* --------- */ +/* Functions */ +/* --------- */ + + +static void sigusr1(void) +{ + zlog_rotate(); +} + +static void sigint(void) +{ + zlog_notice("Terminating on signal"); + frr_fini(); + exit(0); +} + +static void ripd_notification(struct mgmt_be_client *client, uintptr_t usr_data, + struct mgmt_be_client_notification_cb *this, + const char *notif_data) +{ + zlog_notice("Received RIPd notification"); +} + +int main(int argc, char **argv) +{ + frr_preinit(&mgmtd_testc_di, argc, argv); + frr_opt_add("", longopts, ""); + + while (1) { + int opt; + + opt = frr_getopt(argc, argv, NULL); + + if (opt == EOF) + break; + + switch (opt) { + case 0: + break; + default: + frr_help_exit(1); + } + } + + master = frr_init(); + + mgmt_be_client = mgmt_be_client_create("mgmtd-testc", &__client_cbs, 0, + master); + + frr_config_fork(); + frr_run(master); + + /* Reached. */ + return 0; +} diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c index 7f88524e85..dd2023a4fb 100644 --- a/mgmtd/mgmt_txn.c +++ b/mgmtd/mgmt_txn.c @@ -175,6 +175,7 @@ struct txn_req_get_tree { uint64_t recv_clients; /* Bitmask of clients recv reply from */ int32_t partial_error; /* an error while gather results */ uint8_t result_type; /* LYD_FORMAT for results */ + uint8_t wd_options; /* LYD_PRINT_WD_* flags for results */ uint8_t exact; /* if exact node is requested */ uint8_t simple_xpath; /* if xpath is simple */ struct lyd_node *client_results; /* result tree from clients */ @@ -1282,6 +1283,7 @@ static int txn_get_tree_data_done(struct mgmt_txn_ctx *txn, txn->txn_id, txn_req->req_id, get_tree->result_type, + get_tree->wd_options, result, get_tree->partial_error, false); @@ -2339,8 +2341,9 @@ int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, * has registered operational state that matches the given `xpath` */ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, - uint64_t clients, LYD_FORMAT result_type, - uint8_t flags, bool simple_xpath, + uint64_t clients, Mgmtd__DatastoreId ds_id, + LYD_FORMAT result_type, uint8_t flags, + uint32_t wd_options, bool simple_xpath, const char *xpath) { struct mgmt_msg_get_tree *msg; @@ -2359,13 +2362,20 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, txn_req = mgmt_txn_req_alloc(txn, req_id, MGMTD_TXN_PROC_GETTREE); get_tree = txn_req->req.get_tree; get_tree->result_type = result_type; + get_tree->wd_options = wd_options; get_tree->exact = CHECK_FLAG(flags, GET_DATA_FLAG_EXACT); get_tree->simple_xpath = simple_xpath; get_tree->xpath = XSTRDUP(MTYPE_MGMTD_XPATH, xpath); if (CHECK_FLAG(flags, GET_DATA_FLAG_CONFIG)) { + /* + * If the requested datastore is operational, get the config + * from running. + */ struct mgmt_ds_ctx *ds = - mgmt_ds_get_ctx_by_id(mm, MGMTD_DS_RUNNING); + mgmt_ds_get_ctx_by_id(mm, ds_id == MGMTD_DS_OPERATIONAL + ? MGMTD_DS_RUNNING + : ds_id); struct nb_config *config = mgmt_ds_get_nb_config(ds); if (config) { @@ -2411,7 +2421,8 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, } state: /* If we are only getting config, we are done */ - if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || !clients) + if (!CHECK_FLAG(flags, GET_DATA_FLAG_STATE) || + ds_id != MGMTD_DS_OPERATIONAL || !clients) return txn_get_tree_data_done(txn, txn_req); msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_get_tree, slen + 1, diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h index 02b2baa95f..b7198326da 100644 --- a/mgmtd/mgmt_txn.h +++ b/mgmtd/mgmt_txn.h @@ -202,8 +202,10 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, * txn_id: Transaction identifier. * req_id: FE client request identifier. * clients: Bitmask of clients to send get-tree to. + * ds_id: datastore ID. * result_type: LYD_FORMAT result format. * flags: option flags for the request. + * wd_options: LYD_PRINT_WD_* flags for the result. * simple_xpath: true if xpath is simple (only key predicates). * xpath: The xpath to get the tree from. * @@ -211,8 +213,10 @@ extern int mgmt_txn_send_get_req(uint64_t txn_id, uint64_t req_id, * 0 on success. */ extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id, - uint64_t clients, LYD_FORMAT result_type, - uint8_t flags, bool simple_xpath, + uint64_t clients, + Mgmtd__DatastoreId ds_id, + LYD_FORMAT result_type, uint8_t flags, + uint32_t wd_options, bool simple_xpath, const char *xpath); /* diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c index 7135bc5547..12ea62ecef 100644 --- a/mgmtd/mgmt_vty.c +++ b/mgmtd/mgmt_vty.c @@ -258,14 +258,22 @@ DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd, } DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, - "show mgmt get-data WORD$path [with-config|only-config]$content [exact]$exact [json|xml]$fmt", + "show mgmt get-data WORD$path [datastore <candidate|running|operational>$ds] [with-config|only-config]$content [exact]$exact [with-defaults <trim|all-tag|all>$wd] [json|xml]$fmt", SHOW_STR MGMTD_STR "Get a data from the operational datastore\n" "XPath expression specifying the YANG data root\n" + "Specify datastore to get data from (operational by default)\n" + "Candidate datastore\n" + "Running datastore\n" + "Operational datastore\n" "Include \"config true\" data\n" "Get only \"config true\" data\n" "Get exact node instead of the whole data tree\n" + "Configure 'with-defaults' mode per RFC 6243 (\"explicit\" mode by default)\n" + "Use \"trim\" mode\n" + "Use \"report-all-tagged\" mode\n" + "Use \"report-all\" mode\n" "JSON output format\n" "XML output format\n") { @@ -273,6 +281,8 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, int plen = strlen(path); char *xpath = NULL; uint8_t flags = content ? GET_DATA_FLAG_CONFIG : GET_DATA_FLAG_STATE; + uint8_t defaults = GET_DATA_DEFAULTS_EXPLICIT; + uint8_t datastore = MGMT_MSG_DATASTORE_OPERATIONAL; if (content && content[0] == 'w') flags |= GET_DATA_FLAG_STATE; @@ -280,6 +290,22 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, if (exact) flags |= GET_DATA_FLAG_EXACT; + if (wd) { + if (wd[0] == 't') + defaults = GET_DATA_DEFAULTS_TRIM; + else if (wd[3] == '-') + defaults = GET_DATA_DEFAULTS_ALL_ADD_TAG; + else + defaults = GET_DATA_DEFAULTS_ALL; + } + + if (ds) { + if (ds[0] == 'c') + datastore = MGMT_MSG_DATASTORE_CANDIDATE; + else if (ds[0] == 'r') + datastore = MGMT_MSG_DATASTORE_RUNNING; + } + /* get rid of extraneous trailing slash-* or single '/' unless root */ if (plen > 2 && ((path[plen - 2] == '/' && path[plen - 1] == '*') || (path[plen - 2] != '/' && path[plen - 1] == '/'))) { @@ -289,7 +315,8 @@ DEFPY(show_mgmt_get_data, show_mgmt_get_data_cmd, path = xpath; } - vty_mgmt_send_get_data_req(vty, format, flags, path); + vty_mgmt_send_get_data_req(vty, datastore, format, flags, defaults, + path); if (xpath) XFREE(MTYPE_TMP, xpath); diff --git a/mgmtd/subdir.am b/mgmtd/subdir.am index a3955925ed..0af64dc0be 100644 --- a/mgmtd/subdir.am +++ b/mgmtd/subdir.am @@ -50,11 +50,20 @@ noinst_HEADERS += \ sbin_PROGRAMS += mgmtd/mgmtd +if MGMTD_TESTC +sbin_PROGRAMS += mgmtd/mgmtd_testc +mgmtd_mgmtd_testc_SOURCES = mgmtd/mgmt_testc.c +mgmtd_mgmtd_testc_LDADD = lib/libfrr.la +endif + mgmtd_mgmtd_SOURCES = \ mgmtd/mgmt_main.c \ # end nodist_mgmtd_mgmtd_SOURCES = \ yang/frr-zebra.yang.c \ + yang/ietf/ietf-netconf-acm.yang.c \ + yang/ietf/ietf-netconf.yang.c \ + yang/ietf/ietf-netconf-with-defaults.yang.c \ # nothing mgmtd_mgmtd_CFLAGS = $(AM_CFLAGS) -I ./ mgmtd_mgmtd_LDADD = mgmtd/libmgmtd.a lib/libfrr.la $(LIBCAP) $(LIBM) $(LIBYANG_LIBS) $(UST_LIBS) diff --git a/redhat/frr.pam b/redhat/frr.pam index 17a62f1999..a574c5e575 100644 --- a/redhat/frr.pam +++ b/redhat/frr.pam @@ -4,8 +4,8 @@ ##### if running frr as root: # Only allow root (and possibly wheel) to use this because enable access # is unrestricted. -auth sufficient pam_rootok.so -account sufficient pam_rootok.so +auth sufficient pam_permit.so +account sufficient pam_permit.so # Uncomment the following line to implicitly trust users in the "wheel" group. #auth sufficient pam_wheel.so trust use_uid diff --git a/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py b/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py index cec06920cb..eeac7146b1 100644 --- a/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py +++ b/tests/topotests/bgp_aggregate_address_route_map/test_bgp_aggregate-address_route-map.py @@ -74,7 +74,8 @@ def test_bgp_maximum_prefix_invalid(): if tgen.routers_have_failure(): pytest.skip(tgen.errors) - router = tgen.gears["r2"] + r1 = tgen.gears["r1"] + r2 = tgen.gears["r2"] def _bgp_converge(router): output = json.loads(router.vtysh_cmd("show ip bgp neighbor 192.168.255.1 json")) @@ -86,22 +87,30 @@ def test_bgp_maximum_prefix_invalid(): } return topotest.json_cmp(output, expected) - def _bgp_aggregate_address_has_metric(router): + def _bgp_aggregate_address_has_metric(router, metric): output = json.loads(router.vtysh_cmd("show ip bgp 172.16.255.0/24 json")) - expected = {"paths": [{"metric": 123}]} + expected = {"paths": [{"metric": metric}]} return topotest.json_cmp(output, expected) - test_func = functools.partial(_bgp_converge, router) - success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) - - assert result is None, 'Failed to see bgp convergence in "{}"'.format(router) - - test_func = functools.partial(_bgp_aggregate_address_has_metric, router) - success, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) - - assert ( - result is None - ), 'Failed to see applied metric for aggregated prefix in "{}"'.format(router) + test_func = functools.partial(_bgp_converge, r2) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see bgp convergence in r2" + + test_func = functools.partial(_bgp_aggregate_address_has_metric, r2, 123) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see applied metric for aggregated prefix in r2" + + r1.vtysh_cmd( + """ + configure terminal + route-map aggr-rmap permit 10 + set metric 666 + """ + ) + + test_func = functools.partial(_bgp_aggregate_address_has_metric, r2, 666) + _, result = topotest.run_and_expect(test_func, None, count=30, wait=0.5) + assert result is None, "Failed to see applied metric for aggregated prefix in r2" if __name__ == "__main__": diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py index 23ab90794c..45c44ba5db 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/customize.py @@ -165,6 +165,8 @@ def ltemplatePreRouterStartHook(): cmds = [ "ip link add {0}-cust4 type vrf table 30", "ip link set dev {0}-cust4 up", + "ip link add {0}-cust5 type vrf table 40", + "ip link set dev {0}-cust5 up", ] rtr = "r1" for cmd in cmds: diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf index b389eb1013..0d652dac07 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/bgpd.conf @@ -64,5 +64,17 @@ router bgp 5227 vrf r1-cust4 import vpn export vpn exit-address-family -! -end + +router bgp 5227 vrf r1-cust5 + bgp router-id 192.168.1.1 + + address-family ipv4 unicast + network 172.16.1.1/32 + + label vpn export 105 + rd vpn export 10:15 + rt vpn both 52:100 + + import vpn + export vpn + exit-address-family diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf index 221bc7a839..9a5b0a605e 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/r1/zebra.conf @@ -18,6 +18,10 @@ interface r1-eth4 ip address 192.168.1.1/24 no link-detect +interface r1-cust5 + ip address 172.16.1.1/32 + no link-detect + ip forwarding diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py index 3ab9b3f46e..28047f7b2d 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/check_routes.py @@ -60,6 +60,7 @@ want_r1_cust1_routes = [ {"p": "6.0.1.0/24", "n": "99.0.0.1"}, {"p": "6.0.2.0/24", "n": "99.0.0.1"}, {"p": "172.16.0.0/24", "n": "0.0.0.0", "bp": True}, + {"p": "172.16.1.1/32", "n": "0.0.0.0", "bp": True}, {"p": "99.0.0.1/32", "n": "192.168.1.2"}, ] bgpribRequireUnicastRoutes( @@ -73,6 +74,13 @@ bgpribRequireUnicastRoutes( "r1", "ipv4", "r1-cust4", "Customer 4 routes in r1 vrf", want_r1_cust4_routes ) +want_r1_cust5_routes = [ + {"p": "172.16.1.1/32", "n": "0.0.0.0", "bp": True}, +] +bgpribRequireUnicastRoutes( + "r1", "ipv4", "r1-cust5", "Customer 5 routes in r1 vrf", want_r1_cust5_routes +) + want_r3_cust1_routes = [ {"p": "5.1.0.0/24", "n": "99.0.0.2"}, {"p": "5.1.1.0/24", "n": "99.0.0.2"}, @@ -675,7 +683,7 @@ bgpribRequireUnicastRoutes( luCommand( "ce1", 'vtysh -c "show bgp ipv4 uni"', - "13 routes and 13", + "14 routes and 14", "wait", "Local and remote routes", 10, @@ -697,7 +705,7 @@ bgpribRequireUnicastRoutes( luCommand( "ce2", 'vtysh -c "show bgp ipv4 uni"', - "13 routes and 16", + "14 routes and 17", "wait", "Local and remote routes", 10, @@ -729,7 +737,7 @@ luCommand("r4", 'vtysh -c "show ip route vrf r4-cust2"') luCommand( "ce3", 'vtysh -c "show bgp ipv4 uni"', - "13 routes and 14", + "14 routes and 15", "wait", "Local and remote routes", 10, @@ -751,7 +759,7 @@ bgpribRequireUnicastRoutes( luCommand( "ce4", 'vtysh -c "show bgp vrf ce4-cust2 ipv4 uni"', - "13 routes and 15", + "14 routes and 16", "wait", "Local and remote routes", 10, diff --git a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py index 43a5245d0f..190879cc27 100644 --- a/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py +++ b/tests/topotests/bgp_l3vpn_to_bgp_vrf/scripts/scale_down.py @@ -49,7 +49,7 @@ if ret != False and found != None: luCommand( rtr, 'vtysh -c "show bgp ipv4 uni" | grep Display', - " 13 route", + " 14 route", "wait", "BGP routes removed", wait, diff --git a/tests/topotests/lib/fe_client.py b/tests/topotests/lib/fe_client.py new file mode 100755 index 0000000000..ec643bb0bf --- /dev/null +++ b/tests/topotests/lib/fe_client.py @@ -0,0 +1,414 @@ +#!/usr/bin/env python +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: GPL-2.0-or-later +# +# November 27 2023, Christian Hopps <chopps@labn.net> +# +# Copyright (c) 2023, LabN Consulting, L.L.C. +# +# noqa: E501 +# +import argparse +import json +import logging +import os +import socket +import struct +import sys +import time +from pathlib import Path + +CWD = os.path.dirname(os.path.realpath(__file__)) + +# This is painful but works if you have installed protobuf would be better if we +# actually built and installed these but ... python packaging. +try: + sys.path.append(os.path.dirname(CWD)) + from munet.base import commander + + commander.cmd_raises(f"protoc --python_out={CWD} -I {CWD}/../../../lib mgmt.proto") +except Exception as error: + logging.error("can't create protobuf definition modules %s", error) + raise + +try: + sys.path[0:0] = "." + import mgmt_pb2 +except Exception as error: + logging.error("can't import proto definition modules %s", error) + raise + +CANDIDATE_DS = mgmt_pb2.DatastoreId.CANDIDATE_DS +OPERATIONAL_DS = mgmt_pb2.DatastoreId.OPERATIONAL_DS +RUNNING_DS = mgmt_pb2.DatastoreId.RUNNING_DS +STARTUP_DS = mgmt_pb2.DatastoreId.STARTUP_DS + +# ===================== +# Native message values +# ===================== + +MGMT_MSG_MARKER_PROTOBUF = b"\000###" +MGMT_MSG_MARKER_NATIVE = b"\001###" + +# +# Native message formats +# +MSG_HDR_FMT = "=H2xIQQ" +HDR_FIELD_CODE = 0 +HDR_FIELD_VSPLIT = 1 +HDR_FIELD_SESS_ID = 2 +HDR_FIELD_REQ_ID = 3 + +MSG_ERROR_FMT = "=h6x" +ERROR_FIELD_ERROR = 0 + +# MSG_GET_TREE_FMT = "=B7x" +# GET_TREE_FIELD_RESULT_TYPE = 0 + +MSG_TREE_DATA_FMT = "=bBB5x" +TREE_DATA_FIELD_PARTIAL_ERROR = 0 +TREE_DATA_FIELD_RESULT_TYPE = 1 +TREE_DATA_FIELD_MORE = 2 + +MSG_GET_DATA_FMT = "=BB6x" +GET_DATA_FIELD_RESULT_TYPE = 0 +GET_DATA_FIELD_FLAGS = 1 +GET_DATA_FLAG_STATE = 0x1 +GET_DATA_FLAG_CONFIG = 0x2 +GET_DATA_FLAG_EXACT = 0x4 + +MSG_NOTIFY_FMT = "=B7x" +NOTIFY_FIELD_RESULT_TYPE = 0 + +# +# Native message codes +# +MSG_CODE_ERROR = 0 +# MSG_CODE_GET_TREE = 1 +MSG_CODE_TREE_DATA = 2 +MSG_CODE_GET_DATA = 3 +MSG_CODE_NOTIFY = 4 + +msg_native_formats = { + MSG_CODE_ERROR: MSG_ERROR_FMT, + # MSG_CODE_GET_TREE: MSG_GET_TREE_FMT, + MSG_CODE_TREE_DATA: MSG_TREE_DATA_FMT, + MSG_CODE_GET_DATA: MSG_GET_DATA_FMT, + MSG_CODE_NOTIFY: MSG_NOTIFY_FMT, +} + + +# Result formats +MSG_FORMAT_XML = 1 +MSG_FORMAT_JSON = 2 +MSG_FORMAT_LYB = 3 + + +def cstr(mdata): + assert mdata[-1] == 0 + return mdata[:-1] + + +class FEClientError(Exception): + pass + + +class PBMessageError(FEClientError): + def __init__(self, msg, errstr): + self.msg = msg + # self.sess_id = mhdr[HDR_FIELD_SESS_ID] + # self.req_id = mhdr[HDR_FIELD_REQ_ID] + self.error = -1 + self.errstr = errstr + super().__init__(f"PBMessageError: {self.errstr}: {msg}") + + +class NativeMessageError(FEClientError): + def __init__(self, mhdr, mfixed, mdata): + self.mhdr = mhdr + self.sess_id = mhdr[HDR_FIELD_SESS_ID] + self.req_id = mhdr[HDR_FIELD_REQ_ID] + self.error = mfixed[0] + self.errstr = cstr(mdata) + super().__init__( + "NativeMessageError: " + f"session {self.sess_id} reqid {self.req_id} " + f"error {self.error}: {self.errstr}" + ) + + +# +# Low-level socket functions +# + + +def recv_wait(sock, size): + """Receive a fixed number of bytes from a stream socket.""" + data = b"" + while len(data) < size: + newdata = sock.recv(size - len(data)) + if not newdata: + raise Exception("Socket closed") + data += newdata + return data + + +def recv_msg(sock): + marker = recv_wait(sock, 4) + assert marker in (MGMT_MSG_MARKER_PROTOBUF, MGMT_MSG_MARKER_NATIVE) + + msize = int.from_bytes(recv_wait(sock, 4), byteorder=sys.byteorder) + assert msize >= 8 + mdata = recv_wait(sock, msize - 8) if msize > 8 else b"" + + return mdata, marker == MGMT_MSG_MARKER_NATIVE + + +def send_msg(sock, marker, mdata): + """Send a mgmtd native message to a stream socket.""" + msize = int.to_bytes(len(mdata) + 8, byteorder=sys.byteorder, length=4) + sock.send(marker) + sock.send(msize) + sock.send(mdata) + + +class Session: + """A session to the mgmtd server.""" + + client_id = 1 + + def __init__(self, sock): + self.sock = sock + self.next_req_id = 1 + + req = mgmt_pb2.FeMessage() + req.register_req.client_name = "test-client" + self.send_pb_msg(req) + logging.debug("Sent FeRegisterReq: %s", req) + + req = mgmt_pb2.FeMessage() + req.session_req.create = 1 + req.session_req.client_conn_id = Session.client_id + Session.client_id += 1 + self.send_pb_msg(req) + logging.debug("Sent FeSessionReq: %s", req) + + reply = self.recv_pb_msg(mgmt_pb2.FeMessage()) + logging.debug("Received FeSessionReply: %s", repr(reply)) + + assert reply.session_reply.success + self.sess_id = reply.session_reply.session_id + + def close(self, clean=True): + if clean: + req = mgmt_pb2.FeMessage() + req.session_req.create = 0 + req.session_req.sess_id = self.sess_id + self.send_pb_msg(req) + self.sock.close() + self.sock = None + + def get_next_req_id(self): + req_id = self.next_req_id + self.next_req_id += 1 + return req_id + + # -------------------------- + # Protobuf message functions + # -------------------------- + + def recv_pb_msg(self, msg): + """Receive a protobuf message.""" + mdata, native = recv_msg(self.sock) + assert not native + + msg.ParseFromString(mdata) + + req = getattr(msg, msg.WhichOneof("message")) + if req.HasField("success"): + if not req.success: + raise PBMessageError(msg, req.error_if_any) + + return msg + + def send_pb_msg(self, msg): + """Send a protobuf message.""" + mdata = msg.SerializeToString() + return send_msg(self.sock, MGMT_MSG_MARKER_PROTOBUF, mdata) + + # ------------------------ + # Native message functions + # ------------------------ + + def recv_native_msg(self): + """Send a native message.""" + mdata, native = recv_msg(self.sock) + assert native + + hlen = struct.calcsize(MSG_HDR_FMT) + hdata = mdata[:hlen] + mhdr = struct.unpack(MSG_HDR_FMT, hdata) + code = mhdr[0] + + if code not in msg_native_formats: + raise Exception(f"Unknown native msg code {code} rcvd") + + mfmt = msg_native_formats[code] + flen = struct.calcsize(mfmt) + fdata = mdata[hlen : hlen + flen] + mfixed = struct.unpack(mfmt, fdata) + mdata = mdata[hlen + flen :] + + if code == MSG_ERROR_FMT: + raise NativeMessageError(mhdr, mfixed, mdata) + + return mhdr, mfixed, mdata + + def send_native_msg(self, mdata): + """Send a native message.""" + return send_msg(self.sock, MGMT_MSG_MARKER_NATIVE, mdata) + + def get_native_msg_header(self, msg_code): + req_id = self.get_next_req_id() + hdata = struct.pack(MSG_HDR_FMT, msg_code, 0, self.sess_id, req_id) + return hdata, req_id + + # ----------------------- + # Front-end API Fountains + # ----------------------- + + def lock(self, lock=True, ds_id=mgmt_pb2.CANDIDATE_DS): + req = mgmt_pb2.FeMessage() + req.lockds_req.session_id = self.sess_id + req.lockds_req.req_id = self.get_next_req_id() + req.lockds_req.ds_id = ds_id + req.lockds_req.lock = lock + self.send_pb_msg(req) + logging.debug("Sent LockDsReq: %s", req) + + reply = self.recv_pb_msg(mgmt_pb2.FeMessage()) + logging.debug("Received Reply: %s", repr(reply)) + assert reply.lockds_reply.success + + def get_data(self, query, data=True, config=False): + # Create the message + mdata, req_id = self.get_native_msg_header(MSG_CODE_GET_DATA) + flags = GET_DATA_FLAG_STATE if data else 0 + flags |= GET_DATA_FLAG_CONFIG if config else 0 + mdata += struct.pack(MSG_GET_DATA_FMT, MSG_FORMAT_JSON, flags) + mdata += query.encode("utf-8") + b"\x00" + + self.send_native_msg(mdata) + logging.debug("Sent GET-TREE") + + mhdr, mfixed, mdata = self.recv_native_msg() + assert mdata[-1] == 0 + result = mdata[:-1].decode("utf-8") + + logging.debug("Received GET: %s: %s", mfixed, mdata) + return result + + # def subscribe(self, notif_xpath): + # # Create the message + # mdata, req_id = self.get_native_msg_header(MSG_CODE_SUBSCRIBE) + # mdata += struct.pack(MSG_SUBSCRIBE_FMT, MSG_FORMAT_JSON) + # mdata += notif_xpath.encode("utf-8") + b"\x00" + + # self.send_native_msg(mdata) + # logging.debug("Sent SUBSCRIBE") + + def recv_notify(self, xpaths=None): + while True: + logging.debug("Waiting for Notify Message") + mhdr, mfixed, mdata = self.recv_native_msg() + assert mdata[-1] == 0 + result = mdata[:-1].decode("utf-8") + if mhdr[HDR_FIELD_CODE] == MSG_CODE_NOTIFY: + logging.debug("Received Notify Message: %s: %s", mfixed, mdata) + else: + raise Exception(f"Received NON-NOTIFY Message: {mfixed}: {mdata}") + if not xpaths: + return result + js = json.loads(result) + key = [x for x in js.keys()][0] + for xpath in xpaths: + if key.startswith(xpath): + return result + logging.debug("'%s' didn't match xpath filters", key) + + +def __parse_args(): + MPATH = "/var/run/frr/mgmtd_fe.sock" + parser = argparse.ArgumentParser() + parser.add_argument( + "-l", "--listen", nargs="*", metavar="XPATH", help="xpath[s] to listen for" + ) + parser.add_argument( + "--notify-count", + type=int, + default=1, + help="Number of notifications to listen for 0 for infinite", + ) + parser.add_argument( + "-b", "--both", action="store_true", help="return both config and data" + ) + parser.add_argument( + "-c", "--config-only", action="store_true", help="return config only" + ) + parser.add_argument( + "-q", "--query", nargs="+", metavar="XPATH", help="xpath[s] to query" + ) + parser.add_argument("-s", "--server", default=MPATH, help="path to server socket") + parser.add_argument("-v", "--verbose", action="store_true", help="Be verbose") + args = parser.parse_args() + + level = logging.DEBUG if args.verbose else logging.INFO + logging.basicConfig(level=level, format="%(asctime)s %(levelname)s: %(message)s") + + return args + + +def __server_connect(spath): + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + logging.debug("Connecting to server on %s", spath) + while ec := sock.connect_ex(str(spath)): + logging.warn("retry server connection in .5s (%s)", os.strerror(ec)) + time.sleep(0.5) + logging.info("Connected to server on %s", spath) + return sock + + +def __main(): + args = __parse_args() + sock = __server_connect(Path(args.server)) + sess = Session(sock) + + if args.query: + # Performa an xpath query + # query = "/frr-interface:lib/interface/state/mtu" + for query in args.query: + logging.info("Sending query: %s", query) + result = sess.get_data( + query, data=not args.config_only, config=(args.both or args.config_only) + ) + print(result) + + if args.listen is not None: + i = args.notify_count + while i > 0 or args.notify_count == 0: + notif = sess.recv_notify(args.listen) + print(notif) + i -= 1 + + +def main(): + try: + __main() + except KeyboardInterrupt: + logging.info("Exiting") + except Exception as error: + logging.error("Unexpected error exiting: %s", error, exc_info=True) + + +if __name__ == "__main__": + main() diff --git a/tests/topotests/mgmt_fe_client/fe_client.py b/tests/topotests/mgmt_fe_client/fe_client.py deleted file mode 100644 index 04b4184e5b..0000000000 --- a/tests/topotests/mgmt_fe_client/fe_client.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 eval: (blacken-mode 1) -*- -# SPDX-License-Identifier: GPL-2.0-or-later -# -# November 27 2023, Christian Hopps <chopps@labn.net> -# -# Copyright (c) 2023, LabN Consulting, L.L.C. -# -# noqa: E501 -# -import argparse -import errno -import logging -import os -import socket -import sys -import time -from pathlib import Path - -import mgmt_pb2 - -MGMT_MSG_MARKER_PROTOBUF = b"\000###" -MGMT_MSG_MARKER_NATIVE = b"\001###" - - -def __parse_args(): - MPATH = "/var/run/frr/mgmtd_fe.sock" - parser = argparse.ArgumentParser() - parser.add_argument("--verbose", action="store_true", help="Be verbose") - parser.add_argument("--server", default=MPATH, help="path to server socket") - args = parser.parse_args() - - level = logging.DEBUG if args.verbose else logging.INFO - logging.basicConfig(level=level, format="%(asctime)s %(levelname)s: %(message)s") - - return args - - -def __server_connect(spath): - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - logging.debug("Connecting to server on %s", spath) - while ec := sock.connect_ex(str(spath)): - logging.warn("retry server connection in .5s (%s)", os.strerror(ec)) - time.sleep(0.5) - logging.info("Connected to server on %s", spath) - return sock - - -def mgmt_pb_recv_msg(sock, msg): - """Receive a mgmtd protobuf message from a stream socket.""" - marker = sock.recv(4) - assert marker in (MGMT_MSG_MARKER_PROTOBUF, MGMT_MSG_MARKER_NATIVE) - - msize = int.from_bytes(sock.recv(4), byteorder="big") - mdata = sock.recv(msize) - - msg.ParseFromString(mdata) - return msg - - -def mgmt_pb_send_msg(sock, msg): - """Send a mgmtd protobuf message from a stream socket.""" - marker = MGMT_MSG_MARKER_PROTOBUF - mdata = msg.SerializeToString() - msize = int.to_bytes(len(mdata), byteorder="big", length=4) - sock.send(marker) - sock.send(msize) - sock.send(mdata) - - -def create_session(sock): - req = mgmt_pb2.FeRegisterReq() - req.client_name = "test-client" - mgmt_pb_send_msg(sock, req) - logging.debug("Sent FeRegisterReq: %s", req) - - req = mgmt_pb2.FeSessionReq() - req.create = 1 - req.client_conn_id = 1 - mgmt_pb_send_msg(sock, req) - logging.debug("Sent FeSessionReq: %s", req) - - reply = mgmt_pb_recv_msg(sock, mgmt_pb2.FeSessionReply()) - logging.debug("Received FeSessionReply: %s", reply) - - -def __main(): - args = __parse_args() - sock = __server_connect(Path(args.server)) - create_session(sock) - - -def main(): - try: - __main() - except KeyboardInterrupt: - logging.info("Exiting") - except Exception as error: - logging.error("Unexpected error exiting: %s", error, exc_info=True) - - -if __name__ == "__main__": - main() diff --git a/tests/topotests/mgmt_fe_client/test_client.py b/tests/topotests/mgmt_fe_client/test_client.py index 8383e23bb6..b5a74c60ac 100644 --- a/tests/topotests/mgmt_fe_client/test_client.py +++ b/tests/topotests/mgmt_fe_client/test_client.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 eval: (blacken-mode 1) -*- # SPDX-License-Identifier: ISC # diff --git a/tests/topotests/mgmt_notif/oper.py b/tests/topotests/mgmt_notif/oper.py new file mode 120000 index 0000000000..924439251a --- /dev/null +++ b/tests/topotests/mgmt_notif/oper.py @@ -0,0 +1 @@ +../mgmt_oper/oper.py
\ No newline at end of file diff --git a/tests/topotests/mgmt_notif/r1/frr.conf b/tests/topotests/mgmt_notif/r1/frr.conf new file mode 100644 index 0000000000..47e73956cf --- /dev/null +++ b/tests/topotests/mgmt_notif/r1/frr.conf @@ -0,0 +1,27 @@ +log timestamp precision 6 +log file frr.log + +no debug memstats-at-exit + +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks + +debug mgmt backend datastore frontend transaction +debug mgmt client frontend +debug mgmt client backend + +ip route 11.11.11.11/32 lo + +interface r1-eth0 + ip address 1.1.1.1/24 + ip rip authentication string foo + ip rip authentication mode text +exit + +router rip + network 1.1.1.0/24 + timers basic 5 15 10 + redistribute static +exit diff --git a/tests/topotests/mgmt_notif/r2/frr.conf b/tests/topotests/mgmt_notif/r2/frr.conf new file mode 100644 index 0000000000..cd052011e0 --- /dev/null +++ b/tests/topotests/mgmt_notif/r2/frr.conf @@ -0,0 +1,27 @@ +log timestamp precision 6 +log file frr.log + +no debug memstats-at-exit + +debug northbound notifications +debug northbound libyang +debug northbound events +debug northbound callbacks + +debug mgmt backend datastore frontend transaction +debug mgmt client frontend +debug mgmt client backend + +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 mode text +exit + +router rip + network 1.1.1.0/24 + timers basic 5 15 10 + redistribute static +exit
\ No newline at end of file diff --git a/tests/topotests/mgmt_notif/test_notif.py b/tests/topotests/mgmt_notif/test_notif.py new file mode 100644 index 0000000000..873b82d999 --- /dev/null +++ b/tests/topotests/mgmt_notif/test_notif.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 eval: (blacken-mode 1) -*- +# SPDX-License-Identifier: ISC +# +# January 23 2024, Christian Hopps <chopps@labn.net> +# +# Copyright (c) 2024, LabN Consulting, L.L.C. +# + +""" +Test YANG Notifications +""" +import json +import logging +import os + +import pytest +from lib.topogen import Topogen +from lib.topotest import json_cmp +from oper import check_kernel_32 + +pytestmark = [pytest.mark.ripd, pytest.mark.staticd, pytest.mark.mgmtd] + +CWD = os.path.dirname(os.path.realpath(__file__)) + + +@pytest.fixture(scope="module") +def tgen(request): + "Setup/Teardown the environment and provide tgen argument to tests" + + topodef = { + "s1": ("r1", "r2"), + } + + tgen = Topogen(topodef, request.module.__name__) + tgen.start_topology() + + router_list = tgen.routers() + for rname, router in router_list.items(): + router.load_frr_config("frr.conf") + + tgen.start_router() + yield tgen + tgen.stop_topology() + + +def test_oper_simple(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") + + 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) + assert result is None diff --git a/tests/topotests/mgmt_oper/r1/frr-simple.conf b/tests/topotests/mgmt_oper/r1/frr-simple.conf index d262afe359..73df6b97b2 100644 --- a/tests/topotests/mgmt_oper/r1/frr-simple.conf +++ b/tests/topotests/mgmt_oper/r1/frr-simple.conf @@ -15,6 +15,7 @@ debug mgmt client backend interface r1-eth0 ip address 1.1.1.1/24 description r1-eth0-desc + evpn mh es-df-pref 32767 exit interface r1-eth1 vrf red diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json index 9289759274..e48002e672 100644 --- a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-only-config.json @@ -10,7 +10,10 @@ "ip": "1.1.1.1", "prefix-length": 24 } - ] + ], + "evpn-mh": { + "df-preference": 32767 + } } } ] diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all-tag.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all-tag.json new file mode 100644 index 0000000000..caee164468 --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all-tag.json @@ -0,0 +1,13 @@ +{ + "frr-zebra:evpn-mh": { + "df-preference": 32767, + "bypass": false, + "@bypass": { + "ietf-netconf-with-defaults:default": true + }, + "uplink": false, + "@uplink": { + "ietf-netconf-with-defaults:default": true + } + } +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all.json new file mode 100644 index 0000000000..07ba53b8bc --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-all.json @@ -0,0 +1,7 @@ +{ + "frr-zebra:evpn-mh": { + "df-preference": 32767, + "bypass": false, + "uplink": false + } +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-explicit.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-explicit.json new file mode 100644 index 0000000000..1779d1cd4e --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-explicit.json @@ -0,0 +1,5 @@ +{ + "frr-zebra:evpn-mh": { + "df-preference": 32767 + } +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-trim.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-trim.json new file mode 100644 index 0000000000..efd7e8c684 --- /dev/null +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-wd-trim.json @@ -0,0 +1,3 @@ +{ + "frr-zebra:evpn-mh": {} +} diff --git a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json index ef9e005619..84ad82c058 100644 --- a/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json +++ b/tests/topotests/mgmt_oper/simple-results/result-intf-eth0-with-config.json @@ -20,6 +20,9 @@ "prefix-length": 24 } ], + "evpn-mh": { + "df-preference": 32767 + }, "state": { "up-count": 0, "down-count": 0 diff --git a/tests/topotests/mgmt_oper/test_simple.py b/tests/topotests/mgmt_oper/test_simple.py index a52d125ecf..3b115f6238 100644 --- a/tests/topotests/mgmt_oper/test_simple.py +++ b/tests/topotests/mgmt_oper/test_simple.py @@ -146,6 +146,27 @@ def test_oper_simple(tgen): '/frr-interface:lib/interface[name="r1-eth0"]/state/mtu', "simple-results/result-intf-state-mtu.json", ), + # with-defaults + ( + '/frr-interface:lib/interface[name="r1-eth0"]/frr-zebra:zebra/evpn-mh', + "simple-results/result-intf-eth0-wd-explicit.json", + "with-config exact", + ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]/frr-zebra:zebra/evpn-mh', + "simple-results/result-intf-eth0-wd-trim.json", + "with-config exact with-defaults trim", + ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]/frr-zebra:zebra/evpn-mh', + "simple-results/result-intf-eth0-wd-all.json", + "with-config exact with-defaults all", + ), + ( + '/frr-interface:lib/interface[name="r1-eth0"]/frr-zebra:zebra/evpn-mh', + "simple-results/result-intf-eth0-wd-all-tag.json", + "with-config exact with-defaults all-tag", + ), ] r1 = tgen.gears["r1"].net diff --git a/tools/gen_northbound_callbacks.c b/tools/gen_northbound_callbacks.c index 6188559010..a879811363 100644 --- a/tools/gen_northbound_callbacks.c +++ b/tools/gen_northbound_callbacks.c @@ -26,18 +26,21 @@ static void __attribute__((noreturn)) usage(int status) static struct nb_callback_info { int operation; bool optional; + bool need_config_write; char return_type[32]; char return_value[32]; char arguments[128]; } nb_callbacks[] = { { .operation = NB_CB_CREATE, + .need_config_write = true, .return_type = "int ", .return_value = "NB_OK", .arguments = "struct nb_cb_create_args *args", }, { .operation = NB_CB_MODIFY, + .need_config_write = true, .return_type = "int ", .return_value = "NB_OK", .arguments = "struct nb_cb_modify_args *args", @@ -97,6 +100,16 @@ static struct nb_callback_info { }, }; +/* + * Special-purpose info block for the cli-config-write callback. This + * is different enough from the config-oriented callbacks that it doesn't + * really fit in the array above. + */ +static struct nb_callback_info nb_config_write = { + .return_type = "void ", + .arguments = "struct vty *vty, const struct lyd_node *dnode, bool show_defaults", +}; + static void replace_hyphens_by_underscores(char *str) { char *p; @@ -135,14 +148,53 @@ static void generate_callback_name(const struct lysc_node *snode, replace_hyphens_by_underscores(buffer); } +static void generate_config_write_cb_name(const struct lysc_node *snode, + char *buffer, size_t size) +{ + struct list *snodes; + struct listnode *ln; + + buffer[0] = '\0'; + + snodes = list_new(); + for (; snode; snode = snode->parent) { + /* Skip schema-only snodes. */ + if (CHECK_FLAG(snode->nodetype, LYS_USES | LYS_CHOICE | LYS_CASE + | LYS_INPUT + | LYS_OUTPUT)) + continue; + + listnode_add_head(snodes, (void *)snode); + } + + for (ALL_LIST_ELEMENTS_RO(snodes, ln, snode)) { + strlcat(buffer, snode->name, size); + strlcat(buffer, "_", size); + } + + strlcat(buffer, "cli_write", size); + + list_delete(&snodes); + + replace_hyphens_by_underscores(buffer); +} + static void generate_prototype(const struct nb_callback_info *ncinfo, const char *cb_name) { printf("%s%s(%s);\n", ncinfo->return_type, cb_name, ncinfo->arguments); } +static void generate_config_write_prototype(const struct nb_callback_info *ncinfo, + const char *cb_name) +{ + printf("%s%s(%s);\n", ncinfo->return_type, cb_name, ncinfo->arguments); +} + static int generate_prototypes(const struct lysc_node *snode, void *arg) { + bool need_config_write = true; + switch (snode->nodetype) { case LYS_CONTAINER: case LYS_LEAF: @@ -166,6 +218,15 @@ static int generate_prototypes(const struct lysc_node *snode, void *arg) generate_callback_name(snode, cb->operation, cb_name, sizeof(cb_name)); generate_prototype(cb, cb_name); + + if (cb->need_config_write && need_config_write) { + generate_config_write_cb_name(snode, cb_name, + sizeof(cb_name)); + generate_config_write_prototype(&nb_config_write, + cb_name); + + need_config_write = false; + } } return YANG_ITER_CONTINUE; @@ -201,9 +262,22 @@ static void generate_callback(const struct nb_callback_info *ncinfo, printf("\treturn %s;\n}\n\n", ncinfo->return_value); } +static void generate_config_write_callback(const struct nb_callback_info *ncinfo, + const char *cb_name) +{ + printf("%s%s%s(%s)\n{\n", static_cbs ? "static " : "", + ncinfo->return_type, cb_name, ncinfo->arguments); + + /* Add a comment, since these callbacks may not all be needed. */ + printf("\t/* TODO: this cli callback is optional; the cli output may not need to be done at each node. */\n"); + + printf("}\n\n"); +} + static int generate_callbacks(const struct lysc_node *snode, void *arg) { bool first = true; + bool need_config_write = true; switch (snode->nodetype) { case LYS_CONTAINER: @@ -241,6 +315,15 @@ static int generate_callbacks(const struct lysc_node *snode, void *arg) generate_callback_name(snode, cb->operation, cb_name, sizeof(cb_name)); generate_callback(cb, cb_name); + + if (cb->need_config_write && need_config_write) { + generate_config_write_cb_name(snode, cb_name, + sizeof(cb_name)); + generate_config_write_callback(&nb_config_write, + cb_name); + + need_config_write = false; + } } return YANG_ITER_CONTINUE; @@ -249,6 +332,10 @@ static int generate_callbacks(const struct lysc_node *snode, void *arg) static int generate_nb_nodes(const struct lysc_node *snode, void *arg) { bool first = true; + char cb_name[BUFSIZ]; + char xpath[XPATH_MAXLEN]; + bool config_pass = *(bool *)arg; + bool need_config_write = true; switch (snode->nodetype) { case LYS_CONTAINER: @@ -262,32 +349,53 @@ static int generate_nb_nodes(const struct lysc_node *snode, void *arg) return YANG_ITER_CONTINUE; } + /* We generate two types of structs currently; behavior is a little + * different between the types. + */ for (struct nb_callback_info *cb = &nb_callbacks[0]; cb->operation != -1; cb++) { - char cb_name[BUFSIZ]; if (cb->optional || !nb_cb_operation_is_valid(cb->operation, snode)) continue; - if (first) { - char xpath[XPATH_MAXLEN]; + if (config_pass) { + if (first) { + yang_snode_get_path(snode, YANG_PATH_DATA, xpath, + sizeof(xpath)); - yang_snode_get_path(snode, YANG_PATH_DATA, xpath, - sizeof(xpath)); + printf("\t\t{\n" + "\t\t\t.xpath = \"%s\",\n", + xpath); + printf("\t\t\t.cbs = {\n"); + first = false; + } - printf("\t\t{\n" - "\t\t\t.xpath = \"%s\",\n", - xpath); - printf("\t\t\t.cbs = {\n"); - first = false; - } + generate_callback_name(snode, cb->operation, cb_name, + sizeof(cb_name)); + printf("\t\t\t\t.%s = %s,\n", + nb_cb_operation_name(cb->operation), + cb_name); + } else if (cb->need_config_write && need_config_write) { + if (first) { + yang_snode_get_path(snode, + YANG_PATH_DATA, + xpath, + sizeof(xpath)); + + printf("\t\t{\n" + "\t\t\t.xpath = \"%s\",\n", + xpath); + printf("\t\t\t.cbs = {\n"); + first = false; + } - generate_callback_name(snode, cb->operation, cb_name, - sizeof(cb_name)); - printf("\t\t\t\t.%s = %s,\n", - nb_cb_operation_name(cb->operation), - cb_name); + generate_config_write_cb_name(snode, cb_name, + sizeof(cb_name)); + printf("\t\t\t\t.cli_show = %s,\n", cb_name); + + need_config_write = false; + } } if (!first) { @@ -305,6 +413,7 @@ int main(int argc, char *argv[]) char module_name_underscores[64]; struct stat st; int opt; + bool config_pass; while ((opt = getopt(argc, argv, "hp:s")) != -1) { switch (opt) { @@ -357,6 +466,11 @@ int main(int argc, char *argv[]) /* Create a nb_node for all YANG schema nodes. */ nb_nodes_create(); + /* Emit bare-bones license line (and fool the checkpatch regex + * that triggers a warning). + */ + printf("// SPDX-" "License-Identifier: GPL-2.0-or-later\n\n"); + /* Generate callback prototypes. */ if (!static_cbs) { printf("/* prototypes */\n"); @@ -371,13 +485,38 @@ int main(int argc, char *argv[]) sizeof(module_name_underscores)); replace_hyphens_by_underscores(module_name_underscores); - /* Generate frr_yang_module_info array. */ + /* + * We're going to generate two structs here, two arrays of callbacks: + * first one with config-handling callbacks, then a second struct with + * config-output-oriented callbacks. + */ + + /* Generate frr_yang_module_info array, with config-handling callbacks */ + config_pass = true; printf("/* clang-format off */\n" - "const struct frr_yang_module_info %s_info = {\n" + "const struct frr_yang_module_info %s_nb_info = {\n" "\t.name = \"%s\",\n" "\t.nodes = {\n", module_name_underscores, module->name); - yang_snodes_iterate(module->info, generate_nb_nodes, 0, NULL); + yang_snodes_iterate(module->info, generate_nb_nodes, 0, &config_pass); + + /* Emit terminator element */ + printf("\t\t{\n" + "\t\t\t.xpath = NULL,\n" + "\t\t},\n"); + printf("\t}\n" + "};\n"); + + /* Generate second array, with output-oriented callbacks. */ + config_pass = false; + printf("\n/* clang-format off */\n" + "const struct frr_yang_module_info %s_cli_info = {\n" + "\t.name = \"%s\",\n" + "\t.nodes = {\n", + module_name_underscores, module->name); + yang_snodes_iterate(module->info, generate_nb_nodes, 0, &config_pass); + + /* Emit terminator element */ printf("\t\t{\n" "\t\t\t.xpath = NULL,\n" "\t\t},\n"); diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c index 940b63b0e1..4cb46b87a5 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -3120,7 +3120,7 @@ DEFUN (vtysh_show_error_code, } /* Northbound. */ -DEFUN_HIDDEN (show_config_running, +DEFUN (show_config_running, show_config_running_cmd, "show configuration running\ [<json|xml> [translate WORD]]\ diff --git a/yang/ietf/ietf-netconf-acm.yang b/yang/ietf/ietf-netconf-acm.yang new file mode 100644 index 0000000000..f7e02f280e --- /dev/null +++ b/yang/ietf/ietf-netconf-acm.yang @@ -0,0 +1,464 @@ +module ietf-netconf-acm { + + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm"; + + prefix nacm; + + import ietf-yang-types { + prefix yang; + } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: <https://datatracker.ietf.org/wg/netconf/> + WG List: <mailto:netconf@ietf.org> + + Author: Andy Bierman + <mailto:andy@yumaworks.com> + + Author: Martin Bjorklund + <mailto:mbj@tail-f.com>"; + + description + "Network Configuration Access Control Model. + + Copyright (c) 2012 - 2018 IETF Trust and the persons + identified as authors of the code. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD + License set forth in Section 4.c of the IETF Trust's + Legal Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 8341; see + the RFC itself for full legal notices."; + + revision 2018-02-14 { + description + "Added support for YANG 1.1 actions and notifications tied to + data nodes. Clarified how NACM extensions can be used by + other data models."; + reference + "RFC 8341: Network Configuration Access Control Model"; + } + + revision 2012-02-22 { + description + "Initial version."; + reference + "RFC 6536: Network Configuration Protocol (NETCONF) + Access Control Model"; + } + + /* + * Extension statements + */ + + extension default-deny-write { + description + "Used to indicate that the data model node + represents a sensitive security system parameter. + + If present, the NETCONF server will only allow the designated + 'recovery session' to have write access to the node. An + explicit access control rule is required for all other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. + + The 'default-deny-write' extension MAY appear within a data + definition statement. It is ignored otherwise."; + } + + extension default-deny-all { + description + "Used to indicate that the data model node + controls a very sensitive security system parameter. + + If present, the NETCONF server will only allow the designated + 'recovery session' to have read, write, or execute access to + the node. An explicit access control rule is required for all + other users. + + If the NACM module is used, then it must be enabled (i.e., + /nacm/enable-nacm object equals 'true'), or this extension + is ignored. + + The 'default-deny-all' extension MAY appear within a data + definition statement, 'rpc' statement, or 'notification' + statement. It is ignored otherwise."; + } + + /* + * Derived types + */ + + typedef user-name-type { + type string { + length "1..max"; + } + description + "General-purpose username string."; + } + + typedef matchall-string-type { + type string { + pattern '\*'; + } + description + "The string containing a single asterisk '*' is used + to conceptually represent all possible values + for the particular leaf using this data type."; + } + + typedef access-operations-type { + type bits { + bit create { + description + "Any protocol operation that creates a + new data node."; + } + bit read { + description + "Any protocol operation or notification that + returns the value of a data node."; + } + bit update { + description + "Any protocol operation that alters an existing + data node."; + } + bit delete { + description + "Any protocol operation that removes a data node."; + } + bit exec { + description + "Execution access to the specified protocol operation."; + } + } + description + "Access operation."; + } + + typedef group-name-type { + type string { + length "1..max"; + pattern '[^\*].*'; + } + description + "Name of administrative group to which + users can be assigned."; + } + + typedef action-type { + type enumeration { + enum permit { + description + "Requested action is permitted."; + } + enum deny { + description + "Requested action is denied."; + } + } + description + "Action taken by the server when a particular + rule matches."; + } + + typedef node-instance-identifier { + type yang:xpath1.0; + description + "Path expression used to represent a special + data node, action, or notification instance-identifier + string. + + A node-instance-identifier value is an + unrestricted YANG instance-identifier expression. + All the same rules as an instance-identifier apply, + except that predicates for keys are optional. If a key + predicate is missing, then the node-instance-identifier + represents all possible server instances for that key. + + This XML Path Language (XPath) expression is evaluated in the + following context: + + o The set of namespace declarations are those in scope on + the leaf element where this type is used. + + o The set of variable bindings contains one variable, + 'USER', which contains the name of the user of the + current session. + + o The function library is the core function library, but + note that due to the syntax restrictions of an + instance-identifier, no functions are allowed. + + o The context node is the root node in the data tree. + + The accessible tree includes actions and notifications tied + to data nodes."; + } + + /* + * Data definition statements + */ + + container nacm { + nacm:default-deny-all; + + description + "Parameters for NETCONF access control model."; + + leaf enable-nacm { + type boolean; + default "true"; + description + "Enables or disables all NETCONF access control + enforcement. If 'true', then enforcement + is enabled. If 'false', then enforcement + is disabled."; + } + + leaf read-default { + type action-type; + default "permit"; + description + "Controls whether read access is granted if + no appropriate rule is found for a + particular read request."; + } + + leaf write-default { + type action-type; + default "deny"; + description + "Controls whether create, update, or delete access + is granted if no appropriate rule is found for a + particular write request."; + } + + leaf exec-default { + type action-type; + default "permit"; + description + "Controls whether exec access is granted if no appropriate + rule is found for a particular protocol operation request."; + } + + leaf enable-external-groups { + type boolean; + default "true"; + description + "Controls whether the server uses the groups reported by the + NETCONF transport layer when it assigns the user to a set of + NACM groups. If this leaf has the value 'false', any group + names reported by the transport layer are ignored by the + server."; + } + + leaf denied-operations { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request was denied."; + } + + leaf denied-data-writes { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that a + protocol operation request to alter + a configuration datastore was denied."; + } + + leaf denied-notifications { + type yang:zero-based-counter32; + config false; + mandatory true; + description + "Number of times since the server last restarted that + a notification was dropped for a subscription because + access to the event type was denied."; + } + + container groups { + description + "NETCONF access control groups."; + + list group { + key name; + + description + "One NACM group entry. This list will only contain + configured entries, not any entries learned from + any transport protocols."; + + leaf name { + type group-name-type; + description + "Group name associated with this entry."; + } + + leaf-list user-name { + type user-name-type; + description + "Each entry identifies the username of + a member of the group associated with + this entry."; + } + } + } + + list rule-list { + key name; + ordered-by user; + description + "An ordered collection of access control rules."; + + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule-list."; + } + leaf-list group { + type union { + type matchall-string-type; + type group-name-type; + } + description + "List of administrative groups that will be + assigned the associated access rights + defined by the 'rule' list. + + The string '*' indicates that all groups apply to the + entry."; + } + + list rule { + key name; + ordered-by user; + description + "One access control rule. + + Rules are processed in user-defined order until a match is + found. A rule matches if 'module-name', 'rule-type', and + 'access-operations' match the request. If a rule + matches, the 'action' leaf determines whether or not + access is granted."; + + leaf name { + type string { + length "1..max"; + } + description + "Arbitrary name assigned to the rule."; + } + + leaf module-name { + type union { + type matchall-string-type; + type string; + } + default "*"; + description + "Name of the module associated with this rule. + + This leaf matches if it has the value '*' or if the + object being accessed is defined in the module with the + specified module name."; + } + choice rule-type { + description + "This choice matches if all leafs present in the rule + match the request. If no leafs are present, the + choice matches all requests."; + case protocol-operation { + leaf rpc-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if + its value equals the requested protocol operation + name."; + } + } + case notification { + leaf notification-name { + type union { + type matchall-string-type; + type string; + } + description + "This leaf matches if it has the value '*' or if its + value equals the requested notification name."; + } + } + + case data-node { + leaf path { + type node-instance-identifier; + mandatory true; + description + "Data node instance-identifier associated with the + data node, action, or notification controlled by + this rule. + + Configuration data or state data + instance-identifiers start with a top-level + data node. A complete instance-identifier is + required for this type of path value. + + The special value '/' refers to all possible + datastore contents."; + } + } + } + + leaf access-operations { + type union { + type matchall-string-type; + type access-operations-type; + } + default "*"; + description + "Access operations associated with this rule. + + This leaf matches if it has the value '*' or if the + bit corresponding to the requested operation is set."; + } + + leaf action { + type action-type; + mandatory true; + description + "The access control action associated with the + rule. If a rule has been determined to match a + particular request, then this object is used + to determine whether to permit or deny the + request."; + } + + leaf comment { + type string; + description + "A textual description of the access rule."; + } + } + } + } +} diff --git a/yang/ietf/ietf-netconf-with-defaults.yang b/yang/ietf/ietf-netconf-with-defaults.yang new file mode 100644 index 0000000000..05ff399fd7 --- /dev/null +++ b/yang/ietf/ietf-netconf-with-defaults.yang @@ -0,0 +1,139 @@ +module ietf-netconf-with-defaults { + + namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-with-defaults"; + + prefix ncwd; + + import ietf-netconf { prefix nc; } + + organization + "IETF NETCONF (Network Configuration Protocol) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + + WG List: <netconf@ietf.org> + + WG Chair: Bert Wijnen + <bertietf@bwijnen.net> + + WG Chair: Mehmet Ersue + <mehmet.ersue@nsn.com> + + Editor: Andy Bierman + <andy.bierman@brocade.com> + + Editor: Balazs Lengyel + <balazs.lengyel@ericsson.com>"; + + description + "This module defines an extension to the NETCONF protocol + that allows the NETCONF client to control how default + values are handled by the server in particular NETCONF + operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6243; see + the RFC itself for full legal notices."; + + revision 2011-06-01 { + description + "Initial version."; + reference + "RFC 6243: With-defaults Capability for NETCONF"; + } + + typedef with-defaults-mode { + description + "Possible modes to report default data."; + reference + "RFC 6243; Section 3."; + type enumeration { + enum report-all { + description + "All default data is reported."; + reference + "RFC 6243; Section 3.1"; + } + enum report-all-tagged { + description + "All default data is reported. + Any nodes considered to be default data + will contain a 'default' XML attribute, + set to 'true' or '1'."; + reference + "RFC 6243; Section 3.4"; + } + enum trim { + description + "Values are not reported if they contain the default."; + reference + "RFC 6243; Section 3.2"; + } + enum explicit { + description + "Report values that contain the definition of + explicitly set data."; + reference + "RFC 6243; Section 3.3"; + } + } + } + + grouping with-defaults-parameters { + description + "Contains the <with-defaults> parameter for control + of defaults in NETCONF retrieval operations."; + + leaf with-defaults { + description + "The explicit defaults processing mode requested."; + reference + "RFC 6243; Section 4.5.1"; + + type with-defaults-mode; + } + } + + // extending the get-config operation + augment /nc:get-config/nc:input { + description + "Adds the <with-defaults> parameter to the + input of the NETCONF <get-config> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } + + // extending the get operation + augment /nc:get/nc:input { + description + "Adds the <with-defaults> parameter to + the input of the NETCONF <get> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } + + // extending the copy-config operation + augment /nc:copy-config/nc:input { + description + "Adds the <with-defaults> parameter to + the input of the NETCONF <copy-config> operation."; + reference + "RFC 6243; Section 4.5.1"; + + uses with-defaults-parameters; + } +} diff --git a/yang/ietf/ietf-netconf.yang b/yang/ietf/ietf-netconf.yang new file mode 100644 index 0000000000..93927f1c80 --- /dev/null +++ b/yang/ietf/ietf-netconf.yang @@ -0,0 +1,933 @@ +module ietf-netconf { + + // the namespace for NETCONF XML definitions is unchanged + // from RFC 4741, which this document replaces + namespace "urn:ietf:params:xml:ns:netconf:base:1.0"; + + prefix nc; + + import ietf-inet-types { + prefix inet; + } + + import ietf-netconf-acm { prefix nacm; } + + organization + "IETF NETCONF (Network Configuration) Working Group"; + + contact + "WG Web: <http://tools.ietf.org/wg/netconf/> + WG List: <netconf@ietf.org> + + WG Chair: Bert Wijnen + <bertietf@bwijnen.net> + + WG Chair: Mehmet Ersue + <mehmet.ersue@nsn.com> + + Editor: Martin Bjorklund + <mbj@tail-f.com> + + Editor: Juergen Schoenwaelder + <j.schoenwaelder@jacobs-university.de> + + Editor: Andy Bierman + <andy.bierman@brocade.com>"; + description + "NETCONF Protocol Data Types and Protocol Operations. + + Copyright (c) 2011 IETF Trust and the persons identified as + the document authors. All rights reserved. + + Redistribution and use in source and binary forms, with or + without modification, is permitted pursuant to, and subject + to the license terms contained in, the Simplified BSD License + set forth in Section 4.c of the IETF Trust's Legal Provisions + Relating to IETF Documents + (http://trustee.ietf.org/license-info). + + This version of this YANG module is part of RFC 6241; see + the RFC itself for full legal notices."; + + revision 2011-06-01 { + description + "Initial revision; + 2013-09-29: Updated to include NACM attributes, + as specified in RFC 6536: sec 3.2.5 and 3.2.8"; + reference + "RFC 6241: Network Configuration Protocol"; + } + + extension get-filter-element-attributes { + description + "If this extension is present within an 'anyxml' + statement named 'filter', which must be conceptually + defined within the RPC input section for the <get> + and <get-config> protocol operations, then the + following unqualified XML attribute is supported + within the <filter> element, within a <get> or + <get-config> protocol operation: + + type : optional attribute with allowed + value strings 'subtree' and 'xpath'. + If missing, the default value is 'subtree'. + + If the 'xpath' feature is supported, then the + following unqualified XML attribute is + also supported: + + select: optional attribute containing a + string representing an XPath expression. + The 'type' attribute must be equal to 'xpath' + if this attribute is present."; + } + + // NETCONF capabilities defined as features + feature writable-running { + description + "NETCONF :writable-running capability; + If the server advertises the :writable-running + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.2"; + } + + feature candidate { + description + "NETCONF :candidate capability; + If the server advertises the :candidate + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.3"; + } + + feature confirmed-commit { + if-feature candidate; + description + "NETCONF :confirmed-commit:1.1 capability; + If the server advertises the :confirmed-commit:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + + reference "RFC 6241, Section 8.4"; + } + + feature rollback-on-error { + description + "NETCONF :rollback-on-error capability; + If the server advertises the :rollback-on-error + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.5"; + } + + feature validate { + description + "NETCONF :validate:1.1 capability; + If the server advertises the :validate:1.1 + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.6"; + } + + feature startup { + description + "NETCONF :startup capability; + If the server advertises the :startup + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.7"; + } + + feature url { + description + "NETCONF :url capability; + If the server advertises the :url + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.8"; + } + + feature xpath { + description + "NETCONF :xpath capability; + If the server advertises the :xpath + capability for a session, then this feature must + also be enabled for that session. Otherwise, + this feature must not be enabled."; + reference "RFC 6241, Section 8.9"; + } + + // NETCONF Simple Types + + typedef session-id-type { + type uint32 { + range "1..max"; + } + description + "NETCONF Session Id"; + } + + typedef session-id-or-zero-type { + type uint32; + description + "NETCONF Session Id or Zero to indicate none"; + } + typedef error-tag-type { + type enumeration { + enum in-use { + description + "The request requires a resource that + already is in use."; + } + enum invalid-value { + description + "The request specifies an unacceptable value for one + or more parameters."; + } + enum too-big { + description + "The request or response (that would be generated) is + too large for the implementation to handle."; + } + enum missing-attribute { + description + "An expected attribute is missing."; + } + enum bad-attribute { + description + "An attribute value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-attribute { + description + "An unexpected attribute is present."; + } + enum missing-element { + description + "An expected element is missing."; + } + enum bad-element { + description + "An element value is not correct; e.g., wrong type, + out of range, pattern mismatch."; + } + enum unknown-element { + description + "An unexpected element is present."; + } + enum unknown-namespace { + description + "An unexpected namespace is present."; + } + enum access-denied { + description + "Access to the requested protocol operation or + data model is denied because authorization failed."; + } + enum lock-denied { + description + "Access to the requested lock is denied because the + lock is currently held by another entity."; + } + enum resource-denied { + description + "Request could not be completed because of + insufficient resources."; + } + enum rollback-failed { + description + "Request to roll back some configuration change (via + rollback-on-error or <discard-changes> operations) + was not completed for some reason."; + + } + enum data-exists { + description + "Request could not be completed because the relevant + data model content already exists. For example, + a 'create' operation was attempted on data that + already exists."; + } + enum data-missing { + description + "Request could not be completed because the relevant + data model content does not exist. For example, + a 'delete' operation was attempted on + data that does not exist."; + } + enum operation-not-supported { + description + "Request could not be completed because the requested + operation is not supported by this implementation."; + } + enum operation-failed { + description + "Request could not be completed because the requested + operation failed for some reason not covered by + any other error condition."; + } + enum partial-operation { + description + "This error-tag is obsolete, and SHOULD NOT be sent + by servers conforming to this document."; + } + enum malformed-message { + description + "A message could not be handled because it failed to + be parsed correctly. For example, the message is not + well-formed XML or it uses an invalid character set."; + } + } + description "NETCONF Error Tag"; + reference "RFC 6241, Appendix A"; + } + + typedef error-severity-type { + type enumeration { + enum error { + description "Error severity"; + } + enum warning { + description "Warning severity"; + } + } + description "NETCONF Error Severity"; + reference "RFC 6241, Section 4.3"; + } + + typedef edit-operation-type { + type enumeration { + enum merge { + description + "The configuration data identified by the + element containing this attribute is merged + with the configuration at the corresponding + level in the configuration datastore identified + by the target parameter."; + } + enum replace { + description + "The configuration data identified by the element + containing this attribute replaces any related + configuration in the configuration datastore + identified by the target parameter. If no such + configuration data exists in the configuration + datastore, it is created. Unlike a + <copy-config> operation, which replaces the + entire target configuration, only the configuration + actually present in the config parameter is affected."; + } + enum create { + description + "The configuration data identified by the element + containing this attribute is added to the + configuration if and only if the configuration + data does not already exist in the configuration + datastore. If the configuration data exists, an + <rpc-error> element is returned with an + <error-tag> value of 'data-exists'."; + } + enum delete { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if and only if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, an <rpc-error> element is returned with + an <error-tag> value of 'data-missing'."; + } + enum remove { + description + "The configuration data identified by the element + containing this attribute is deleted from the + configuration if the configuration + data currently exists in the configuration + datastore. If the configuration data does not + exist, the 'remove' operation is silently ignored + by the server."; + } + } + default "merge"; + description "NETCONF 'operation' attribute values"; + reference "RFC 6241, Section 7.2"; + } + + // NETCONF Standard Protocol Operations + + rpc get-config { + description + "Retrieve all or part of a specified configuration."; + + reference "RFC 6241, Section 7.1"; + + input { + container source { + description + "Particular configuration to retrieve."; + + choice config-source { + mandatory true; + description + "The configuration to retrieve."; + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source. + This is optional-to-implement on the server because + not all servers will support filtering for this + datastore."; + } + } + } + + anyxml filter { + description + "Subtree or XPath filter to use."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the source datastore subset that matched + the filter criteria (if any). An empty data container + indicates that the request did not produce any results."; + } + } + } + + rpc edit-config { + description + "The <edit-config> operation loads all or part of a specified + configuration to the specified target configuration."; + + reference "RFC 6241, Section 7.2"; + + input { + container target { + description + "Particular configuration to edit."; + + choice config-target { + mandatory true; + description + "The configuration target."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config source."; + } + } + } + + leaf default-operation { + type enumeration { + enum merge { + description + "The default operation is merge."; + } + enum replace { + description + "The default operation is replace."; + } + enum none { + description + "There is no default operation."; + } + } + default "merge"; + description + "The default operation to use."; + } + + leaf test-option { + if-feature validate; + type enumeration { + enum test-then-set { + description + "The server will test and then set if no errors."; + } + enum set { + description + "The server will set without a test first."; + } + + enum test-only { + description + "The server will only test and not set, even + if there are no errors."; + } + } + default "test-then-set"; + description + "The test option to use."; + } + + leaf error-option { + type enumeration { + enum stop-on-error { + description + "The server will stop on errors."; + } + enum continue-on-error { + description + "The server may continue on errors."; + } + enum rollback-on-error { + description + "The server will roll back on errors. + This value can only be used if the 'rollback-on-error' + feature is supported."; + } + } + default "stop-on-error"; + description + "The error option to use."; + } + + choice edit-content { + mandatory true; + description + "The content for the edit operation."; + + anyxml config { + description + "Inline Config content."; + } + leaf url { + if-feature url; + type inet:uri; + description + "URL-based config content."; + } + } + } + } + + rpc copy-config { + description + "Create or replace an entire configuration datastore with the + contents of another complete configuration datastore."; + + reference "RFC 6241, Section 7.3"; + + input { + container target { + description + "Particular configuration to copy to."; + + choice config-target { + mandatory true; + description + "The configuration target of the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + if-feature writable-running; + type empty; + description + "The running configuration is the config target. + This is optional-to-implement on the server."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + + container source { + description + "Particular configuration to copy from."; + + choice config-source { + mandatory true; + description + "The configuration source for the copy operation."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: <config> element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } + + rpc delete-config { + nacm:default-deny-all; + description + "Delete a configuration datastore."; + + reference "RFC 6241, Section 7.4"; + + input { + container target { + description + "Particular configuration to delete."; + + choice config-target { + mandatory true; + description + "The configuration target to delete."; + + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config target."; + } + } + } + } + } + + rpc lock { + description + "The lock operation allows the client to lock the configuration + system of a device."; + + reference "RFC 6241, Section 7.5"; + + input { + container target { + description + "Particular configuration to lock."; + + choice config-target { + mandatory true; + description + "The configuration target to lock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc unlock { + description + "The unlock operation is used to release a configuration lock, + previously obtained with the 'lock' operation."; + + reference "RFC 6241, Section 7.6"; + + input { + container target { + description + "Particular configuration to unlock."; + + choice config-target { + mandatory true; + description + "The configuration target to unlock."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config target."; + } + leaf running { + type empty; + description + "The running configuration is the config target."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config target."; + } + } + } + } + } + + rpc get { + description + "Retrieve running configuration and device state information."; + + reference "RFC 6241, Section 7.7"; + + input { + anyxml filter { + description + "This parameter specifies the portion of the system + configuration and state data to retrieve."; + nc:get-filter-element-attributes; + } + } + + output { + anyxml data { + description + "Copy of the running datastore subset and/or state + data that matched the filter criteria (if any). + An empty data container indicates that the request did not + produce any results."; + } + } + } + + rpc close-session { + description + "Request graceful termination of a NETCONF session."; + + reference "RFC 6241, Section 7.8"; + } + + rpc kill-session { + nacm:default-deny-all; + description + "Force the termination of a NETCONF session."; + + reference "RFC 6241, Section 7.9"; + + input { + leaf session-id { + type session-id-type; + mandatory true; + description + "Particular session to kill."; + } + } + } + + rpc commit { + if-feature candidate; + + description + "Commit the candidate configuration as the device's new + current configuration."; + + reference "RFC 6241, Section 8.3.4.1"; + + input { + leaf confirmed { + if-feature confirmed-commit; + type empty; + description + "Requests a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf confirm-timeout { + if-feature confirmed-commit; + type uint32 { + range "1..max"; + } + units "seconds"; + default "600"; // 10 minutes + description + "The timeout interval for a confirmed commit."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist { + if-feature confirmed-commit; + type string; + description + "This parameter is used to make a confirmed commit + persistent. A persistent confirmed commit is not aborted + if the NETCONF session terminates. The only way to abort + a persistent confirmed commit is to let the timer expire, + or to use the <cancel-commit> operation. + + The value of this parameter is a token that must be given + in the 'persist-id' parameter of <commit> or + <cancel-commit> operations in order to confirm or cancel + the persistent confirmed commit. + + The token should be a random string."; + reference "RFC 6241, Section 8.3.4.1"; + } + + leaf persist-id { + if-feature confirmed-commit; + type string; + description + "This parameter is given in order to commit a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the <commit> operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + reference "RFC 6241, Section 8.3.4.1"; + } + + } + } + + rpc discard-changes { + if-feature candidate; + + description + "Revert the candidate configuration to the current + running configuration."; + reference "RFC 6241, Section 8.3.4.2"; + } + + rpc cancel-commit { + if-feature confirmed-commit; + description + "This operation is used to cancel an ongoing confirmed commit. + If the confirmed commit is persistent, the parameter + 'persist-id' must be given, and it must match the value of the + 'persist' parameter."; + reference "RFC 6241, Section 8.4.4.1"; + + input { + leaf persist-id { + type string; + description + "This parameter is given in order to cancel a persistent + confirmed commit. The value must be equal to the value + given in the 'persist' parameter to the <commit> operation. + If it does not match, the operation fails with an + 'invalid-value' error."; + } + } + } + + rpc validate { + if-feature validate; + + description + "Validates the contents of the specified configuration."; + + reference "RFC 6241, Section 8.6.4.1"; + + input { + container source { + description + "Particular configuration to validate."; + + choice config-source { + mandatory true; + description + "The configuration source to validate."; + + leaf candidate { + if-feature candidate; + type empty; + description + "The candidate configuration is the config source."; + } + leaf running { + type empty; + description + "The running configuration is the config source."; + } + leaf startup { + if-feature startup; + type empty; + description + "The startup configuration is the config source."; + } + leaf url { + if-feature url; + type inet:uri; + description + "The URL-based configuration is the config source."; + } + anyxml config { + description + "Inline Config content: <config> element. Represents + an entire configuration datastore, not + a subset of the running datastore."; + } + } + } + } + } +} diff --git a/yang/subdir.am b/yang/subdir.am index eb17c38dbc..0bdf93793f 100644 --- a/yang/subdir.am +++ b/yang/subdir.am @@ -38,6 +38,9 @@ dist_yangmodels_DATA += yang/frr-routing.yang dist_yangmodels_DATA += yang/ietf/ietf-routing-types.yang dist_yangmodels_DATA += yang/ietf/ietf-interfaces.yang dist_yangmodels_DATA += yang/ietf/ietf-bgp-types.yang +dist_yangmodels_DATA += yang/ietf/ietf-netconf-acm.yang +dist_yangmodels_DATA += yang/ietf/ietf-netconf.yang +dist_yangmodels_DATA += yang/ietf/ietf-netconf-with-defaults.yang if BFDD dist_yangmodels_DATA += yang/frr-bfdd.yang diff --git a/zebra/zebra_cli.c b/zebra/zebra_cli.c index 296f03ae89..76b2df157e 100644 --- a/zebra/zebra_cli.c +++ b/zebra/zebra_cli.c @@ -230,11 +230,12 @@ DEFUN_YANG_NOSH (link_params, ret = nb_cli_apply_changes(vty, NULL); if (ret == CMD_SUCCESS) { - char xpath[XPATH_MAXLEN]; + char *xpath; - snprintf(xpath, sizeof(xpath), "%s/frr-zebra:zebra/link-params", - VTY_CURR_XPATH); + xpath = asprintfrr(MTYPE_TMP, "%s/frr-zebra:zebra/link-params", + VTY_CURR_XPATH); VTY_PUSH_XPATH(LINK_PARAMS_NODE, xpath); + XFREE(MTYPE_TMP, xpath); } return ret; |
