]> git.puffer.fish Git - matthieu/frr.git/commitdiff
mgmtd: backend subscriptions to notifications
authorChristian Hopps <chopps@labn.net>
Tue, 30 Jan 2024 06:27:35 +0000 (01:27 -0500)
committerChristian Hopps <chopps@labn.net>
Tue, 30 Jan 2024 20:41:26 +0000 (15:41 -0500)
Signed-off-by: Christian Hopps <chopps@labn.net>
lib/mgmt.proto
lib/mgmt_be_client.c
lib/mgmt_be_client.h
mgmtd/mgmt_be_adapter.c
mgmtd/mgmt_be_adapter.h
mgmtd/mgmt_testc.c [new file with mode: 0644]
mgmtd/subdir.am

index 5d83fca347ba72f5d1d6fe0c7d5769bcca2969c5..01a99ab63b986bcf1754372fc3570be8d5df6cd0 100644 (file)
@@ -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 {
index 070d6627b44dda75de3afa79446e1c08ae3af31b..b217ce40ed5712553cdd530a1de67223eabebb67 100644 (file)
@@ -822,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,
@@ -957,6 +963,31 @@ static void be_client_handle_get_tree(struct mgmt_be_client *client,
                   be_client_send_tree_data_batch, args);
 }
 
+/*
+ * 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
  *
@@ -972,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;
@@ -1011,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,
@@ -1054,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)
@@ -1100,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);
 
index 930cbf0cdc2a38b0b4f15ef45d51679486bb6c07..32a717c496174fa45b36f7d0789c5542670f19b5 100644 (file)
@@ -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,9 @@ 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.
index 5cc2d30f712b9dbcc984df6c82526806226eb09f..66e622b326381c1a7f07ccfb9a0243c0bba66831 100644 (file)
@@ -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
  */
@@ -611,6 +666,7 @@ static void be_adapter_handle_native_msg(struct mgmt_be_client_adapter *adapter,
        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:
index 2eb80437b69a9aef638eaceb187752c024370c1c..491410aa153b6f50773f56d2813ae852db6c1c77 100644 (file)
@@ -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
diff --git a/mgmtd/mgmt_testc.c b/mgmtd/mgmt_testc.c
new file mode 100644 (file)
index 0000000..70cd2bb
--- /dev/null
@@ -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;
+}
index a3955925edd5072a347d469a95a996eeefe7ff64..ab3d25819837610dbeddf44616e4606fe191c634 100644 (file)
@@ -48,7 +48,10 @@ noinst_HEADERS += \
        zebra/zebra_cli.h \
        # end
 
-sbin_PROGRAMS += mgmtd/mgmtd
+sbin_PROGRAMS += mgmtd/mgmtd mgmtd/mgmtd_testc
+
+mgmtd_mgmtd_testc_SOURCES = mgmtd/mgmt_testc.c
+mgmtd_mgmtd_testc_LDADD = lib/libfrr.la
 
 mgmtd_mgmtd_SOURCES = \
        mgmtd/mgmt_main.c \