]> git.proxmox.com Git - mirror_frr.git/blob - lib/northbound_confd.c
Merge pull request #3356 from opensourcerouting/router-id-loopbacks
[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
45 static void frr_confd_finish_cdb(void);
46 static void frr_confd_finish_dp(void);
47 static 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. */
58 static 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. */
75 static 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. */
95 static 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++) {
104 strlcpy(keys->key[keys->num].value,
105 (char *)kp->v[i][j].val.buf.ptr,
106 sizeof(keys->key[keys->num].value));
107 keys->num++;
108 }
109 }
110 }
111
112 /* Fill the current date and time into a confd_datetime structure. */
113 static 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
135 struct cdb_iter_args {
136 struct nb_config *candidate;
137 bool error;
138 };
139
140 static enum cdb_iter_ret
141 frr_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
234 static 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. */
335 static 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
379 static 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
470 error:
471 frr_confd_finish_cdb();
472
473 return -1;
474 }
475
476 static 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
486 static 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
493 static 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
540 static 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++)
575 CONFD_SET_STR(&v[i], keys.key[i].value);
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 */
584 static 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 */
664 static 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
788 static 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)) {
819 struct nb_node *option_arg;
820
821 option_arg = data->snode->priv;
822 CONFD_SET_TAG_STR(&values[i++], option_arg->confd_hash,
823 data->value);
824 }
825 CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash,
826 module->confd_hash);
827
828 getdatetime(&now);
829 ret = confd_notification_send(live_ctx, &now, values, nvalues);
830
831 /* Release memory. */
832 XFREE(MTYPE_CONFD, values);
833
834 /* Map ConfD return code to northbound return code. */
835 switch (ret) {
836 case CONFD_OK:
837 return NB_OK;
838 default:
839 return NB_ERR;
840 }
841 }
842
843 static int frr_confd_action_init(struct confd_user_info *uinfo)
844 {
845 confd_action_set_fd(uinfo, dp_worker_sock);
846
847 return CONFD_OK;
848 }
849
850 static int frr_confd_action_execute(struct confd_user_info *uinfo,
851 struct xml_tag *name, confd_hkeypath_t *kp,
852 confd_tag_value_t *params, int nparams)
853 {
854 char xpath[BUFSIZ];
855 struct nb_node *nb_node;
856 struct list *input;
857 struct list *output;
858 struct yang_data *data;
859 confd_tag_value_t *reply;
860 int ret = CONFD_OK;
861
862 /* Getting the XPath is tricky. */
863 if (kp) {
864 /* This is a YANG RPC. */
865 frr_confd_get_xpath(kp, xpath, sizeof(xpath));
866 strlcat(xpath, "/", sizeof(xpath));
867 strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath));
868 } else {
869 /* This is a YANG action. */
870 snprintf(xpath, sizeof(xpath), "/%s:%s",
871 confd_ns2prefix(name->ns), confd_hash2str(name->tag));
872 }
873
874 nb_node = nb_node_find(xpath);
875 if (!nb_node) {
876 flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
877 "%s: unknown data path: %s", __func__, xpath);
878 return CONFD_ERR;
879 }
880
881 input = yang_data_list_new();
882 output = yang_data_list_new();
883
884 /* Process input nodes. */
885 for (int i = 0; i < nparams; i++) {
886 char xpath_input[BUFSIZ];
887 char value_str[YANG_VALUE_MAXLEN];
888
889 snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath,
890 confd_hash2str(params[i].tag.tag));
891
892 if (frr_confd_val2str(xpath_input, &params[i].v, value_str,
893 sizeof(value_str))
894 != 0) {
895 flog_err(
896 EC_LIB_CONFD_DATA_CONVERT,
897 "%s: failed to convert ConfD value to a string",
898 __func__);
899 ret = CONFD_ERR;
900 goto exit;
901 }
902
903 data = yang_data_new(xpath_input, value_str);
904 listnode_add(input, data);
905 }
906
907 /* Execute callback registered for this XPath. */
908 if (nb_node->cbs.rpc(xpath, input, output) != NB_OK) {
909 flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
910 __func__, xpath);
911 ret = CONFD_ERR;
912 goto exit;
913 }
914
915 /* Process output nodes. */
916 if (listcount(output) > 0) {
917 struct listnode *node;
918 int i = 0;
919
920 reply = XMALLOC(MTYPE_CONFD,
921 listcount(output) * sizeof(*reply));
922
923 for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
924 int hash;
925
926 hash = confd_str2hash(data->snode->name);
927 CONFD_SET_TAG_STR(&reply[i++], hash, data->value);
928 }
929 confd_action_reply_values(uinfo, reply, listcount(output));
930 XFREE(MTYPE_CONFD, reply);
931 }
932
933 exit:
934 /* Release memory. */
935 list_delete(&input);
936 list_delete(&output);
937
938 return ret;
939 }
940
941
942 static int frr_confd_dp_read(struct thread *thread)
943 {
944 struct confd_daemon_ctx *dctx = THREAD_ARG(thread);
945 int fd = THREAD_FD(thread);
946 int ret;
947
948 thread = NULL;
949 thread_add_read(master, frr_confd_dp_read, dctx, fd, &thread);
950
951 ret = confd_fd_ready(dctx, fd);
952 if (ret == CONFD_EOF) {
953 flog_err_confd("confd_fd_ready");
954 return -1;
955 } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
956 flog_err_confd("confd_fd_ready");
957 return -1;
958 }
959
960 return 0;
961 }
962
963 static void frr_confd_subscribe_state(const struct lys_node *snode, void *arg1,
964 void *arg2)
965 {
966 struct nb_node *nb_node = snode->priv;
967 struct confd_data_cbs *data_cbs = arg1;
968
969 if (!(snode->flags & LYS_CONFIG_R))
970 return;
971 /* We only need to subscribe to the root of the state subtrees. */
972 if (snode->parent && (snode->parent->flags & LYS_CONFIG_R))
973 return;
974
975 if (debug_northbound)
976 zlog_debug("%s: providing data to '%s' (callpoint %s)",
977 __func__, nb_node->xpath, snode->name);
978
979 strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint));
980 if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK)
981 flog_err_confd("confd_register_data_cb");
982 }
983
984 static int frr_confd_init_dp(const char *program_name)
985 {
986 struct confd_trans_cbs trans_cbs;
987 struct confd_data_cbs data_cbs;
988 struct confd_notification_stream_cbs ncbs;
989 struct confd_action_cbs acbs;
990
991 /* Initialize daemon context. */
992 dctx = confd_init_daemon(program_name);
993 if (!dctx) {
994 flog_err_confd("confd_init_daemon");
995 goto error;
996 }
997
998 /*
999 * Inform we want to receive YANG values as raw strings, and that we
1000 * want to provide only strings in the reply functions, regardless of
1001 * the YANG type.
1002 */
1003 confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY);
1004
1005 /* Establish a control socket. */
1006 dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0);
1007 if (dp_ctl_sock < 0) {
1008 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
1009 __func__, safe_strerror(errno));
1010 goto error;
1011 }
1012
1013 if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr,
1014 sizeof(struct sockaddr_in))
1015 != CONFD_OK) {
1016 flog_err_confd("confd_connect");
1017 goto error;
1018 }
1019
1020 /*
1021 * Establish a worker socket (only one since this plugin runs on a
1022 * single thread).
1023 */
1024 dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0);
1025 if (dp_worker_sock < 0) {
1026 flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
1027 __func__, safe_strerror(errno));
1028 goto error;
1029 }
1030 if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr,
1031 sizeof(struct sockaddr_in))
1032 != CONFD_OK) {
1033 flog_err_confd("confd_connect");
1034 goto error;
1035 }
1036
1037 /* Register transaction callback functions. */
1038 memset(&trans_cbs, 0, sizeof(trans_cbs));
1039 trans_cbs.init = frr_confd_transaction_init;
1040 confd_register_trans_cb(dctx, &trans_cbs);
1041
1042 /* Register our read/write callbacks. */
1043 memset(&data_cbs, 0, sizeof(data_cbs));
1044 data_cbs.get_elem = frr_confd_data_get_elem;
1045 data_cbs.exists_optional = frr_confd_data_get_elem;
1046 data_cbs.get_next = frr_confd_data_get_next;
1047 data_cbs.get_object = frr_confd_data_get_object;
1048 data_cbs.get_next_object = frr_confd_data_get_next_object;
1049
1050 /*
1051 * Iterate over all loaded YANG modules and subscribe to the paths
1052 * referent to state data.
1053 */
1054 yang_all_snodes_iterate(frr_confd_subscribe_state, 0, &data_cbs, NULL);
1055
1056 /* Register notification stream. */
1057 memset(&ncbs, 0, sizeof(ncbs));
1058 ncbs.fd = dp_worker_sock;
1059 /*
1060 * RFC 5277 - Section 3.2.3:
1061 * A NETCONF server implementation supporting the notification
1062 * capability MUST support the "NETCONF" notification event
1063 * stream. This stream contains all NETCONF XML event notifications
1064 * supported by the NETCONF server.
1065 */
1066 strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname));
1067 if (confd_register_notification_stream(dctx, &ncbs, &live_ctx)
1068 != CONFD_OK) {
1069 flog_err_confd("confd_register_notification_stream");
1070 goto error;
1071 }
1072
1073 /* Register the action handler callback. */
1074 memset(&acbs, 0, sizeof(acbs));
1075 strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint));
1076 acbs.init = frr_confd_action_init;
1077 acbs.action = frr_confd_action_execute;
1078 if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) {
1079 flog_err_confd("confd_register_action_cbs");
1080 goto error;
1081 }
1082
1083 /* Notify we registered all callbacks we wanted. */
1084 if (confd_register_done(dctx) != CONFD_OK) {
1085 flog_err_confd("confd_register_done");
1086 goto error;
1087 }
1088
1089 thread_add_read(master, frr_confd_dp_read, dctx, dp_ctl_sock,
1090 &t_dp_ctl);
1091 thread_add_read(master, frr_confd_dp_read, dctx, dp_worker_sock,
1092 &t_dp_worker);
1093
1094 return 0;
1095
1096 error:
1097 frr_confd_finish_dp();
1098
1099 return -1;
1100 }
1101
1102 static void frr_confd_finish_dp(void)
1103 {
1104 if (dp_worker_sock > 0) {
1105 THREAD_OFF(t_dp_worker);
1106 close(dp_worker_sock);
1107 }
1108 if (dp_ctl_sock > 0) {
1109 THREAD_OFF(t_dp_ctl);
1110 close(dp_ctl_sock);
1111 }
1112 if (dctx != NULL)
1113 confd_release_daemon(dctx);
1114 }
1115
1116 /* ------------ Main ------------ */
1117
1118 static void frr_confd_calculate_snode_hash(const struct lys_node *snode,
1119 void *arg1, void *arg2)
1120 {
1121 struct nb_node *nb_node = snode->priv;
1122
1123 nb_node->confd_hash = confd_str2hash(snode->name);
1124 }
1125
1126 static int frr_confd_init(const char *program_name)
1127 {
1128 struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr;
1129 int debuglevel = CONFD_SILENT;
1130 int ret = -1;
1131
1132 /* Initialize ConfD library. */
1133 confd_init(program_name, stderr, debuglevel);
1134
1135 confd_addr4->sin_family = AF_INET;
1136 confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1");
1137 confd_addr4->sin_port = htons(CONFD_PORT);
1138 if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in))
1139 != CONFD_OK) {
1140 flog_err_confd("confd_load_schemas");
1141 return -1;
1142 }
1143
1144 ret = frr_confd_init_cdb();
1145 if (ret != 0)
1146 goto error;
1147
1148 ret = frr_confd_init_dp(program_name);
1149 if (ret != 0) {
1150 frr_confd_finish_cdb();
1151 goto error;
1152 }
1153
1154 yang_all_snodes_iterate(frr_confd_calculate_snode_hash, 0, NULL, NULL);
1155
1156 hook_register(nb_notification_send, frr_confd_notification_send);
1157
1158 confd_connected = true;
1159 return 0;
1160
1161 error:
1162 confd_free_schemas();
1163
1164 return ret;
1165 }
1166
1167 static int frr_confd_finish(void)
1168 {
1169 if (confd_connected == false)
1170 return 0;
1171
1172 frr_confd_finish_cdb();
1173 frr_confd_finish_dp();
1174
1175 confd_free_schemas();
1176
1177 confd_connected = false;
1178
1179 return 0;
1180 }
1181
1182 static int frr_confd_module_late_init(struct thread_master *tm)
1183 {
1184 master = tm;
1185
1186 if (frr_confd_init(frr_get_progname()) < 0) {
1187 flog_err(EC_LIB_CONFD_INIT,
1188 "failed to initialize the ConfD module");
1189 return -1;
1190 }
1191
1192 hook_register(frr_fini, frr_confd_finish);
1193
1194 return 0;
1195 }
1196
1197 static int frr_confd_module_init(void)
1198 {
1199 hook_register(frr_late_init, frr_confd_module_late_init);
1200
1201 return 0;
1202 }
1203
1204 FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION,
1205 .description = "FRR ConfD integration module",
1206 .init = frr_confd_module_init, )