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