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