2 * Copyright (C) 2018 NetDEF, Inc.
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along
16 * with this program; see the file COPYING; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 #include "lib_errors.h"
27 #include "lib/version.h"
28 #include "northbound.h"
30 #include <confd_lib.h>
31 #include <confd_cdb.h>
33 #include <confd_maapi.h>
35 DEFINE_MTYPE_STATIC(LIB
, CONFD
, "ConfD module");
37 static struct debug nb_dbg_client_confd
= {0, "Northbound client: ConfD"};
39 static struct thread_master
*master
;
40 static struct sockaddr confd_addr
;
41 static int cdb_sub_sock
, dp_ctl_sock
, dp_worker_sock
;
42 static struct thread
*t_cdb_sub
, *t_dp_ctl
, *t_dp_worker
;
43 static struct confd_daemon_ctx
*dctx
;
44 static struct confd_notification_ctx
*live_ctx
;
45 static bool confd_connected
;
46 static struct list
*confd_spoints
;
47 static struct nb_transaction
*transaction
;
49 static void frr_confd_finish_cdb(void);
50 static void frr_confd_finish_dp(void);
51 static int frr_confd_finish(void);
53 #define flog_err_confd(funcname) \
54 flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \
55 (funcname), confd_strerror(confd_errno), confd_errno, \
59 /* ------------ Utils ------------ */
61 /* Get XPath string from ConfD hashed keypath. */
62 static void frr_confd_get_xpath(const confd_hkeypath_t
*kp
, char *xpath
,
67 confd_xpath_pp_kpath(xpath
, len
, 0, kp
);
70 * Replace double quotes by single quotes (the format accepted by the
74 while ((p
= strchr(p
, '"')) != NULL
)
78 /* Convert ConfD binary value to a string. */
79 static int frr_confd_val2str(const char *xpath
, const confd_value_t
*value
,
80 char *string
, size_t string_size
)
82 struct confd_cs_node
*csp
;
84 csp
= confd_cs_node_cd(NULL
, xpath
);
86 flog_err_confd("confd_cs_node_cd");
89 if (confd_val2str(csp
->info
.type
, value
, string
, string_size
)
91 flog_err_confd("confd_val2str");
98 /* Obtain list entry from ConfD hashed keypath. */
99 static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t
*kp
,
100 struct nb_node
*nb_node
,
101 const void **list_entry
)
103 struct nb_node
*nb_node_list
;
104 int parent_lists
= 0;
110 * Count the number of YANG lists in the path, disconsidering the
113 nb_node_list
= nb_node
;
114 while (nb_node_list
->parent_list
) {
115 nb_node_list
= nb_node_list
->parent_list
;
118 if (nb_node
->snode
->nodetype
!= LYS_LIST
&& parent_lists
== 0)
121 /* Start from the beginning and move down the tree. */
122 for (int i
= kp
->len
; i
>= 0; i
--) {
123 struct yang_list_keys keys
;
125 /* Not a YANG list. */
126 if (kp
->v
[i
][0].type
!= C_BUF
)
129 /* Obtain list keys. */
130 memset(&keys
, 0, sizeof(keys
));
131 for (int j
= 0; kp
->v
[i
][j
].type
!= C_NOEXISTS
; j
++) {
132 strlcpy(keys
.key
[keys
.num
],
133 (char *)kp
->v
[i
][j
].val
.buf
.ptr
,
134 sizeof(keys
.key
[keys
.num
]));
138 /* Obtain northbound node associated to the YANG list. */
139 nb_node_list
= nb_node
;
140 for (int j
= curr_list
; j
< parent_lists
; j
++)
141 nb_node_list
= nb_node_list
->parent_list
;
143 /* Obtain list entry. */
144 if (!CHECK_FLAG(nb_node_list
->flags
, F_NB_NODE_KEYLESS_LIST
)) {
145 *list_entry
= nb_callback_lookup_entry(
146 nb_node
, *list_entry
, &keys
);
147 if (*list_entry
== NULL
)
150 unsigned long ptr_ulong
;
152 /* Retrieve list entry from pseudo-key (string). */
153 if (sscanf(keys
.key
[0], "%lu", &ptr_ulong
) != 1)
155 *list_entry
= (const void *)ptr_ulong
;
164 /* Fill the current date and time into a confd_datetime structure. */
165 static void getdatetime(struct confd_datetime
*datetime
)
170 gettimeofday(&tv
, NULL
);
171 gmtime_r(&tv
.tv_sec
, &tm
);
173 memset(datetime
, 0, sizeof(*datetime
));
174 datetime
->year
= 1900 + tm
.tm_year
;
175 datetime
->month
= tm
.tm_mon
+ 1;
176 datetime
->day
= tm
.tm_mday
;
177 datetime
->sec
= tm
.tm_sec
;
178 datetime
->micro
= tv
.tv_usec
;
179 datetime
->timezone
= 0;
180 datetime
->timezone_minutes
= 0;
181 datetime
->hour
= tm
.tm_hour
;
182 datetime
->min
= tm
.tm_min
;
185 /* ------------ CDB code ------------ */
187 struct cdb_iter_args
{
188 struct nb_config
*candidate
;
192 static enum cdb_iter_ret
193 frr_confd_cdb_diff_iter(confd_hkeypath_t
*kp
, enum cdb_iter_op cdb_op
,
194 confd_value_t
*oldv
, confd_value_t
*newv
, void *args
)
196 char xpath
[XPATH_MAXLEN
];
197 struct nb_node
*nb_node
;
198 enum nb_operation nb_op
;
199 struct cdb_iter_args
*iter_args
= args
;
200 char value_str
[YANG_VALUE_MAXLEN
];
201 struct yang_data
*data
;
205 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
208 * HACK: obtain value of leaf-list elements from the XPath due to
209 * a bug in the ConfD API.
212 sb1
= strrchr(xpath
, '[');
213 sb2
= strrchr(xpath
, ']');
214 if (sb1
&& sb2
&& !strchr(sb1
, '=')) {
216 strlcpy(value_str
, sb1
+ 1, sizeof(value_str
));
220 nb_node
= nb_node_find(xpath
);
222 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
223 "%s: unknown data path: %s", __func__
, xpath
);
224 iter_args
->error
= true;
228 /* Map operation values. */
231 nb_op
= NB_OP_CREATE
;
234 nb_op
= NB_OP_DESTROY
;
237 if (nb_operation_is_valid(NB_OP_MODIFY
, nb_node
->snode
))
238 nb_op
= NB_OP_MODIFY
;
240 /* Ignore list keys modifications. */
243 case MOP_MOVED_AFTER
:
247 /* We're not interested on this. */
250 flog_err(EC_LIB_DEVELOPMENT
,
251 "%s: unexpected operation %u [xpath %s]", __func__
,
253 iter_args
->error
= true;
257 /* Convert ConfD value to a string. */
258 if (nb_node
->snode
->nodetype
!= LYS_LEAFLIST
&& newv
259 && frr_confd_val2str(nb_node
->xpath
, newv
, value_str
,
262 flog_err(EC_LIB_CONFD_DATA_CONVERT
,
263 "%s: failed to convert ConfD value to a string",
265 iter_args
->error
= true;
269 /* Edit the candidate configuration. */
270 data
= yang_data_new(xpath
, value_str
);
271 ret
= nb_candidate_edit(iter_args
->candidate
, nb_node
, nb_op
, xpath
,
273 yang_data_free(data
);
276 EC_LIB_NB_CANDIDATE_EDIT_ERROR
,
277 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
278 __func__
, nb_operation_name(nb_op
), xpath
);
279 iter_args
->error
= true;
286 static void frr_confd_cdb_read_cb_prepare(int fd
, int *subp
, int reslen
)
288 struct nb_context context
= {};
289 struct nb_config
*candidate
;
290 struct cdb_iter_args iter_args
;
291 char errmsg
[BUFSIZ
] = {0};
294 candidate
= nb_config_dup(running_config
);
296 /* Iterate over all configuration changes. */
297 iter_args
.candidate
= candidate
;
298 iter_args
.error
= false;
299 for (int i
= 0; i
< reslen
; i
++) {
300 if (cdb_diff_iterate(fd
, subp
[i
], frr_confd_cdb_diff_iter
,
301 ITER_WANT_PREV
, &iter_args
)
303 flog_err_confd("cdb_diff_iterate");
308 if (iter_args
.error
) {
309 nb_config_free(candidate
);
311 if (cdb_sub_abort_trans(
312 cdb_sub_sock
, CONFD_ERRCODE_APPLICATION_INTERNAL
, 0,
313 0, "Couldn't apply configuration changes")
315 flog_err_confd("cdb_sub_abort_trans");
322 * Validate the configuration changes and allocate all resources
323 * required to apply them.
326 context
.client
= NB_CLIENT_CONFD
;
327 ret
= nb_candidate_commit_prepare(&context
, candidate
, NULL
,
328 &transaction
, errmsg
, sizeof(errmsg
));
329 if (ret
!= NB_OK
&& ret
!= NB_ERR_NO_CHANGES
) {
330 enum confd_errcode errcode
;
334 errcode
= CONFD_ERRCODE_IN_USE
;
336 case NB_ERR_RESOURCE
:
337 errcode
= CONFD_ERRCODE_RESOURCE_DENIED
;
340 errcode
= CONFD_ERRCODE_APPLICATION
;
344 /* Reject the configuration changes. */
345 if (cdb_sub_abort_trans(cdb_sub_sock
, errcode
, 0, 0, "%s",
348 flog_err_confd("cdb_sub_abort_trans");
352 /* Acknowledge the notification. */
353 if (cdb_sync_subscription_socket(fd
, CDB_DONE_PRIORITY
)
355 flog_err_confd("cdb_sync_subscription_socket");
359 /* No configuration changes. */
361 nb_config_free(candidate
);
365 static void frr_confd_cdb_read_cb_commit(int fd
, int *subp
, int reslen
)
368 * No need to process the configuration changes again as we're already
369 * keeping track of them in the "transaction" variable.
373 /* Apply the transaction. */
375 struct nb_config
*candidate
= transaction
->config
;
376 char errmsg
[BUFSIZ
] = {0};
378 nb_candidate_commit_apply(transaction
, true, NULL
, errmsg
,
380 nb_config_free(candidate
);
383 /* Acknowledge the notification. */
384 if (cdb_sync_subscription_socket(fd
, CDB_DONE_PRIORITY
) != CONFD_OK
) {
385 flog_err_confd("cdb_sync_subscription_socket");
390 static int frr_confd_cdb_read_cb_abort(int fd
, int *subp
, int reslen
)
393 * No need to process the configuration changes again as we're already
394 * keeping track of them in the "transaction" variable.
398 /* Abort the transaction. */
400 struct nb_config
*candidate
= transaction
->config
;
401 char errmsg
[BUFSIZ
] = {0};
403 nb_candidate_commit_abort(transaction
, errmsg
, sizeof(errmsg
));
404 nb_config_free(candidate
);
407 /* Acknowledge the notification. */
408 if (cdb_sync_subscription_socket(fd
, CDB_DONE_PRIORITY
) != CONFD_OK
) {
409 flog_err_confd("cdb_sync_subscription_socket");
416 static void frr_confd_cdb_read_cb(struct thread
*thread
)
418 int fd
= THREAD_FD(thread
);
419 enum cdb_sub_notification cdb_ev
;
424 thread_add_read(master
, frr_confd_cdb_read_cb
, NULL
, fd
, &t_cdb_sub
);
426 if (cdb_read_subscription_socket2(fd
, &cdb_ev
, &flags
, &subp
, &reslen
)
428 flog_err_confd("cdb_read_subscription_socket2");
433 case CDB_SUB_PREPARE
:
434 frr_confd_cdb_read_cb_prepare(fd
, subp
, reslen
);
437 frr_confd_cdb_read_cb_commit(fd
, subp
, reslen
);
440 frr_confd_cdb_read_cb_abort(fd
, subp
, reslen
);
443 flog_err_confd("unknown CDB event");
448 /* Trigger CDB subscriptions to read the startup configuration. */
449 static void *thread_cdb_trigger_subscriptions(void *data
)
452 int *sub_points
= NULL
, len
= 0;
453 struct listnode
*node
;
457 /* Create CDB data socket. */
458 sock
= socket(PF_INET
, SOCK_STREAM
, 0);
460 flog_err(EC_LIB_SOCKET
, "%s: failed to create socket: %s",
461 __func__
, safe_strerror(errno
));
465 if (cdb_connect(sock
, CDB_DATA_SOCKET
, &confd_addr
,
466 sizeof(struct sockaddr_in
))
468 flog_err_confd("cdb_connect");
473 * Fill array containing the subscription point of all loaded YANG
476 len
= listcount(confd_spoints
);
477 sub_points
= XCALLOC(MTYPE_CONFD
, len
* sizeof(int));
478 for (ALL_LIST_ELEMENTS_RO(confd_spoints
, node
, spoint
))
479 sub_points
[i
++] = *spoint
;
481 if (cdb_trigger_subscriptions(sock
, sub_points
, len
) != CONFD_OK
) {
482 flog_err_confd("cdb_trigger_subscriptions");
486 /* Cleanup and exit thread. */
487 XFREE(MTYPE_CONFD
, sub_points
);
493 static int frr_confd_subscribe(const struct lysc_node
*snode
, void *arg
)
495 struct yang_module
*module
= arg
;
496 struct nb_node
*nb_node
;
500 switch (snode
->nodetype
) {
507 return YANG_ITER_CONTINUE
;
510 if (CHECK_FLAG(snode
->flags
, LYS_CONFIG_R
))
511 return YANG_ITER_CONTINUE
;
513 nb_node
= snode
->priv
;
515 return YANG_ITER_CONTINUE
;
517 DEBUGD(&nb_dbg_client_confd
, "%s: subscribing to '%s'", __func__
,
520 spoint
= XMALLOC(MTYPE_CONFD
, sizeof(*spoint
));
521 ret
= cdb_subscribe2(cdb_sub_sock
, CDB_SUB_RUNNING_TWOPHASE
,
522 CDB_SUB_WANT_ABORT_ON_ABORT
, 3, spoint
,
523 module
->confd_hash
, nb_node
->xpath
);
524 if (ret
!= CONFD_OK
) {
525 flog_err_confd("cdb_subscribe2");
526 XFREE(MTYPE_CONFD
, spoint
);
527 return YANG_ITER_CONTINUE
;
530 listnode_add(confd_spoints
, spoint
);
531 return YANG_ITER_CONTINUE
;
534 static int frr_confd_init_cdb(void)
536 struct yang_module
*module
;
537 pthread_t cdb_trigger_thread
;
539 /* Create CDB subscription socket. */
540 cdb_sub_sock
= socket(PF_INET
, SOCK_STREAM
, 0);
541 if (cdb_sub_sock
< 0) {
542 flog_err(EC_LIB_SOCKET
, "%s: failed to create socket: %s",
543 __func__
, safe_strerror(errno
));
547 if (cdb_connect(cdb_sub_sock
, CDB_SUBSCRIPTION_SOCKET
, &confd_addr
,
548 sizeof(struct sockaddr_in
))
550 flog_err_confd("cdb_connect");
554 /* Subscribe to all loaded YANG data modules. */
555 confd_spoints
= list_new();
556 RB_FOREACH (module
, yang_modules
, &yang_modules
) {
557 module
->confd_hash
= confd_str2hash(module
->info
->ns
);
558 if (module
->confd_hash
== 0) {
561 "%s: failed to find hash value for namespace %s",
562 __func__
, module
->info
->ns
);
567 * The CDB API doesn't provide a mechanism to subscribe to an
568 * entire YANG module. So we have to find the top level
569 * nodes ourselves and subscribe to their paths.
571 yang_snodes_iterate(module
->info
, frr_confd_subscribe
, 0,
575 if (cdb_subscribe_done(cdb_sub_sock
) != CONFD_OK
) {
576 flog_err_confd("cdb_subscribe_done");
580 /* Create short lived pthread to trigger the CDB subscriptions. */
581 if (pthread_create(&cdb_trigger_thread
, NULL
,
582 thread_cdb_trigger_subscriptions
, NULL
)) {
583 flog_err(EC_LIB_SYSTEM_CALL
, "%s: error creating pthread: %s",
584 __func__
, safe_strerror(errno
));
587 pthread_detach(cdb_trigger_thread
);
589 thread_add_read(master
, frr_confd_cdb_read_cb
, NULL
, cdb_sub_sock
,
595 frr_confd_finish_cdb();
600 static void frr_confd_finish_cdb(void)
602 if (cdb_sub_sock
> 0) {
603 THREAD_OFF(t_cdb_sub
);
604 cdb_close(cdb_sub_sock
);
608 /* ------------ DP code ------------ */
610 static int frr_confd_transaction_init(struct confd_trans_ctx
*tctx
)
612 confd_trans_set_fd(tctx
, dp_worker_sock
);
617 #define CONFD_MAX_CHILD_NODES 32
619 static int frr_confd_data_get_elem(struct confd_trans_ctx
*tctx
,
620 confd_hkeypath_t
*kp
)
622 struct nb_node
*nb_node
;
623 char xpath
[XPATH_MAXLEN
];
624 struct yang_data
*data
;
626 const void *list_entry
= NULL
;
628 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
630 nb_node
= nb_node_find(xpath
);
632 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
633 "%s: unknown data path: %s", __func__
, xpath
);
634 confd_data_reply_not_found(tctx
);
638 if (frr_confd_hkeypath_get_list_entry(kp
, nb_node
, &list_entry
) != 0) {
639 confd_data_reply_not_found(tctx
);
643 data
= nb_callback_get_elem(nb_node
, xpath
, list_entry
);
646 CONFD_SET_STR(&v
, data
->value
);
647 confd_data_reply_value(tctx
, &v
);
649 confd_data_reply_found(tctx
);
650 yang_data_free(data
);
652 confd_data_reply_not_found(tctx
);
657 static int frr_confd_data_get_next(struct confd_trans_ctx
*tctx
,
658 confd_hkeypath_t
*kp
, long next
)
660 struct nb_node
*nb_node
;
661 char xpath
[XPATH_MAXLEN
];
662 struct yang_data
*data
;
663 const void *parent_list_entry
, *nb_next
;
664 confd_value_t v
[LIST_MAXKEYS
];
666 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
668 nb_node
= nb_node_find(xpath
);
670 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
671 "%s: unknown data path: %s", __func__
, xpath
);
672 confd_data_reply_next_key(tctx
, NULL
, -1, -1);
676 if (frr_confd_hkeypath_get_list_entry(kp
, nb_node
, &parent_list_entry
)
678 /* List entry doesn't exist anymore. */
679 confd_data_reply_next_key(tctx
, NULL
, -1, -1);
683 nb_next
= nb_callback_get_next(nb_node
, parent_list_entry
,
684 (next
== -1) ? NULL
: (void *)next
);
686 /* End of the list or leaf-list. */
687 confd_data_reply_next_key(tctx
, NULL
, -1, -1);
691 switch (nb_node
->snode
->nodetype
) {
693 if (!CHECK_FLAG(nb_node
->flags
, F_NB_NODE_KEYLESS_LIST
)) {
694 struct yang_list_keys keys
;
696 memset(&keys
, 0, sizeof(keys
));
697 if (nb_callback_get_keys(nb_node
, nb_next
, &keys
)
699 flog_warn(EC_LIB_NB_CB_STATE
,
700 "%s: failed to get list keys",
702 confd_data_reply_next_key(tctx
, NULL
, -1, -1);
706 /* Feed keys to ConfD. */
707 for (size_t i
= 0; i
< keys
.num
; i
++)
708 CONFD_SET_STR(&v
[i
], keys
.key
[i
]);
709 confd_data_reply_next_key(tctx
, v
, keys
.num
,
712 char pointer_str
[32];
715 * ConfD 6.6 user guide, chapter 6.11 (Operational data
716 * lists without keys):
717 * "To support this without having completely separate
718 * APIs, we use a "pseudo" key in the ConfD APIs for
719 * this type of list. This key is not part of the data
720 * model, and completely hidden in the northbound agent
721 * interfaces, but is used with e.g. the get_next() and
722 * get_elem() callbacks as if it were a normal key. This
723 * "pseudo" key is always a single signed 64-bit
724 * integer, i.e. the confd_value_t type is C_INT64. The
725 * values can be chosen arbitrarily by the application,
726 * as long as a key value returned by get_next() can be
727 * used to get the data for the corresponding list entry
728 * with get_elem() or get_object() as usual. It could
729 * e.g. be an index into an array that holds the data,
730 * or even a memory address in integer form".
732 * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY
733 * option, we must convert our pseudo-key (a void
734 * pointer) to a string before sending it to confd.
736 snprintf(pointer_str
, sizeof(pointer_str
), "%lu",
737 (unsigned long)nb_next
);
738 CONFD_SET_STR(&v
[0], pointer_str
);
739 confd_data_reply_next_key(tctx
, v
, 1, (long)nb_next
);
743 data
= nb_callback_get_elem(nb_node
, xpath
, nb_next
);
746 CONFD_SET_STR(&v
[0], data
->value
);
747 confd_data_reply_next_key(tctx
, v
, 1,
750 yang_data_free(data
);
752 confd_data_reply_next_key(tctx
, NULL
, -1, -1);
762 * Optional callback - implemented for performance reasons.
764 static int frr_confd_data_get_object(struct confd_trans_ctx
*tctx
,
765 confd_hkeypath_t
*kp
)
767 struct nb_node
*nb_node
;
768 const struct lysc_node
*child
;
769 char xpath
[XPATH_MAXLEN
];
770 char xpath_child
[XPATH_MAXLEN
* 2];
771 struct list
*elements
;
772 struct yang_data
*data
;
773 const void *list_entry
;
774 confd_value_t values
[CONFD_MAX_CHILD_NODES
];
777 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
779 nb_node
= nb_node_find(xpath
);
781 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
782 "%s: unknown data path: %s", __func__
, xpath
);
783 confd_data_reply_not_found(tctx
);
787 if (frr_confd_hkeypath_get_list_entry(kp
, nb_node
, &list_entry
) != 0) {
788 confd_data_reply_not_found(tctx
);
792 elements
= yang_data_list_new();
794 /* Loop through list child nodes. */
795 LY_LIST_FOR (lysc_node_child(nb_node
->snode
), child
) {
796 struct nb_node
*nb_node_child
= child
->priv
;
799 if (nvalues
> CONFD_MAX_CHILD_NODES
)
802 v
= &values
[nvalues
++];
804 /* Non-presence containers, lists and leaf-lists. */
805 if (!nb_node_child
->cbs
.get_elem
) {
806 CONFD_SET_NOEXISTS(v
);
810 snprintf(xpath_child
, sizeof(xpath_child
), "%s/%s", xpath
,
812 data
= nb_callback_get_elem(nb_node_child
, xpath_child
,
816 CONFD_SET_STR(v
, data
->value
);
818 /* Presence containers and empty leafs. */
820 v
, nb_node_child
->confd_hash
,
821 confd_str2hash(nb_node_child
->snode
824 listnode_add(elements
, data
);
826 CONFD_SET_NOEXISTS(v
);
829 confd_data_reply_value_array(tctx
, values
, nvalues
);
831 /* Release memory. */
832 list_delete(&elements
);
838 * Optional callback - implemented for performance reasons.
840 static int frr_confd_data_get_next_object(struct confd_trans_ctx
*tctx
,
841 confd_hkeypath_t
*kp
, long next
)
843 char xpath
[XPATH_MAXLEN
];
844 struct nb_node
*nb_node
;
845 struct list
*elements
;
846 const void *parent_list_entry
;
848 #define CONFD_OBJECTS_PER_TIME 100
849 struct confd_next_object objects
[CONFD_OBJECTS_PER_TIME
+ 1];
850 char pseudo_keys
[CONFD_OBJECTS_PER_TIME
][32];
853 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
855 nb_node
= nb_node_find(xpath
);
857 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
858 "%s: unknown data path: %s", __func__
, xpath
);
859 confd_data_reply_next_object_array(tctx
, NULL
, 0, 0);
863 if (frr_confd_hkeypath_get_list_entry(kp
, nb_node
, &parent_list_entry
)
865 confd_data_reply_next_object_array(tctx
, NULL
, 0, 0);
869 elements
= yang_data_list_new();
870 nb_next
= (next
== -1) ? NULL
: (void *)next
;
872 memset(objects
, 0, sizeof(objects
));
873 for (int j
= 0; j
< CONFD_OBJECTS_PER_TIME
; j
++) {
874 struct confd_next_object
*object
;
875 const struct lysc_node
*child
;
876 struct yang_data
*data
;
879 object
= &objects
[j
];
881 nb_next
= nb_callback_get_next(nb_node
, parent_list_entry
,
884 /* End of the list. */
887 object
->next
= (long)nb_next
;
889 /* Leaf-lists require special handling. */
890 if (nb_node
->snode
->nodetype
== LYS_LEAFLIST
) {
891 object
->v
= XMALLOC(MTYPE_CONFD
, sizeof(confd_value_t
));
892 data
= nb_callback_get_elem(nb_node
, xpath
, nb_next
);
893 assert(data
&& data
->value
);
894 CONFD_SET_STR(object
->v
, data
->value
);
896 listnode_add(elements
, data
);
902 CONFD_MAX_CHILD_NODES
* sizeof(confd_value_t
));
905 * ConfD 6.6 user guide, chapter 6.11 (Operational data lists
907 * "In the response to the get_next_object() callback, the data
908 * provider is expected to provide the key values along with the
909 * other leafs in an array that is populated according to the
910 * data model. This must be done also for this type of list,
911 * even though the key isn't actually in the data model. The
912 * "pseudo" key must always be the first element in the array".
914 if (CHECK_FLAG(nb_node
->flags
, F_NB_NODE_KEYLESS_LIST
)) {
917 snprintf(pseudo_keys
[j
], sizeof(pseudo_keys
[j
]), "%lu",
918 (unsigned long)nb_next
);
920 v
= &object
->v
[nvalues
++];
921 CONFD_SET_STR(v
, pseudo_keys
[j
]);
924 /* Loop through list child nodes. */
925 LY_LIST_FOR (lysc_node_child(nb_node
->snode
), child
) {
926 struct nb_node
*nb_node_child
= child
->priv
;
927 char xpath_child
[XPATH_MAXLEN
* 2];
930 if (nvalues
> CONFD_MAX_CHILD_NODES
)
933 v
= &object
->v
[nvalues
++];
935 /* Non-presence containers, lists and leaf-lists. */
936 if (!nb_node_child
->cbs
.get_elem
) {
937 CONFD_SET_NOEXISTS(v
);
941 snprintf(xpath_child
, sizeof(xpath_child
), "%s/%s",
943 data
= nb_callback_get_elem(nb_node_child
, xpath_child
,
947 CONFD_SET_STR(v
, data
->value
);
950 * Presence containers and empty leafs.
953 v
, nb_node_child
->confd_hash
,
958 listnode_add(elements
, data
);
960 CONFD_SET_NOEXISTS(v
);
968 confd_data_reply_next_object_array(tctx
, NULL
, 0, 0);
969 list_delete(&elements
);
973 /* Detect end of the list. */
976 objects
[nobjects
].v
= NULL
;
979 /* Reply to ConfD. */
980 confd_data_reply_next_object_arrays(tctx
, objects
, nobjects
, 0);
984 /* Release memory. */
985 list_delete(&elements
);
986 for (int j
= 0; j
< nobjects
; j
++) {
987 struct confd_next_object
*object
;
989 object
= &objects
[j
];
990 XFREE(MTYPE_CONFD
, object
->v
);
996 static int frr_confd_notification_send(const char *xpath
,
997 struct list
*arguments
)
999 struct nb_node
*nb_node
;
1000 struct yang_module
*module
;
1001 struct confd_datetime now
;
1002 confd_tag_value_t
*values
;
1005 struct yang_data
*data
;
1006 struct listnode
*node
;
1009 nb_node
= nb_node_find(xpath
);
1011 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
1012 "%s: unknown data path: %s", __func__
, xpath
);
1015 module
= yang_module_find(nb_node
->snode
->module
->name
);
1020 nvalues
+= listcount(arguments
);
1022 values
= XMALLOC(MTYPE_CONFD
, nvalues
* sizeof(*values
));
1024 CONFD_SET_TAG_XMLBEGIN(&values
[i
++], nb_node
->confd_hash
,
1025 module
->confd_hash
);
1026 for (ALL_LIST_ELEMENTS_RO(arguments
, node
, data
)) {
1027 struct nb_node
*nb_node_arg
;
1029 nb_node_arg
= nb_node_find(data
->xpath
);
1031 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
1032 "%s: unknown data path: %s", __func__
,
1034 XFREE(MTYPE_CONFD
, values
);
1038 CONFD_SET_TAG_STR(&values
[i
++], nb_node_arg
->confd_hash
,
1041 CONFD_SET_TAG_XMLEND(&values
[i
++], nb_node
->confd_hash
,
1042 module
->confd_hash
);
1045 ret
= confd_notification_send(live_ctx
, &now
, values
, nvalues
);
1047 /* Release memory. */
1048 XFREE(MTYPE_CONFD
, values
);
1050 /* Map ConfD return code to northbound return code. */
1059 static int frr_confd_action_init(struct confd_user_info
*uinfo
)
1061 confd_action_set_fd(uinfo
, dp_worker_sock
);
1066 static int frr_confd_action_execute(struct confd_user_info
*uinfo
,
1067 struct xml_tag
*name
, confd_hkeypath_t
*kp
,
1068 confd_tag_value_t
*params
, int nparams
)
1070 char xpath
[XPATH_MAXLEN
];
1071 struct nb_node
*nb_node
;
1073 struct list
*output
;
1074 struct yang_data
*data
;
1075 confd_tag_value_t
*reply
;
1077 char errmsg
[BUFSIZ
] = {0};
1079 /* Getting the XPath is tricky. */
1081 /* This is a YANG RPC. */
1082 frr_confd_get_xpath(kp
, xpath
, sizeof(xpath
));
1083 strlcat(xpath
, "/", sizeof(xpath
));
1084 strlcat(xpath
, confd_hash2str(name
->tag
), sizeof(xpath
));
1086 /* This is a YANG action. */
1087 snprintf(xpath
, sizeof(xpath
), "/%s:%s",
1088 confd_ns2prefix(name
->ns
), confd_hash2str(name
->tag
));
1091 nb_node
= nb_node_find(xpath
);
1093 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
1094 "%s: unknown data path: %s", __func__
, xpath
);
1098 input
= yang_data_list_new();
1099 output
= yang_data_list_new();
1101 /* Process input nodes. */
1102 for (int i
= 0; i
< nparams
; i
++) {
1103 char xpath_input
[XPATH_MAXLEN
* 2];
1104 char value_str
[YANG_VALUE_MAXLEN
];
1106 snprintf(xpath_input
, sizeof(xpath_input
), "%s/%s", xpath
,
1107 confd_hash2str(params
[i
].tag
.tag
));
1109 if (frr_confd_val2str(xpath_input
, ¶ms
[i
].v
, value_str
,
1113 EC_LIB_CONFD_DATA_CONVERT
,
1114 "%s: failed to convert ConfD value to a string",
1120 data
= yang_data_new(xpath_input
, value_str
);
1121 listnode_add(input
, data
);
1124 /* Execute callback registered for this XPath. */
1125 if (nb_callback_rpc(nb_node
, xpath
, input
, output
, errmsg
,
1128 flog_warn(EC_LIB_NB_CB_RPC
, "%s: rpc callback failed: %s",
1134 /* Process output nodes. */
1135 if (listcount(output
) > 0) {
1136 struct listnode
*node
;
1139 reply
= XMALLOC(MTYPE_CONFD
,
1140 listcount(output
) * sizeof(*reply
));
1142 for (ALL_LIST_ELEMENTS_RO(output
, node
, data
)) {
1143 struct nb_node
*nb_node_output
;
1146 nb_node_output
= nb_node_find(data
->xpath
);
1147 if (!nb_node_output
) {
1148 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH
,
1149 "%s: unknown data path: %s", __func__
,
1154 hash
= confd_str2hash(nb_node_output
->snode
->name
);
1155 CONFD_SET_TAG_STR(&reply
[i
++], hash
, data
->value
);
1157 confd_action_reply_values(uinfo
, reply
, listcount(output
));
1158 XFREE(MTYPE_CONFD
, reply
);
1162 /* Release memory. */
1163 list_delete(&input
);
1164 list_delete(&output
);
1170 static int frr_confd_dp_read(struct confd_daemon_ctx
*dctx
, int fd
)
1174 ret
= confd_fd_ready(dctx
, fd
);
1175 if (ret
== CONFD_EOF
) {
1176 flog_err_confd("confd_fd_ready");
1179 } else if (ret
== CONFD_ERR
&& confd_errno
!= CONFD_ERR_EXTERNAL
) {
1180 flog_err_confd("confd_fd_ready");
1188 static void frr_confd_dp_ctl_read(struct thread
*thread
)
1190 struct confd_daemon_ctx
*dctx
= THREAD_ARG(thread
);
1191 int fd
= THREAD_FD(thread
);
1193 thread_add_read(master
, frr_confd_dp_ctl_read
, dctx
, fd
, &t_dp_ctl
);
1195 frr_confd_dp_read(dctx
, fd
);
1198 static void frr_confd_dp_worker_read(struct thread
*thread
)
1200 struct confd_daemon_ctx
*dctx
= THREAD_ARG(thread
);
1201 int fd
= THREAD_FD(thread
);
1203 thread_add_read(master
, frr_confd_dp_worker_read
, dctx
, fd
, &t_dp_worker
);
1205 frr_confd_dp_read(dctx
, fd
);
1208 static int frr_confd_subscribe_state(const struct lysc_node
*snode
, void *arg
)
1210 struct nb_node
*nb_node
= snode
->priv
;
1211 struct confd_data_cbs
*data_cbs
= arg
;
1213 if (!nb_node
|| !CHECK_FLAG(snode
->flags
, LYS_CONFIG_R
))
1214 return YANG_ITER_CONTINUE
;
1215 /* We only need to subscribe to the root of the state subtrees. */
1216 if (snode
->parent
&& CHECK_FLAG(snode
->parent
->flags
, LYS_CONFIG_R
))
1217 return YANG_ITER_CONTINUE
;
1219 DEBUGD(&nb_dbg_client_confd
,
1220 "%s: providing data to '%s' (callpoint %s)", __func__
,
1221 nb_node
->xpath
, snode
->name
);
1223 strlcpy(data_cbs
->callpoint
, snode
->name
, sizeof(data_cbs
->callpoint
));
1224 if (confd_register_data_cb(dctx
, data_cbs
) != CONFD_OK
)
1225 flog_err_confd("confd_register_data_cb");
1227 return YANG_ITER_CONTINUE
;
1230 static int frr_confd_init_dp(const char *program_name
)
1232 struct confd_trans_cbs trans_cbs
;
1233 struct confd_data_cbs data_cbs
;
1234 struct confd_notification_stream_cbs ncbs
;
1235 struct confd_action_cbs acbs
;
1237 /* Initialize daemon context. */
1238 dctx
= confd_init_daemon(program_name
);
1240 flog_err_confd("confd_init_daemon");
1245 * Inform we want to receive YANG values as raw strings, and that we
1246 * want to provide only strings in the reply functions, regardless of
1249 confd_set_daemon_flags(dctx
, CONFD_DAEMON_FLAG_STRINGSONLY
);
1251 /* Establish a control socket. */
1252 dp_ctl_sock
= socket(PF_INET
, SOCK_STREAM
, 0);
1253 if (dp_ctl_sock
< 0) {
1254 flog_err(EC_LIB_SOCKET
, "%s: failed to create socket: %s",
1255 __func__
, safe_strerror(errno
));
1259 if (confd_connect(dctx
, dp_ctl_sock
, CONTROL_SOCKET
, &confd_addr
,
1260 sizeof(struct sockaddr_in
))
1262 flog_err_confd("confd_connect");
1267 * Establish a worker socket (only one since this plugin runs on a
1270 dp_worker_sock
= socket(PF_INET
, SOCK_STREAM
, 0);
1271 if (dp_worker_sock
< 0) {
1272 flog_err(EC_LIB_SOCKET
, "%s: failed to create socket: %s",
1273 __func__
, safe_strerror(errno
));
1276 if (confd_connect(dctx
, dp_worker_sock
, WORKER_SOCKET
, &confd_addr
,
1277 sizeof(struct sockaddr_in
))
1279 flog_err_confd("confd_connect");
1283 /* Register transaction callback functions. */
1284 memset(&trans_cbs
, 0, sizeof(trans_cbs
));
1285 trans_cbs
.init
= frr_confd_transaction_init
;
1286 confd_register_trans_cb(dctx
, &trans_cbs
);
1288 /* Register our read/write callbacks. */
1289 memset(&data_cbs
, 0, sizeof(data_cbs
));
1290 data_cbs
.get_elem
= frr_confd_data_get_elem
;
1291 data_cbs
.exists_optional
= frr_confd_data_get_elem
;
1292 data_cbs
.get_next
= frr_confd_data_get_next
;
1293 data_cbs
.get_object
= frr_confd_data_get_object
;
1294 data_cbs
.get_next_object
= frr_confd_data_get_next_object
;
1297 * Iterate over all loaded YANG modules and subscribe to the paths
1298 * referent to state data.
1300 yang_snodes_iterate(NULL
, frr_confd_subscribe_state
, 0, &data_cbs
);
1302 /* Register notification stream. */
1303 memset(&ncbs
, 0, sizeof(ncbs
));
1304 ncbs
.fd
= dp_worker_sock
;
1306 * RFC 5277 - Section 3.2.3:
1307 * A NETCONF server implementation supporting the notification
1308 * capability MUST support the "NETCONF" notification event
1309 * stream. This stream contains all NETCONF XML event notifications
1310 * supported by the NETCONF server.
1312 strlcpy(ncbs
.streamname
, "NETCONF", sizeof(ncbs
.streamname
));
1313 if (confd_register_notification_stream(dctx
, &ncbs
, &live_ctx
)
1315 flog_err_confd("confd_register_notification_stream");
1319 /* Register the action handler callback. */
1320 memset(&acbs
, 0, sizeof(acbs
));
1321 strlcpy(acbs
.actionpoint
, "actionpoint", sizeof(acbs
.actionpoint
));
1322 acbs
.init
= frr_confd_action_init
;
1323 acbs
.action
= frr_confd_action_execute
;
1324 if (confd_register_action_cbs(dctx
, &acbs
) != CONFD_OK
) {
1325 flog_err_confd("confd_register_action_cbs");
1329 /* Notify we registered all callbacks we wanted. */
1330 if (confd_register_done(dctx
) != CONFD_OK
) {
1331 flog_err_confd("confd_register_done");
1335 thread_add_read(master
, frr_confd_dp_ctl_read
, dctx
, dp_ctl_sock
,
1337 thread_add_read(master
, frr_confd_dp_worker_read
, dctx
, dp_worker_sock
,
1343 frr_confd_finish_dp();
1348 static void frr_confd_finish_dp(void)
1350 if (dp_worker_sock
> 0) {
1351 THREAD_OFF(t_dp_worker
);
1352 close(dp_worker_sock
);
1354 if (dp_ctl_sock
> 0) {
1355 THREAD_OFF(t_dp_ctl
);
1359 confd_release_daemon(dctx
);
1362 /* ------------ CLI ------------ */
1364 DEFUN (debug_nb_confd
,
1366 "[no] debug northbound client confd",
1369 "Northbound debugging\n"
1373 uint32_t mode
= DEBUG_NODE2MODE(vty
->node
);
1374 bool no
= strmatch(argv
[0]->text
, "no");
1376 DEBUG_MODE_SET(&nb_dbg_client_confd
, mode
, !no
);
1381 static int frr_confd_debug_config_write(struct vty
*vty
)
1383 if (DEBUG_MODE_CHECK(&nb_dbg_client_confd
, DEBUG_MODE_CONF
))
1384 vty_out(vty
, "debug northbound client confd\n");
1389 static int frr_confd_debug_set_all(uint32_t flags
, bool set
)
1391 DEBUG_FLAGS_SET(&nb_dbg_client_confd
, flags
, set
);
1393 /* If all modes have been turned off, don't preserve options. */
1394 if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd
, DEBUG_MODE_ALL
))
1395 DEBUG_CLEAR(&nb_dbg_client_confd
);
1400 static void frr_confd_cli_init(void)
1402 hook_register(nb_client_debug_config_write
,
1403 frr_confd_debug_config_write
);
1404 hook_register(nb_client_debug_set_all
, frr_confd_debug_set_all
);
1406 install_element(ENABLE_NODE
, &debug_nb_confd_cmd
);
1407 install_element(CONFIG_NODE
, &debug_nb_confd_cmd
);
1410 /* ------------ Main ------------ */
1412 static int frr_confd_calculate_snode_hash(const struct lysc_node
*snode
,
1415 struct nb_node
*nb_node
= snode
->priv
;
1418 nb_node
->confd_hash
= confd_str2hash(snode
->name
);
1420 return YANG_ITER_CONTINUE
;
1423 static int frr_confd_init(const char *program_name
)
1425 struct sockaddr_in
*confd_addr4
= (struct sockaddr_in
*)&confd_addr
;
1426 int debuglevel
= CONFD_SILENT
;
1429 /* Initialize ConfD library. */
1430 confd_init(program_name
, stderr
, debuglevel
);
1432 confd_addr4
->sin_family
= AF_INET
;
1433 confd_addr4
->sin_addr
.s_addr
= inet_addr("127.0.0.1");
1434 confd_addr4
->sin_port
= htons(CONFD_PORT
);
1435 if (confd_load_schemas(&confd_addr
, sizeof(struct sockaddr_in
))
1437 flog_err_confd("confd_load_schemas");
1441 ret
= frr_confd_init_cdb();
1445 ret
= frr_confd_init_dp(program_name
);
1447 frr_confd_finish_cdb();
1451 yang_snodes_iterate(NULL
, frr_confd_calculate_snode_hash
, 0, NULL
);
1453 hook_register(nb_notification_send
, frr_confd_notification_send
);
1455 confd_connected
= true;
1459 confd_free_schemas();
1464 static int frr_confd_finish(void)
1466 if (!confd_connected
)
1469 frr_confd_finish_cdb();
1470 frr_confd_finish_dp();
1472 confd_free_schemas();
1474 confd_connected
= false;
1479 static int frr_confd_module_late_init(struct thread_master
*tm
)
1483 if (frr_confd_init(frr_get_progname()) < 0) {
1484 flog_err(EC_LIB_CONFD_INIT
,
1485 "failed to initialize the ConfD module");
1489 hook_register(frr_fini
, frr_confd_finish
);
1490 frr_confd_cli_init();
1495 static int frr_confd_module_init(void)
1497 hook_register(frr_late_init
, frr_confd_module_late_init
);
1502 FRR_MODULE_SETUP(.name
= "frr_confd", .version
= FRR_VERSION
,
1503 .description
= "FRR ConfD integration module",
1504 .init
= frr_confd_module_init
,