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