diff options
59 files changed, 3220 insertions, 333 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.h b/bgpd/bgp_attr.h index 5a39f734d3..942de57112 100644 --- a/bgpd/bgp_attr.h +++ b/bgpd/bgp_attr.h @@ -162,6 +162,45 @@ struct attr { /* 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; @@ -217,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; @@ -262,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 @@ -290,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; @@ -305,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_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 4772610856..d328be9506 100644 --- a/doc/developer/northbound/retrofitting-configuration-commands.rst +++ b/doc/developer/northbound/retrofitting-configuration-commands.rst @@ -982,34 +982,30 @@ 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. Since all CLI configuration commands from FRR will need to be rewritten, this is an excellent opportunity to rework this part of the code to make the commands easier to maintain and extend. These are the three main -recommendations: 1. Always use DEFPY instead of DEFUN to improve code -readability. 2. Always try to join multiple DEFUNs into a single DEFPY -whenever possible. As an example, there’s no need to have both -``distance (1-255) A.B.C.D/M`` and ``distance (1-255) A.B.C.D/M WORD`` -when a single ``distance (1-255) A.B.C.D/M [WORD]`` would suffice. 3. -When necessary, create a separate DEFPY for ``no`` commands so that part -of the configuration command can be made optional for convenience. -Example: -``no timers basic [(5-2147483647) (5-2147483647) (5-2147483647)]``. In -this example, everything after ``no timers basic`` is ignored by FRR, so -it makes sense to accept ``no timers basic`` as a valid command. But it -also makes sense to accept all parameters -(``no timers basic (5-2147483647) (5-2147483647) (5-2147483647)``) to -make it easier to remove the command just by prefixing a “no” to it. +recommendations: + +#. Always use DEFPY instead of DEFUN to improve code readability +#. Always try to join multiple DEFUNs into a single DEFPY whenever possible. As + an example, there’s no need to have both ``distance (1-255) A.B.C.D/M`` and + ``distance (1-255) A.B.C.D/M WORD`` when a single ``distance (1-255) + A.B.C.D/M [WORD]`` would suffice. +#. When making a negative form of a command, put ``[no]`` in the positive form + and use ``![...]`` to mark portions of the command that should be optional + only in the ``no`` version. To rewrite a CLI command as a dumb wrapper around the northbound callbacks, use the ``nb_cli_cfg_change()`` function. This function accepts as a parameter an array of ``cli_config_change`` structures that specify the changes that need to performed on the candidate configuration. Here’s the declaration of this structure (taken from the -*lib/northbound_cli.h* file): +``lib/northbound_cli.h`` file): .. code:: c 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/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 |
