]> git.puffer.fish Git - mirror/frr.git/commitdiff
lib, mgmtd: fix processing of yang notifications
authorIgor Ryzhov <iryzhov@nfware.com>
Fri, 9 Feb 2024 22:58:49 +0000 (00:58 +0200)
committerIgor Ryzhov <iryzhov@nfware.com>
Fri, 9 Feb 2024 23:00:24 +0000 (01:00 +0200)
Current code assumes that notification is always sent in stripped JSON
format and therefore notification xpath starts at the third symbol of
notification data. Assuming JSON is more or less fine, because this
representation is internal to FRR, but the assumption about the xpath is
wrong, because it won't work for not top-level notifications. YANG
allows to define notification as a child for some data node deep into
the tree and in this case notification data contains not only the
notification node itself, but also all its parents.

To fix the issue, parse the notification data and get its xpath from its
schema node.

Signed-off-by: Igor Ryzhov <iryzhov@nfware.com>
lib/mgmt_be_client.c
lib/yang.c
lib/yang.h
mgmtd/mgmt_be_adapter.c
mgmtd/mgmt_testc.c
tests/topotests/mgmt_notif/test_notif.py

index 6530022db8001368d333119ba7a36c77f65351c9..e2a720acb7d8681003189ce76c2e4e6854fc295b 100644 (file)
@@ -964,13 +964,21 @@ static void be_client_handle_notify(struct mgmt_be_client *client, void *msgbuf,
 {
        struct mgmt_msg_notify_data *notif_msg = msgbuf;
        struct mgmt_be_client_notification_cb *cb;
-       const char *notif;
+       char notif[XPATH_MAXLEN];
+       struct lyd_node *dnode;
+       LY_ERR err;
        uint i;
 
        debug_be_client("Received notification for client %s", client->name);
 
-       /* "{\"modname:notification-name\": ...}" */
-       notif = (const char *)notif_msg->result + 2;
+       err = yang_parse_notification(notif_msg->result_type,
+                                     (char *)notif_msg->result, &dnode);
+       if (err)
+               return;
+
+       lysc_path(dnode->schema, LYSC_PATH_DATA, notif, sizeof(notif));
+
+       lyd_free_all(dnode);
 
        for (i = 0; i < client->cbs.nnotify_cbs; i++) {
                cb = &client->cbs.notify_cbs[i];
index adf2ba2ab024f3a5002174f22117b1feb744603a..ff7df0b379bc088b3812897c6fafc05828c270f1 100644 (file)
@@ -714,6 +714,52 @@ static void ly_log_cb(LY_LOG_LEVEL level, const char *msg, const char *path)
                zlog(priority, "libyang: %s", msg);
 }
 
+LY_ERR yang_parse_notification(LYD_FORMAT format, const char *data,
+                              struct lyd_node **notif)
+{
+       struct lyd_node *tree, *dnode;
+       struct ly_in *in = NULL;
+       bool found = false;
+       LY_ERR err;
+
+       err = ly_in_new_memory(data, &in);
+       if (err) {
+               zlog_err("Failed to initialize ly_in: %s", ly_last_errmsg());
+               return err;
+       }
+
+       err = lyd_parse_op(ly_native_ctx, NULL, in, format, LYD_TYPE_NOTIF_YANG,
+                          &tree, NULL);
+       if (err) {
+               zlog_err("Failed to parse notification: %s", ly_last_errmsg());
+               ly_in_free(in, 0);
+               return err;
+       }
+
+       /*
+        * Notification can be a child of some data node, so traverse the tree
+        * until we find the notification.
+        */
+       LYD_TREE_DFS_BEGIN (tree, dnode) {
+               if (dnode->schema->nodetype == LYS_NOTIF) {
+                       found = true;
+                       break;
+               }
+               LYD_TREE_DFS_END(tree, dnode);
+       }
+
+       if (!found) {
+               zlog_err("Notification not found in the parsed tree");
+               lyd_free_all(tree);
+               ly_in_free(in, 0);
+               return LY_ENOTFOUND;
+       }
+
+       *notif = dnode;
+
+       return LY_SUCCESS;
+}
+
 static ssize_t yang_print_darr(void *arg, const void *buf, size_t count)
 {
        uint8_t *dst = darr_append_n(*(uint8_t **)arg, count);
index 4ed0a39ba41fe27da1594cb071098c51c1b22ed2..9c221445cd2ec893f11b73765a8a4a223ef06acd 100644 (file)
@@ -607,6 +607,19 @@ extern struct ly_ctx *yang_ctx_new_setup(bool embedded_modules,
  */
 extern void yang_debugging_set(bool enable);
 
+/*
+ * Parse a YANG notification.
+ *
+ * Args:
+ *     format: LYD_FORMAT of input data.
+ *     data: input data.
+ *     notif: pointer to the libyang data tree to store the parsed notification.
+ *            If the notification is not on the top level of the yang model,
+ *            the pointer to the notification node is still returned, but it's
+ *            part of the full data tree with all its parents.
+ */
+extern LY_ERR yang_parse_notification(LYD_FORMAT format, const char *data,
+                                     struct lyd_node **notif);
 
 /*
  * "Print" the yang tree in `root` into dynamic sized array.
index aba02e4653bbf46badf6678a457e330c10502031..f4353defe9cfb4b45e2cfa0993a43ca84f7e91c3 100644 (file)
@@ -592,14 +592,22 @@ static void mgmt_be_adapter_send_notify(struct mgmt_msg_notify_data *msg,
 {
        struct mgmt_be_client_adapter *adapter;
        struct mgmt_be_xpath_map *map;
-       const char *notif;
+       char notif[XPATH_MAXLEN];
+       struct lyd_node *dnode;
+       LY_ERR err;
        uint id;
 
        if (!darr_len(be_notif_xpath_map))
                return;
 
-       /* "{\"modname:notification-name\": ...}" */
-       notif = (const char *)msg->result + 2;
+       err = yang_parse_notification(msg->result_type, (char *)msg->result,
+                                     &dnode);
+       if (err)
+               return;
+
+       lysc_path(dnode->schema, LYSC_PATH_DATA, notif, sizeof(notif));
+
+       lyd_free_all(dnode);
 
        darr_foreach_p (be_notif_xpath_map, map) {
                if (strncmp(map->xpath_prefix, notif, strlen(map->xpath_prefix)))
index 02a308f32849f8fe96780c9d8d85060811b7db9f..a664c9d7a5ac0b674659d25267cd22c6103bb072 100644 (file)
@@ -79,6 +79,20 @@ struct frr_signal_t __signals[] = {
 #define MGMTD_TESTC_VTY_PORT 2624
 
 /* clang-format off */
+static const struct frr_yang_module_info frr_ripd_info = {
+       .name = "frr-ripd",
+       .ignore_cfg_cbs = true,
+       .nodes = {
+               {
+                       .xpath = NULL,
+               }
+       }
+};
+
+static const struct frr_yang_module_info *const mgmt_yang_modules[] = {
+       &frr_ripd_info,
+};
+
 FRR_DAEMON_INFO(mgmtd_testc, MGMTD_TESTC,
                .proghelp = "FRR Management Daemon Test Client.",
 
@@ -87,8 +101,8 @@ FRR_DAEMON_INFO(mgmtd_testc, MGMTD_TESTC,
 
                .privs = &__privs,
 
-               // .yang_modules = mgmt_yang_modules,
-               // .n_yang_modules = array_size(mgmt_yang_modules),
+               .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,
index 2f923e398c15c0449dd28ed23616ca48af973f50..c85e7ba7959622960d53f6c12461864e8da4cc68 100644 (file)
@@ -92,7 +92,7 @@ def test_backend_notification(tgen):
         pytest.skip("No mgmtd_testc")
 
     output = r1.cmd_raises(
-        be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen frr-ripd"
+        be_client_path + " --timeout 20 --log file:mgmt_testc.log --listen /frr-ripd"
     )
 
     jsout = json.loads(output)