]> git.proxmox.com Git - mirror_frr.git/blame - lib/northbound_confd.c
lib: add support for YANG lists with mixed config and state data
[mirror_frr.git] / lib / northbound_confd.c
CommitLineData
5bce33b3
RW
1/*
2 * Copyright (C) 2018 NetDEF, Inc.
3 * Renato Westphal
4 *
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)
8 * any later version.
9 *
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
13 * more details.
14 *
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
18 */
19
20#include <zebra.h>
21
22#include "log.h"
23#include "lib_errors.h"
24#include "command.h"
25#include "libfrr.h"
26#include "version.h"
27#include "northbound.h"
28
29#include <confd_lib.h>
30#include <confd_cdb.h>
31#include <confd_dp.h>
32#include <confd_maapi.h>
33
34DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module")
35
36static struct thread_master *master;
37static struct sockaddr confd_addr;
38static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock;
39static struct thread *t_cdb_sub, *t_dp_ctl, *t_dp_worker;
40static struct confd_daemon_ctx *dctx;
41static struct confd_notification_ctx *live_ctx;
42static bool confd_connected;
43static struct list *confd_spoints;
44
45static void frr_confd_finish_cdb(void);
46static void frr_confd_finish_dp(void);
47static int frr_confd_finish(void);
48
49#define flog_err_confd(funcname) \
50 flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \
51 (funcname), confd_strerror(confd_errno), confd_errno, \
52 confd_lasterr())
53
54
55/* ------------ Utils ------------ */
56
57/* Get XPath string from ConfD hashed keypath. */
58static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath,
59 size_t len)
60{
61 char *p;
62
63 confd_xpath_pp_kpath(xpath, len, 0, kp);
64
65 /*
66 * Replace double quotes by single quotes (the format accepted by the
67 * northbound API).
68 */
69 p = xpath;
70 while ((p = strchr(p, '"')) != NULL)
71 *p++ = '\'';
72}
73
74/* Convert ConfD binary value to a string. */
75static int frr_confd_val2str(const char *xpath, const confd_value_t *value,
76 char *string, size_t string_size)
77{
78 struct confd_cs_node *csp;
79
80 csp = confd_cs_node_cd(NULL, xpath);
81 if (!csp) {
82 flog_err_confd("confd_cs_node_cd");
83 return -1;
84 }
85 if (confd_val2str(csp->info.type, value, string, string_size)
86 == CONFD_ERR) {
87 flog_err_confd("confd_val2str");
88 return -1;
89 }
90
91 return 0;
92}
93
94/* Obtain list keys from ConfD hashed keypath. */
95static void frr_confd_hkeypath_get_keys(const confd_hkeypath_t *kp,
96 struct yang_list_keys *keys)
97{
98 memset(keys, 0, sizeof(*keys));
99 for (int i = 0; i < kp->len; i++) {
100 if (kp->v[i][0].type != C_BUF)
101 continue;
102
103 for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) {
80243aef 104 strlcpy(keys->key[keys->num],
5bce33b3 105 (char *)kp->v[i][j].val.buf.ptr,
80243aef 106 sizeof(keys->key[keys->num]));
5bce33b3
RW
107 keys->num++;
108 }
109 }
110}
111
112/* Fill the current date and time into a confd_datetime structure. */
113static void getdatetime(struct confd_datetime *datetime)
114{
115 struct tm tm;
116 struct timeval tv;
117
118 gettimeofday(&tv, NULL);
119 gmtime_r(&tv.tv_sec, &tm);
120
121 memset(datetime, 0, sizeof(*datetime));
122 datetime->year = 1900 + tm.tm_year;
123 datetime->month = tm.tm_mon + 1;
124 datetime->day = tm.tm_mday;
125 datetime->sec = tm.tm_sec;
126 datetime->micro = tv.tv_usec;
127 datetime->timezone = 0;
128 datetime->timezone_minutes = 0;
129 datetime->hour = tm.tm_hour;
130 datetime->min = tm.tm_min;
131}
132
133/* ------------ CDB code ------------ */
134
135struct cdb_iter_args {
136 struct nb_config *candidate;
137 bool error;
138};
139
140static enum cdb_iter_ret
141frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op,
142 confd_value_t *oldv, confd_value_t *newv, void *args)
143{
144 char xpath[XPATH_MAXLEN];
145 struct nb_node *nb_node;
146 enum nb_operation nb_op;
147 struct cdb_iter_args *iter_args = args;
148 char value_str[YANG_VALUE_MAXLEN];
149 struct yang_data *data;
150 char *sb1, *sb2;
151 int ret;
152
153 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
154
155 /*
156 * HACK: obtain value of leaf-list elements from the XPath due to
157 * a bug in the ConfD API.
158 */
159 value_str[0] = '\0';
160 sb1 = strrchr(xpath, '[');
161 sb2 = strrchr(xpath, ']');
162 if (sb1 && sb2 && !strchr(sb1, '=')) {
163 *sb2 = '\0';
164 strlcpy(value_str, sb1 + 1, sizeof(value_str));
165 *sb1 = '\0';
166 }
167
168 nb_node = nb_node_find(xpath);
169 if (!nb_node) {
170 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
171 "%s: unknown data path: %s", __func__, xpath);
172 iter_args->error = true;
173 return ITER_STOP;
174 }
175
176 /* Map operation values. */
177 switch (cdb_op) {
178 case MOP_CREATED:
179 nb_op = NB_OP_CREATE;
180 break;
181 case MOP_DELETED:
182 nb_op = NB_OP_DELETE;
183 break;
184 case MOP_VALUE_SET:
185 if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode))
186 nb_op = NB_OP_MODIFY;
187 else
188 /* Ignore list keys modifications. */
189 return ITER_RECURSE;
190 break;
191 case MOP_MOVED_AFTER:
192 nb_op = NB_OP_MOVE;
193 break;
194 case MOP_MODIFIED:
195 /* We're not interested on this. */
196 return ITER_RECURSE;
197 default:
198 flog_err(EC_LIB_DEVELOPMENT,
199 "%s: unexpected operation %u [xpath %s]", __func__,
200 cdb_op, xpath);
201 iter_args->error = true;
202 return ITER_STOP;
203 }
204
205 /* Convert ConfD value to a string. */
206 if (nb_node->snode->nodetype != LYS_LEAFLIST && newv
207 && frr_confd_val2str(nb_node->xpath, newv, value_str,
208 sizeof(value_str))
209 != 0) {
210 flog_err(EC_LIB_CONFD_DATA_CONVERT,
211 "%s: failed to convert ConfD value to a string",
212 __func__);
213 iter_args->error = true;
214 return ITER_STOP;
215 }
216
217 /* Edit the candidate configuration. */
218 data = yang_data_new(xpath, value_str);
219 ret = nb_candidate_edit(iter_args->candidate, nb_node, nb_op, xpath,
220 NULL, data);
221 yang_data_free(data);
222 if (ret != NB_OK) {
223 flog_warn(
224 EC_LIB_NB_CANDIDATE_EDIT_ERROR,
225 "%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
226 __func__, nb_operation_name(nb_op), xpath);
227 iter_args->error = true;
228 return ITER_STOP;
229 }
230
231 return ITER_RECURSE;
232}
233
234static int frr_confd_cdb_read_cb(struct thread *thread)
235{
236 int fd = THREAD_FD(thread);
237 int *subp = NULL;
238 enum cdb_sub_notification cdb_ev;
239 int flags;
240 int reslen = 0;
241 struct nb_config *candidate;
242 struct cdb_iter_args iter_args;
243 int ret;
244
245 thread = NULL;
246 thread_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &thread);
247
248 if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen)
249 != CONFD_OK) {
250 flog_err_confd("cdb_read_subscription_socket2");
251 return -1;
252 }
253
254 /*
255 * Ignore CDB_SUB_ABORT and CDB_SUB_COMMIT. We'll leverage the
256 * northbound layer itself to abort or apply the configuration changes
257 * when a transaction is created.
258 */
259 if (cdb_ev != CDB_SUB_PREPARE) {
260 free(subp);
261 if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
262 != CONFD_OK) {
263 flog_err_confd("cdb_sync_subscription_socket");
264 return -1;
265 }
266 return 0;
267 }
268
269 candidate = nb_config_dup(running_config);
270
271 /* Iterate over all configuration changes. */
272 iter_args.candidate = candidate;
273 iter_args.error = false;
274 for (int i = 0; i < reslen; i++) {
275 if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter,
276 ITER_WANT_PREV, &iter_args)
277 != CONFD_OK) {
278 flog_err_confd("cdb_diff_iterate");
279 }
280 }
281 free(subp);
282
283 if (iter_args.error) {
284 nb_config_free(candidate);
285
286 if (cdb_sub_abort_trans(
287 cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0,
288 0, "Couldn't apply configuration changes")
289 != CONFD_OK) {
290 flog_err_confd("cdb_sub_abort_trans");
291 return -1;
292 }
293 return 0;
294 }
295
296 ret = nb_candidate_commit(candidate, NB_CLIENT_CONFD, true, NULL, NULL);
297 nb_config_free(candidate);
298 if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
299 enum confd_errcode errcode;
300 const char *errmsg;
301
302 switch (ret) {
303 case NB_ERR_LOCKED:
304 errcode = CONFD_ERRCODE_IN_USE;
305 errmsg = "Configuration is locked by another process";
306 break;
307 case NB_ERR_RESOURCE:
308 errcode = CONFD_ERRCODE_RESOURCE_DENIED;
309 errmsg = "Failed do allocate resources";
310 break;
311 default:
312 errcode = CONFD_ERRCODE_INTERNAL;
313 errmsg = "Internal error";
314 break;
315 }
316
317 if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s",
318 errmsg)
319 != CONFD_OK) {
320 flog_err_confd("cdb_sub_abort_trans");
321 return -1;
322 }
323 } else {
324 if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
325 != CONFD_OK) {
326 flog_err_confd("cdb_sync_subscription_socket");
327 return -1;
328 }
329 }
330
331 return 0;
332}
333
334/* Trigger CDB subscriptions to read the startup configuration. */
335static void *thread_cdb_trigger_subscriptions(void *data)
336{
337 int sock;
338 int *sub_points = NULL, len = 0;
339 struct listnode *node;
340 int *spoint;
341 int i = 0;
342
343 /* Create CDB data socket. */
344 sock = socket(PF_INET, SOCK_STREAM, 0);
345 if (sock < 0) {
346 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
347 __func__, safe_strerror(errno));
348 return NULL;
349 }
350
351 if (cdb_connect(sock, CDB_DATA_SOCKET, &confd_addr,
352 sizeof(struct sockaddr_in))
353 != CONFD_OK) {
354 flog_err_confd("cdb_connect");
355 return NULL;
356 }
357
358 /*
359 * Fill array containing the subscription point of all loaded YANG
360 * modules.
361 */
362 len = listcount(confd_spoints);
363 sub_points = XCALLOC(MTYPE_CONFD, len * sizeof(int));
364 for (ALL_LIST_ELEMENTS_RO(confd_spoints, node, spoint))
365 sub_points[i++] = *spoint;
366
367 if (cdb_trigger_subscriptions(sock, sub_points, len) != CONFD_OK) {
368 flog_err_confd("cdb_trigger_subscriptions");
369 return NULL;
370 }
371
372 /* Cleanup and exit thread. */
373 XFREE(MTYPE_CONFD, sub_points);
374 cdb_close(sock);
375
376 return NULL;
377}
378
379static int frr_confd_init_cdb(void)
380{
381 struct yang_module *module;
382 pthread_t cdb_trigger_thread;
383
384 /* Create CDB subscription socket. */
385 cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0);
386 if (cdb_sub_sock < 0) {
387 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
388 __func__, safe_strerror(errno));
389 return -1;
390 }
391
392 if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr,
393 sizeof(struct sockaddr_in))
394 != CONFD_OK) {
395 flog_err_confd("cdb_connect");
396 goto error;
397 }
398
399 /* Subscribe to all loaded YANG data modules. */
400 confd_spoints = list_new();
401 RB_FOREACH (module, yang_modules, &yang_modules) {
402 struct lys_node *snode;
403
404 module->confd_hash = confd_str2hash(module->info->ns);
405 if (module->confd_hash == 0) {
406 flog_err(
407 EC_LIB_LIBCONFD,
408 "%s: failed to find hash value for namespace %s",
409 __func__, module->info->ns);
410 goto error;
411 }
412
413 /*
414 * The CDB API doesn't provide a mechanism to subscribe to an
415 * entire YANG module. So we have to find the top level
416 * nodes ourselves and subscribe to their paths.
417 */
418 LY_TREE_FOR (module->info->data, snode) {
419 struct nb_node *nb_node;
420 int *spoint;
421 int ret;
422
423 switch (snode->nodetype) {
424 case LYS_CONTAINER:
425 case LYS_LEAF:
426 case LYS_LEAFLIST:
427 case LYS_LIST:
428 break;
429 default:
430 continue;
431 }
432
433 nb_node = snode->priv;
434 if (debug_northbound)
435 zlog_debug("%s: subscribing to '%s'", __func__,
436 nb_node->xpath);
437
438 spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint));
439 ret = cdb_subscribe2(
440 cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE,
441 CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint,
442 module->confd_hash, nb_node->xpath);
443 if (ret != CONFD_OK) {
444 flog_err_confd("cdb_subscribe2");
445 XFREE(MTYPE_CONFD, spoint);
446 }
447 listnode_add(confd_spoints, spoint);
448 }
449 }
450
451 if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) {
452 flog_err_confd("cdb_subscribe_done");
453 goto error;
454 }
455
456 /* Create short lived pthread to trigger the CDB subscriptions. */
457 if (pthread_create(&cdb_trigger_thread, NULL,
458 thread_cdb_trigger_subscriptions, NULL)) {
459 flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
460 __func__, safe_strerror(errno));
461 goto error;
462 }
463 pthread_detach(cdb_trigger_thread);
464
465 thread_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock,
466 &t_cdb_sub);
467
468 return 0;
469
470error:
471 frr_confd_finish_cdb();
472
473 return -1;
474}
475
476static void frr_confd_finish_cdb(void)
477{
478 if (cdb_sub_sock > 0) {
479 THREAD_OFF(t_cdb_sub);
480 cdb_close(cdb_sub_sock);
481 }
482}
483
484/* ------------ DP code ------------ */
485
486static int frr_confd_transaction_init(struct confd_trans_ctx *tctx)
487{
488 confd_trans_set_fd(tctx, dp_worker_sock);
489
490 return CONFD_OK;
491}
492
493static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
494 confd_hkeypath_t *kp)
495{
496 struct nb_node *nb_node, *parent_list;
497 char xpath[BUFSIZ];
498 struct yang_list_keys keys;
499 struct yang_data *data;
500 confd_value_t v;
501 const void *list_entry = NULL;
502
503 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
504
505 nb_node = nb_node_find(xpath);
506 if (!nb_node) {
507 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
508 "%s: unknown data path: %s", __func__, xpath);
509 confd_data_reply_not_found(tctx);
510 return CONFD_OK;
511 }
512
513 parent_list = nb_node->parent_list;
514 if (parent_list) {
515 frr_confd_hkeypath_get_keys(kp, &keys);
516 list_entry = parent_list->cbs.lookup_entry(&keys);
517 if (!list_entry) {
518 flog_warn(EC_LIB_NB_CB_STATE,
519 "%s: list entry not found: %s", __func__,
520 xpath);
521 confd_data_reply_not_found(tctx);
522 return CONFD_OK;
523 }
524 }
525
526 data = nb_node->cbs.get_elem(xpath, list_entry);
527 if (data) {
528 if (data->value) {
529 CONFD_SET_STR(&v, data->value);
530 confd_data_reply_value(tctx, &v);
531 } else
532 confd_data_reply_found(tctx);
533 yang_data_free(data);
534 } else
535 confd_data_reply_not_found(tctx);
536
537 return CONFD_OK;
538}
539
540static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
541 confd_hkeypath_t *kp, long next)
542{
543 struct nb_node *nb_node;
544 char xpath[BUFSIZ];
545 struct yang_list_keys keys;
546 const void *nb_next;
547 confd_value_t v[LIST_MAXKEYS];
548
549 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
550
551 nb_node = nb_node_find(xpath);
552 if (!nb_node) {
553 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
554 "%s: unknown data path: %s", __func__, xpath);
555 confd_data_reply_next_key(tctx, NULL, -1, -1);
556 return CONFD_OK;
557 }
558
559 nb_next = nb_node->cbs.get_next(xpath,
560 (next == -1) ? NULL : (void *)next);
561 if (!nb_next) {
562 /* End of the list. */
563 confd_data_reply_next_key(tctx, NULL, -1, -1);
564 return CONFD_OK;
565 }
566 if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
567 flog_warn(EC_LIB_NB_CB_STATE, "%s: failed to get list keys",
568 __func__);
569 confd_data_reply_next_key(tctx, NULL, -1, -1);
570 return CONFD_OK;
571 }
572
573 /* Feed keys to ConfD. */
574 for (size_t i = 0; i < keys.num; i++)
80243aef 575 CONFD_SET_STR(&v[i], keys.key[i]);
5bce33b3
RW
576 confd_data_reply_next_key(tctx, v, keys.num, (long)nb_next);
577
578 return CONFD_OK;
579}
580
581/*
582 * Optional callback - implemented for performance reasons.
583 */
584static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
585 confd_hkeypath_t *kp)
586{
587 struct nb_node *nb_node;
588 char xpath[BUFSIZ];
589 char xpath_children[XPATH_MAXLEN];
590 char xpath_child[XPATH_MAXLEN];
591 struct yang_list_keys keys;
592 struct list *elements;
593 struct yang_data *data;
594 const void *list_entry;
595 struct ly_set *set;
596 confd_value_t *values;
597
598 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
599
600 nb_node = nb_node_find(xpath);
601 if (!nb_node) {
602 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
603 "%s: unknown data path: %s", __func__, xpath);
604 confd_data_reply_not_found(tctx);
605 return CONFD_OK;
606 }
607
608 frr_confd_hkeypath_get_keys(kp, &keys);
609 list_entry = nb_node->cbs.lookup_entry(&keys);
610 if (!list_entry) {
611 flog_warn(EC_LIB_NB_CB_STATE, "%s: list entry not found: %s",
612 __func__, xpath);
613 confd_data_reply_not_found(tctx);
614 return CONFD_OK;
615 }
616
617 /* Find list child nodes. */
618 snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
619 set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
620 if (!set) {
621 flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
622 __func__);
623 return CONFD_ERR;
624 }
625
626 elements = yang_data_list_new();
627 values = XMALLOC(MTYPE_CONFD, set->number * sizeof(*values));
628
629 /* Loop through list child nodes. */
630 for (size_t i = 0; i < set->number; i++) {
631 struct lys_node *child;
632 struct nb_node *nb_node_child;
633
634 child = set->set.s[i];
635 nb_node_child = child->priv;
636
637 snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
638 child->name);
639
640 data = nb_node_child->cbs.get_elem(xpath_child, list_entry);
641 if (data) {
642 if (data->value)
643 CONFD_SET_STR(&values[i], data->value);
644 else
645 CONFD_SET_NOEXISTS(&values[i]);
646 listnode_add(elements, data);
647 } else
648 CONFD_SET_NOEXISTS(&values[i]);
649 }
650
651 confd_data_reply_value_array(tctx, values, set->number);
652
653 /* Release memory. */
654 ly_set_free(set);
655 XFREE(MTYPE_CONFD, values);
656 list_delete(&elements);
657
658 return CONFD_OK;
659}
660
661/*
662 * Optional callback - implemented for performance reasons.
663 */
664static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
665 confd_hkeypath_t *kp, long next)
666{
667 char xpath[BUFSIZ];
668 char xpath_children[XPATH_MAXLEN];
669 struct nb_node *nb_node;
670 struct ly_set *set;
671 struct list *elements;
672 const void *nb_next;
673#define CONFD_OBJECTS_PER_TIME 100
674 struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
675 int nobjects = 0;
676
677 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
678
679 nb_node = nb_node_find(xpath);
680 if (!nb_node) {
681 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
682 "%s: unknown data path: %s", __func__, xpath);
683 confd_data_reply_next_object_array(tctx, NULL, 0, 0);
684 return CONFD_OK;
685 }
686
687 /* Find list child nodes. */
688 snprintf(xpath_children, sizeof(xpath_children), "%s/*", xpath);
689 set = lys_find_path(nb_node->snode->module, NULL, xpath_children);
690 if (!set) {
691 flog_warn(EC_LIB_LIBYANG, "%s: lys_find_path() failed",
692 __func__);
693 return CONFD_ERR;
694 }
695
696 elements = yang_data_list_new();
697 nb_next = (next == -1) ? NULL : (void *)next;
698
699 memset(objects, 0, sizeof(objects));
700 for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) {
701 struct confd_next_object *object;
702 struct yang_list_keys keys;
703 struct yang_data *data;
704 const void *list_entry;
705
706 object = &objects[j];
707
708 nb_next = nb_node->cbs.get_next(xpath, nb_next);
709 if (!nb_next)
710 /* End of the list. */
711 break;
712
713 if (nb_node->cbs.get_keys(nb_next, &keys) != NB_OK) {
714 flog_warn(EC_LIB_NB_CB_STATE,
715 "%s: failed to get list keys", __func__);
716 continue;
717 }
718 object->next = (long)nb_next;
719
720 list_entry = nb_node->cbs.lookup_entry(&keys);
721 if (!list_entry) {
722 flog_warn(EC_LIB_NB_CB_STATE,
723 "%s: failed to lookup list entry", __func__);
724 continue;
725 }
726
727 object->v = XMALLOC(MTYPE_CONFD,
728 set->number * sizeof(confd_value_t));
729
730 /* Loop through list child nodes. */
731 for (unsigned int i = 0; i < set->number; i++) {
732 struct lys_node *child;
733 struct nb_node *nb_node_child;
734 char xpath_child[XPATH_MAXLEN];
735 confd_value_t *v = &object->v[i];
736
737 child = set->set.s[i];
738 nb_node_child = child->priv;
739
740 snprintf(xpath_child, sizeof(xpath_child), "%s/%s",
741 xpath, child->name);
742
743 data = nb_node_child->cbs.get_elem(xpath_child,
744 list_entry);
745 if (data) {
746 if (data->value)
747 CONFD_SET_STR(v, data->value);
748 else
749 CONFD_SET_NOEXISTS(v);
750 listnode_add(elements, data);
751 } else
752 CONFD_SET_NOEXISTS(v);
753 }
754 object->n = set->number;
755 nobjects++;
756 }
757 ly_set_free(set);
758
759 if (nobjects == 0) {
760 confd_data_reply_next_object_array(tctx, NULL, 0, 0);
761 list_delete(&elements);
762 return CONFD_OK;
763 }
764
765 /* Detect end of the list. */
766 if (!nb_next) {
767 nobjects++;
768 objects[nobjects].v = NULL;
769 }
770
771 /* Reply to ConfD. */
772 confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0);
773 if (!nb_next)
774 nobjects--;
775
776 /* Release memory. */
777 list_delete(&elements);
778 for (int j = 0; j < nobjects; j++) {
779 struct confd_next_object *object;
780
781 object = &objects[j];
782 XFREE(MTYPE_CONFD, object->v);
783 }
784
785 return CONFD_OK;
786}
787
788static int frr_confd_notification_send(const char *xpath,
789 struct list *arguments)
790{
791 struct nb_node *nb_node;
792 struct yang_module *module;
793 struct confd_datetime now;
794 confd_tag_value_t *values;
795 int nvalues;
796 int i = 0;
797 struct yang_data *data;
798 struct listnode *node;
799 int ret;
800
801 nb_node = nb_node_find(xpath);
802 if (!nb_node) {
803 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
804 "%s: unknown data path: %s", __func__, xpath);
805 return -1;
806 }
807 module = yang_module_find(nb_node->snode->module->name);
808 assert(module);
809
810 nvalues = 2;
811 if (arguments)
812 nvalues += listcount(arguments);
813
814 values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values));
815
816 CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash,
817 module->confd_hash);
818 for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) {
80243aef
RW
819 struct nb_node *nb_node_arg;
820
821 nb_node_arg = nb_node_find(data->xpath);
822 if (!nb_node_arg) {
823 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
824 "%s: unknown data path: %s", __func__,
825 data->xpath);
826 XFREE(MTYPE_CONFD, values);
827 return NB_ERR;
828 }
5bce33b3 829
80243aef 830 CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash,
5bce33b3
RW
831 data->value);
832 }
833 CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash,
834 module->confd_hash);
835
836 getdatetime(&now);
837 ret = confd_notification_send(live_ctx, &now, values, nvalues);
838
839 /* Release memory. */
840 XFREE(MTYPE_CONFD, values);
841
842 /* Map ConfD return code to northbound return code. */
843 switch (ret) {
844 case CONFD_OK:
845 return NB_OK;
846 default:
847 return NB_ERR;
848 }
849}
850
851static int frr_confd_action_init(struct confd_user_info *uinfo)
852{
853 confd_action_set_fd(uinfo, dp_worker_sock);
854
855 return CONFD_OK;
856}
857
858static int frr_confd_action_execute(struct confd_user_info *uinfo,
859 struct xml_tag *name, confd_hkeypath_t *kp,
860 confd_tag_value_t *params, int nparams)
861{
862 char xpath[BUFSIZ];
863 struct nb_node *nb_node;
864 struct list *input;
865 struct list *output;
866 struct yang_data *data;
867 confd_tag_value_t *reply;
868 int ret = CONFD_OK;
869
870 /* Getting the XPath is tricky. */
871 if (kp) {
872 /* This is a YANG RPC. */
873 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
874 strlcat(xpath, "/", sizeof(xpath));
875 strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath));
876 } else {
877 /* This is a YANG action. */
878 snprintf(xpath, sizeof(xpath), "/%s:%s",
879 confd_ns2prefix(name->ns), confd_hash2str(name->tag));
880 }
881
882 nb_node = nb_node_find(xpath);
883 if (!nb_node) {
884 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
885 "%s: unknown data path: %s", __func__, xpath);
886 return CONFD_ERR;
887 }
888
889 input = yang_data_list_new();
890 output = yang_data_list_new();
891
892 /* Process input nodes. */
893 for (int i = 0; i < nparams; i++) {
894 char xpath_input[BUFSIZ];
895 char value_str[YANG_VALUE_MAXLEN];
896
897 snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath,
898 confd_hash2str(params[i].tag.tag));
899
900 if (frr_confd_val2str(xpath_input, &params[i].v, value_str,
901 sizeof(value_str))
902 != 0) {
903 flog_err(
904 EC_LIB_CONFD_DATA_CONVERT,
905 "%s: failed to convert ConfD value to a string",
906 __func__);
907 ret = CONFD_ERR;
908 goto exit;
909 }
910
911 data = yang_data_new(xpath_input, value_str);
912 listnode_add(input, data);
913 }
914
915 /* Execute callback registered for this XPath. */
916 if (nb_node->cbs.rpc(xpath, input, output) != NB_OK) {
917 flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
918 __func__, xpath);
919 ret = CONFD_ERR;
920 goto exit;
921 }
922
923 /* Process output nodes. */
924 if (listcount(output) > 0) {
925 struct listnode *node;
926 int i = 0;
927
928 reply = XMALLOC(MTYPE_CONFD,
929 listcount(output) * sizeof(*reply));
930
931 for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
80243aef 932 struct nb_node *nb_node_output;
5bce33b3
RW
933 int hash;
934
80243aef
RW
935 nb_node_output = nb_node_find(data->xpath);
936 if (!nb_node_output) {
937 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
938 "%s: unknown data path: %s", __func__,
939 data->xpath);
940 goto exit;
941 }
942
943 hash = confd_str2hash(nb_node_output->snode->name);
5bce33b3
RW
944 CONFD_SET_TAG_STR(&reply[i++], hash, data->value);
945 }
946 confd_action_reply_values(uinfo, reply, listcount(output));
947 XFREE(MTYPE_CONFD, reply);
948 }
949
950exit:
951 /* Release memory. */
952 list_delete(&input);
953 list_delete(&output);
954
955 return ret;
956}
957
958
959static int frr_confd_dp_read(struct thread *thread)
960{
961 struct confd_daemon_ctx *dctx = THREAD_ARG(thread);
962 int fd = THREAD_FD(thread);
963 int ret;
964
965 thread = NULL;
966 thread_add_read(master, frr_confd_dp_read, dctx, fd, &thread);
967
968 ret = confd_fd_ready(dctx, fd);
969 if (ret == CONFD_EOF) {
970 flog_err_confd("confd_fd_ready");
971 return -1;
972 } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
973 flog_err_confd("confd_fd_ready");
974 return -1;
975 }
976
977 return 0;
978}
979
e0ccfad2 980static int frr_confd_subscribe_state(const struct lys_node *snode, void *arg)
5bce33b3
RW
981{
982 struct nb_node *nb_node = snode->priv;
e0ccfad2 983 struct confd_data_cbs *data_cbs = arg;
5bce33b3 984
db452508 985 if (!CHECK_FLAG(snode->flags, LYS_CONFIG_R))
e0ccfad2 986 return YANG_ITER_CONTINUE;
5bce33b3 987 /* We only need to subscribe to the root of the state subtrees. */
db452508 988 if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
e0ccfad2 989 return YANG_ITER_CONTINUE;
5bce33b3
RW
990
991 if (debug_northbound)
992 zlog_debug("%s: providing data to '%s' (callpoint %s)",
993 __func__, nb_node->xpath, snode->name);
994
995 strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint));
996 if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK)
997 flog_err_confd("confd_register_data_cb");
e0ccfad2
RW
998
999 return YANG_ITER_CONTINUE;
5bce33b3
RW
1000}
1001
1002static int frr_confd_init_dp(const char *program_name)
1003{
1004 struct confd_trans_cbs trans_cbs;
1005 struct confd_data_cbs data_cbs;
1006 struct confd_notification_stream_cbs ncbs;
1007 struct confd_action_cbs acbs;
1008
1009 /* Initialize daemon context. */
1010 dctx = confd_init_daemon(program_name);
1011 if (!dctx) {
1012 flog_err_confd("confd_init_daemon");
1013 goto error;
1014 }
1015
1016 /*
1017 * Inform we want to receive YANG values as raw strings, and that we
1018 * want to provide only strings in the reply functions, regardless of
1019 * the YANG type.
1020 */
1021 confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY);
1022
1023 /* Establish a control socket. */
1024 dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0);
1025 if (dp_ctl_sock < 0) {
1026 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
1027 __func__, safe_strerror(errno));
1028 goto error;
1029 }
1030
1031 if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr,
1032 sizeof(struct sockaddr_in))
1033 != CONFD_OK) {
1034 flog_err_confd("confd_connect");
1035 goto error;
1036 }
1037
1038 /*
1039 * Establish a worker socket (only one since this plugin runs on a
1040 * single thread).
1041 */
1042 dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0);
1043 if (dp_worker_sock < 0) {
1044 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
1045 __func__, safe_strerror(errno));
1046 goto error;
1047 }
1048 if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr,
1049 sizeof(struct sockaddr_in))
1050 != CONFD_OK) {
1051 flog_err_confd("confd_connect");
1052 goto error;
1053 }
1054
1055 /* Register transaction callback functions. */
1056 memset(&trans_cbs, 0, sizeof(trans_cbs));
1057 trans_cbs.init = frr_confd_transaction_init;
1058 confd_register_trans_cb(dctx, &trans_cbs);
1059
1060 /* Register our read/write callbacks. */
1061 memset(&data_cbs, 0, sizeof(data_cbs));
1062 data_cbs.get_elem = frr_confd_data_get_elem;
1063 data_cbs.exists_optional = frr_confd_data_get_elem;
1064 data_cbs.get_next = frr_confd_data_get_next;
1065 data_cbs.get_object = frr_confd_data_get_object;
1066 data_cbs.get_next_object = frr_confd_data_get_next_object;
1067
1068 /*
1069 * Iterate over all loaded YANG modules and subscribe to the paths
1070 * referent to state data.
1071 */
e0ccfad2 1072 yang_snodes_iterate_all(frr_confd_subscribe_state, 0, &data_cbs);
5bce33b3
RW
1073
1074 /* Register notification stream. */
1075 memset(&ncbs, 0, sizeof(ncbs));
1076 ncbs.fd = dp_worker_sock;
1077 /*
1078 * RFC 5277 - Section 3.2.3:
1079 * A NETCONF server implementation supporting the notification
1080 * capability MUST support the "NETCONF" notification event
1081 * stream. This stream contains all NETCONF XML event notifications
1082 * supported by the NETCONF server.
1083 */
1084 strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname));
1085 if (confd_register_notification_stream(dctx, &ncbs, &live_ctx)
1086 != CONFD_OK) {
1087 flog_err_confd("confd_register_notification_stream");
1088 goto error;
1089 }
1090
1091 /* Register the action handler callback. */
1092 memset(&acbs, 0, sizeof(acbs));
1093 strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint));
1094 acbs.init = frr_confd_action_init;
1095 acbs.action = frr_confd_action_execute;
1096 if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) {
1097 flog_err_confd("confd_register_action_cbs");
1098 goto error;
1099 }
1100
1101 /* Notify we registered all callbacks we wanted. */
1102 if (confd_register_done(dctx) != CONFD_OK) {
1103 flog_err_confd("confd_register_done");
1104 goto error;
1105 }
1106
1107 thread_add_read(master, frr_confd_dp_read, dctx, dp_ctl_sock,
1108 &t_dp_ctl);
1109 thread_add_read(master, frr_confd_dp_read, dctx, dp_worker_sock,
1110 &t_dp_worker);
1111
1112 return 0;
1113
1114error:
1115 frr_confd_finish_dp();
1116
1117 return -1;
1118}
1119
1120static void frr_confd_finish_dp(void)
1121{
1122 if (dp_worker_sock > 0) {
1123 THREAD_OFF(t_dp_worker);
1124 close(dp_worker_sock);
1125 }
1126 if (dp_ctl_sock > 0) {
1127 THREAD_OFF(t_dp_ctl);
1128 close(dp_ctl_sock);
1129 }
1130 if (dctx != NULL)
1131 confd_release_daemon(dctx);
1132}
1133
1134/* ------------ Main ------------ */
1135
e0ccfad2
RW
1136static int frr_confd_calculate_snode_hash(const struct lys_node *snode,
1137 void *arg)
5bce33b3
RW
1138{
1139 struct nb_node *nb_node = snode->priv;
1140
1141 nb_node->confd_hash = confd_str2hash(snode->name);
e0ccfad2
RW
1142
1143 return YANG_ITER_CONTINUE;
5bce33b3
RW
1144}
1145
1146static int frr_confd_init(const char *program_name)
1147{
1148 struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr;
1149 int debuglevel = CONFD_SILENT;
1150 int ret = -1;
1151
1152 /* Initialize ConfD library. */
1153 confd_init(program_name, stderr, debuglevel);
1154
1155 confd_addr4->sin_family = AF_INET;
1156 confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1");
1157 confd_addr4->sin_port = htons(CONFD_PORT);
1158 if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in))
1159 != CONFD_OK) {
1160 flog_err_confd("confd_load_schemas");
1161 return -1;
1162 }
1163
1164 ret = frr_confd_init_cdb();
1165 if (ret != 0)
1166 goto error;
1167
1168 ret = frr_confd_init_dp(program_name);
1169 if (ret != 0) {
1170 frr_confd_finish_cdb();
1171 goto error;
1172 }
1173
e0ccfad2 1174 yang_snodes_iterate_all(frr_confd_calculate_snode_hash, 0, NULL);
5bce33b3
RW
1175
1176 hook_register(nb_notification_send, frr_confd_notification_send);
1177
1178 confd_connected = true;
1179 return 0;
1180
1181error:
1182 confd_free_schemas();
1183
1184 return ret;
1185}
1186
1187static int frr_confd_finish(void)
1188{
1189 if (confd_connected == false)
1190 return 0;
1191
1192 frr_confd_finish_cdb();
1193 frr_confd_finish_dp();
1194
1195 confd_free_schemas();
1196
1197 confd_connected = false;
1198
1199 return 0;
1200}
1201
1202static int frr_confd_module_late_init(struct thread_master *tm)
1203{
1204 master = tm;
1205
1206 if (frr_confd_init(frr_get_progname()) < 0) {
1207 flog_err(EC_LIB_CONFD_INIT,
1208 "failed to initialize the ConfD module");
1209 return -1;
1210 }
1211
1212 hook_register(frr_fini, frr_confd_finish);
1213
1214 return 0;
1215}
1216
1217static int frr_confd_module_init(void)
1218{
1219 hook_register(frr_late_init, frr_confd_module_late_init);
1220
1221 return 0;
1222}
1223
1224FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION,
1225 .description = "FRR ConfD integration module",
1226 .init = frr_confd_module_init, )