summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/filter.c12
-rw-r--r--lib/filter.h5
-rw-r--r--lib/filter_cli.c257
-rw-r--r--lib/filter_nb.c32
-rw-r--r--lib/routemap.c12
-rw-r--r--lib/routemap.h2
-rw-r--r--lib/routemap_northbound.c42
-rw-r--r--lib/vty.c6
-rw-r--r--lib/yang.c24
-rw-r--r--lib/yang.h15
10 files changed, 224 insertions, 183 deletions
diff --git a/lib/filter.c b/lib/filter.c
index f86adab5d6..a0adff0e35 100644
--- a/lib/filter.c
+++ b/lib/filter.c
@@ -885,7 +885,7 @@ static void access_list_init_ipv6(void)
install_element(ENABLE_NODE, &show_ipv6_access_list_name_cmd);
}
-void access_list_init(void)
+void access_list_init_new(bool in_backend)
{
cmd_variable_handler_register(access_list_handlers);
@@ -893,7 +893,15 @@ void access_list_init(void)
access_list_init_ipv6();
access_list_init_mac();
- filter_cli_init();
+ if (!in_backend) {
+ /* we do not want to handle config commands in the backend */
+ filter_cli_init();
+ }
+}
+
+void access_list_init(void)
+{
+ access_list_init_new(false);
}
void access_list_reset(void)
diff --git a/lib/filter.h b/lib/filter.h
index e092f0771a..bd9e22d384 100644
--- a/lib/filter.h
+++ b/lib/filter.h
@@ -114,6 +114,7 @@ struct access_master {
/* Prototypes for access-list. */
extern void access_list_init(void);
+extern void access_list_init_new(bool in_backend);
extern void access_list_reset(void);
extern void access_list_add_hook(void (*func)(struct access_list *));
extern void access_list_delete_hook(void (*func)(struct access_list *));
@@ -124,13 +125,13 @@ extern enum filter_type access_list_apply(struct access_list *access,
struct access_list *access_list_get(afi_t afi, const char *name);
void access_list_delete(struct access_list *access);
struct filter *filter_new(void);
-void access_list_filter_add(struct access_list *access,
- struct filter *filter);
+void access_list_filter_add(struct access_list *access, struct filter *filter);
void access_list_filter_delete(struct access_list *access,
struct filter *filter);
int64_t filter_new_seq_get(struct access_list *access);
extern const struct frr_yang_module_info frr_filter_info;
+extern const struct frr_yang_module_info frr_filter_cli_info;
/* filter_nb.c */
diff --git a/lib/filter_cli.c b/lib/filter_cli.c
index 529b46b6ad..28790f69e7 100644
--- a/lib/filter_cli.c
+++ b/lib/filter_cli.c
@@ -69,53 +69,60 @@ static int64_t acl_get_seq(struct vty *vty, const char *xpath, bool is_remove)
return seq;
}
-static int acl_remove_if_empty(struct vty *vty, const char *iptype,
- const char *name)
+/**
+ * Remove main data structure filter list if there are no more entries or
+ * remark. This fixes compatibility with old CLI and tests.
+ */
+static int filter_remove_check_empty(struct vty *vty, const char *ftype,
+ const char *iptype, const char *name,
+ uint32_t del_seq, bool del_remark)
{
+ const struct lyd_node *remark_dnode = NULL;
+ const struct lyd_node *entry_dnode = NULL;
char xpath[XPATH_MAXLEN];
+ uint32_t count;
+
+ /* Count existing entries */
+ count = yang_dnode_count(vty->candidate_config->dnode,
+ "/frr-filter:lib/%s-list[type='%s'][name='%s']/entry",
+ ftype, iptype, name);
+
+ /* Check entry-to-delete actually exists */
+ if (del_seq) {
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/%s-list[type='%s'][name='%s']/entry[sequence='%u']",
+ ftype, iptype, name, del_seq);
+ entry_dnode = yang_dnode_get(vty->candidate_config->dnode,
+ xpath);
+
+ /* If exists, delete and don't count it, we need only remaining entries */
+ if (entry_dnode) {
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ count--;
+ }
+ }
+ /* Delete the remark, or check whether it exists if we're keeping it */
snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='%s'][name='%s']/remark",
+ "/frr-filter:lib/%s-list[type='%s'][name='%s']/remark", ftype,
iptype, name);
- /* List is not empty if there is a remark, check that: */
- if (yang_dnode_exists(vty->candidate_config->dnode, xpath))
- return CMD_SUCCESS;
-
- /* Check if we have any entries: */
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='%s'][name='%s']", iptype,
- name);
- /*
- * NOTE: if the list is empty it will return the first sequence
- * number: 5.
- */
- if (acl_get_seq(vty, xpath, true) != 5)
- return CMD_SUCCESS;
+ if (del_remark)
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ else
+ remark_dnode = yang_dnode_get(vty->candidate_config->dnode,
+ xpath);
+
+ /* If there are no entries left and no remark, delete the whole list */
+ if (count == 0 && !remark_dnode) {
+ snprintf(xpath, sizeof(xpath),
+ "/frr-filter:lib/%s-list[type='%s'][name='%s']", ftype,
+ iptype, name);
+ nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
+ }
- /* Nobody is using this list, lets remove it. */
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
return nb_cli_apply_changes(vty, NULL);
}
-static int acl_remove(struct vty *vty, const char *iptype, const char *name,
- int64_t sseq)
-{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintfrr(
- xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='%s'][name='%s']/entry[sequence='%" PRId64 "']",
- iptype, name, sseq);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return acl_remove_if_empty(vty, iptype, name);
-
- return rv;
-}
-
/*
* Cisco (legacy) access lists.
*/
@@ -213,7 +220,8 @@ DEFPY_YANG(
/* If the user provided sequence number, then just go for it. */
if (seq_str != NULL)
- return acl_remove(vty, "ipv4", name, seq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
ada.ada_type = "ipv4";
@@ -237,7 +245,8 @@ DEFPY_YANG(
else
return CMD_WARNING_CONFIG_FAILED;
- return acl_remove(vty, "ipv4", name, sseq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name, sseq,
+ false);
}
DEFPY_YANG(
@@ -384,7 +393,8 @@ DEFPY_YANG(
/* If the user provided sequence number, then just go for it. */
if (seq_str != NULL)
- return acl_remove(vty, "ipv4", name, seq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
ada.ada_type = "ipv4";
@@ -429,7 +439,8 @@ DEFPY_YANG(
else
return CMD_WARNING_CONFIG_FAILED;
- return acl_remove(vty, "ipv4", name, sseq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name, sseq,
+ false);
}
/*
@@ -525,7 +536,8 @@ DEFPY_YANG(
/* If the user provided sequence number, then just go for it. */
if (seq_str != NULL)
- return acl_remove(vty, "ipv4", name, seq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
ada.ada_type = "ipv4";
@@ -549,7 +561,8 @@ DEFPY_YANG(
else
return CMD_WARNING_CONFIG_FAILED;
- return acl_remove(vty, "ipv4", name, sseq);
+ return filter_remove_check_empty(vty, "access", "ipv4", name, sseq,
+ false);
}
DEFPY_YANG(
@@ -600,19 +613,7 @@ DEFPY_YANG(
ACCESS_LIST_ZEBRA_STR
ACCESS_LIST_REMARK_STR)
{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='ipv4'][name='%s']/remark",
- name);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return acl_remove_if_empty(vty, "ipv4", name);
-
- return rv;
+ return filter_remove_check_empty(vty, "access", "ipv4", name, 0, true);
}
ALIAS(
@@ -716,7 +717,8 @@ DEFPY_YANG(
/* If the user provided sequence number, then just go for it. */
if (seq_str != NULL)
- return acl_remove(vty, "ipv6", name, seq);
+ return filter_remove_check_empty(vty, "access", "ipv6", name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
ada.ada_type = "ipv6";
@@ -740,7 +742,8 @@ DEFPY_YANG(
else
return CMD_WARNING_CONFIG_FAILED;
- return acl_remove(vty, "ipv6", name, sseq);
+ return filter_remove_check_empty(vty, "access", "ipv6", name, sseq,
+ false);
}
DEFPY_YANG(
@@ -794,19 +797,7 @@ DEFPY_YANG(
ACCESS_LIST_ZEBRA_STR
ACCESS_LIST_REMARK_STR)
{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='ipv6'][name='%s']/remark",
- name);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return acl_remove_if_empty(vty, "ipv6", name);
-
- return rv;
+ return filter_remove_check_empty(vty, "access", "ipv6", name, 0, true);
}
ALIAS(
@@ -902,7 +893,8 @@ DEFPY_YANG(
/* If the user provided sequence number, then just go for it. */
if (seq_str != NULL)
- return acl_remove(vty, "mac", name, seq);
+ return filter_remove_check_empty(vty, "access", "mac", name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
ada.ada_type = "mac";
@@ -922,7 +914,8 @@ DEFPY_YANG(
else
return CMD_WARNING_CONFIG_FAILED;
- return acl_remove(vty, "mac", name, sseq);
+ return filter_remove_check_empty(vty, "access", "mac", name, sseq,
+ false);
}
DEFPY_YANG(
@@ -976,19 +969,7 @@ DEFPY_YANG(
ACCESS_LIST_ZEBRA_STR
ACCESS_LIST_REMARK_STR)
{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/access-list[type='mac'][name='%s']/remark",
- name);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return acl_remove_if_empty(vty, "mac", name);
-
- return rv;
+ return filter_remove_check_empty(vty, "access", "mac", name, 0, true);
}
ALIAS(
@@ -1149,62 +1130,17 @@ void access_list_remark_show(struct vty *vty, const struct lyd_node *dnode,
* Prefix lists.
*/
-/**
- * Remove main data structure prefix list if there are no more entries or
- * remark. This fixes compatibility with old CLI and tests.
- */
-static int plist_remove_if_empty(struct vty *vty, const char *iptype,
- const char *name)
-{
- char xpath[XPATH_MAXLEN];
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/prefix-list[type='%s'][name='%s']/remark",
- iptype, name);
- /* List is not empty if there is a remark, check that: */
- if (yang_dnode_exists(vty->candidate_config->dnode, xpath))
- return CMD_SUCCESS;
-
- /* Check if we have any entries: */
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/prefix-list[type='%s'][name='%s']", iptype,
- name);
- /*
- * NOTE: if the list is empty it will return the first sequence
- * number: 5.
- */
- if (acl_get_seq(vty, xpath, true) != 5)
- return CMD_SUCCESS;
-
- /* Nobody is using this list, lets remove it. */
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
- return nb_cli_apply_changes(vty, NULL);
-}
-
static int plist_remove(struct vty *vty, const char *iptype, const char *name,
- const char *seq, const char *action,
+ uint32_t seq, const char *action,
union prefixconstptr prefix, int ge, int le)
{
int64_t sseq;
struct plist_dup_args pda = {};
- char xpath[XPATH_MAXLEN];
- char xpath_entry[XPATH_MAXLEN + 32];
- int rv;
/* If the user provided sequence number, then just go for it. */
- if (seq != NULL) {
- snprintf(
- xpath, sizeof(xpath),
- "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry[sequence='%s']",
- iptype, name, seq);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return plist_remove_if_empty(vty, iptype, name);
-
- return rv;
- }
+ if (seq != 0)
+ return filter_remove_check_empty(vty, "prefix", iptype, name,
+ seq, false);
/* Otherwise, to keep compatibility, we need to figure it out. */
pda.pda_type = iptype;
@@ -1224,17 +1160,8 @@ static int plist_remove(struct vty *vty, const char *iptype, const char *name,
else
return CMD_WARNING_CONFIG_FAILED;
- snprintfrr(
- xpath_entry, sizeof(xpath_entry),
- "/frr-filter:lib/prefix-list[type='%s'][name='%s']/entry[sequence='%" PRId64 "']",
- iptype, name, sseq);
- nb_cli_enqueue_change(vty, xpath_entry, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return plist_remove_if_empty(vty, iptype, name);
-
- return rv;
+ return filter_remove_check_empty(vty, "prefix", iptype, name, sseq,
+ false);
}
DEFPY_YANG(
@@ -1347,7 +1274,7 @@ DEFPY_YANG(
"Maximum prefix length to be matched\n"
"Maximum prefix length\n")
{
- return plist_remove(vty, "ipv4", name, seq_str, action,
+ return plist_remove(vty, "ipv4", name, seq, action,
prefix_str ? prefix : NULL, ge, le);
}
@@ -1360,7 +1287,7 @@ DEFPY_YANG(
PREFIX_LIST_NAME_STR
ACCESS_LIST_SEQ_STR)
{
- return plist_remove(vty, "ipv4", name, seq_str, NULL, NULL, 0, 0);
+ return plist_remove(vty, "ipv4", name, seq, NULL, NULL, 0, 0);
}
DEFPY_YANG(
@@ -1414,19 +1341,7 @@ DEFPY_YANG(
PREFIX_LIST_NAME_STR
ACCESS_LIST_REMARK_STR)
{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/prefix-list[type='ipv4'][name='%s']/remark",
- name);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return plist_remove_if_empty(vty, "ipv4", name);
-
- return rv;
+ return filter_remove_check_empty(vty, "prefix", "ipv4", name, 0, true);
}
ALIAS(
@@ -1549,7 +1464,7 @@ DEFPY_YANG(
"Minimum prefix length to be matched\n"
"Minimum prefix length\n")
{
- return plist_remove(vty, "ipv6", name, seq_str, action,
+ return plist_remove(vty, "ipv6", name, seq, action,
prefix_str ? prefix : NULL, ge, le);
}
@@ -1562,7 +1477,7 @@ DEFPY_YANG(
PREFIX_LIST_NAME_STR
ACCESS_LIST_SEQ_STR)
{
- return plist_remove(vty, "ipv6", name, seq_str, NULL, NULL, 0, 0);
+ return plist_remove(vty, "ipv6", name, seq, NULL, NULL, 0, 0);
}
DEFPY_YANG(
@@ -1616,19 +1531,7 @@ DEFPY_YANG(
PREFIX_LIST_NAME_STR
ACCESS_LIST_REMARK_STR)
{
- char xpath[XPATH_MAXLEN];
- int rv;
-
- snprintf(xpath, sizeof(xpath),
- "/frr-filter:lib/prefix-list[type='ipv6'][name='%s']/remark",
- name);
- nb_cli_enqueue_change(vty, xpath, NB_OP_DESTROY, NULL);
-
- rv = nb_cli_apply_changes(vty, NULL);
- if (rv == CMD_SUCCESS)
- return plist_remove_if_empty(vty, "ipv6", name);
-
- return rv;
+ return filter_remove_check_empty(vty, "prefix", "ipv6", name, 0, true);
}
ALIAS(
diff --git a/lib/filter_nb.c b/lib/filter_nb.c
index 1c436cc6f1..eba4e421c0 100644
--- a/lib/filter_nb.c
+++ b/lib/filter_nb.c
@@ -1785,3 +1785,35 @@ const struct frr_yang_module_info frr_filter_info = {
},
}
};
+
+const struct frr_yang_module_info frr_filter_cli_info = {
+ .name = "frr-filter",
+ .ignore_cfg_cbs = true,
+ .nodes = {
+ {
+ .xpath = "/frr-filter:lib/access-list/remark",
+ .cbs.cli_show = access_list_remark_show,
+ },
+ {
+ .xpath = "/frr-filter:lib/access-list/entry",
+ .cbs = {
+ .cli_cmp = access_list_cmp,
+ .cli_show = access_list_show,
+ }
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/remark",
+ .cbs.cli_show = prefix_list_remark_show,
+ },
+ {
+ .xpath = "/frr-filter:lib/prefix-list/entry",
+ .cbs = {
+ .cli_cmp = prefix_list_cmp,
+ .cli_show = prefix_list_show,
+ }
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/routemap.c b/lib/routemap.c
index e8a92cda0b..6b3f81b4d4 100644
--- a/lib/routemap.c
+++ b/lib/routemap.c
@@ -3409,7 +3409,7 @@ DEFUN_HIDDEN(show_route_map_pfx_tbl, show_route_map_pfx_tbl_cmd,
}
/* Initialization of route map vector. */
-void route_map_init(void)
+void route_map_init_new(bool in_backend)
{
int i;
@@ -3424,7 +3424,10 @@ void route_map_init(void)
UNSET_FLAG(rmap_debug, DEBUG_ROUTEMAP);
- route_map_cli_init();
+ if (!in_backend) {
+ /* we do not want to handle config commands in the backend */
+ route_map_cli_init();
+ }
/* Install route map top node. */
install_node(&rmap_debug_node);
@@ -3444,3 +3447,8 @@ void route_map_init(void)
install_element(ENABLE_NODE, &show_route_map_pfx_tbl_cmd);
}
+
+void route_map_init(void)
+{
+ route_map_init_new(false);
+}
diff --git a/lib/routemap.h b/lib/routemap.h
index 08e341221d..dfb84ced5b 100644
--- a/lib/routemap.h
+++ b/lib/routemap.h
@@ -401,6 +401,7 @@ enum ecommunity_lb_type {
/* Prototypes. */
extern void route_map_init(void);
+extern void route_map_init_new(bool in_backend);
/*
* This should only be called on shutdown
@@ -1024,6 +1025,7 @@ routemap_hook_context_insert(struct route_map_index *rmi);
void routemap_hook_context_free(struct routemap_hook_context *rhc);
extern const struct frr_yang_module_info frr_route_map_info;
+extern const struct frr_yang_module_info frr_route_map_cli_info;
/* routemap_cli.c */
extern int route_map_instance_cmp(const struct lyd_node *dnode1,
diff --git a/lib/routemap_northbound.c b/lib/routemap_northbound.c
index a7a77cc23b..1bba4dad47 100644
--- a/lib/routemap_northbound.c
+++ b/lib/routemap_northbound.c
@@ -1550,3 +1550,45 @@ const struct frr_yang_module_info frr_route_map_info = {
},
}
};
+
+const struct frr_yang_module_info frr_route_map_cli_info = {
+ .name = "frr-route-map",
+ .ignore_cfg_cbs = true,
+ .nodes = {
+ {
+ .xpath = "/frr-route-map:lib/route-map/optimization-disabled",
+ .cbs.cli_show = route_map_optimization_disabled_show,
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry",
+ .cbs = {
+ .cli_cmp = route_map_instance_cmp,
+ .cli_show = route_map_instance_show,
+ .cli_show_end = route_map_instance_show_end,
+ }
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/description",
+ .cbs.cli_show = route_map_description_show,
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/call",
+ .cbs.cli_show = route_map_call_show,
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/exit-policy",
+ .cbs.cli_show = route_map_exit_policy_show,
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/match-condition",
+ .cbs.cli_show = route_map_condition_show,
+ },
+ {
+ .xpath = "/frr-route-map:lib/route-map/entry/set-action",
+ .cbs.cli_show = route_map_action_show,
+ },
+ {
+ .xpath = NULL,
+ },
+ }
+};
diff --git a/lib/vty.c b/lib/vty.c
index 3fc7c38083..a8d90d901b 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -124,6 +124,12 @@ bool vty_log_commands;
static bool vty_log_commands_perm;
char const *const mgmt_daemons[] = {
+#ifdef HAVE_RIPD
+ "ripd",
+#endif
+#ifdef HAVE_RIPNGD
+ "ripngd",
+#endif
#ifdef HAVE_STATICD
"staticd",
#endif
diff --git a/lib/yang.c b/lib/yang.c
index 7d35fb0d3d..ed855c8498 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -508,6 +508,30 @@ void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg,
ly_set_free(set, NULL);
}
+uint32_t yang_dnode_count(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...)
+{
+ va_list ap;
+ char xpath[XPATH_MAXLEN];
+ struct ly_set *set;
+ uint32_t count;
+
+ va_start(ap, xpath_fmt);
+ vsnprintf(xpath, sizeof(xpath), xpath_fmt, ap);
+ va_end(ap);
+
+ if (lyd_find_xpath(dnode, xpath, &set)) {
+ assert(0);
+ return 0;
+ }
+
+ count = set->count;
+
+ ly_set_free(set, NULL);
+
+ return count;
+}
+
bool yang_dnode_is_default(const struct lyd_node *dnode, const char *xpath)
{
const struct lysc_node *snode;
diff --git a/lib/yang.h b/lib/yang.h
index dbb7f7163b..1235125f26 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -421,6 +421,21 @@ void yang_dnode_iterate(yang_dnode_iter_cb cb, void *arg,
...) PRINTFRR(4, 5);
/*
+ * Count the number of data nodes that satisfy an XPath query.
+ *
+ * dnode
+ * Base libyang data node to operate on.
+ *
+ * xpath_fmt
+ * XPath expression (absolute or relative).
+ *
+ * ...
+ * any parameters for xpath_fmt.
+ */
+uint32_t yang_dnode_count(const struct lyd_node *dnode, const char *xpath_fmt,
+ ...) PRINTFRR(2, 3);
+
+/*
* Check if the libyang data node contains a default value. Non-presence
* containers are assumed to always contain a default value.
*