]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
5bce33b3 RW |
2 | /* |
3 | * Copyright (C) 2018 NetDEF, Inc. | |
4 | * Renato Westphal | |
5bce33b3 RW |
5 | */ |
6 | ||
7 | #include <zebra.h> | |
8 | ||
9 | #include "log.h" | |
10 | #include "lib_errors.h" | |
11 | #include "command.h" | |
9eb2c0a1 | 12 | #include "debug.h" |
5bce33b3 | 13 | #include "libfrr.h" |
09781197 | 14 | #include "lib/version.h" |
5bce33b3 RW |
15 | #include "northbound.h" |
16 | ||
17 | #include <confd_lib.h> | |
18 | #include <confd_cdb.h> | |
19 | #include <confd_dp.h> | |
20 | #include <confd_maapi.h> | |
21 | ||
bf8d3d6a | 22 | DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module"); |
5bce33b3 | 23 | |
9eb2c0a1 RW |
24 | static struct debug nb_dbg_client_confd = {0, "Northbound client: ConfD"}; |
25 | ||
cd9d0537 | 26 | static struct event_loop *master; |
5bce33b3 RW |
27 | static struct sockaddr confd_addr; |
28 | static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock; | |
e6685141 | 29 | static struct event *t_cdb_sub, *t_dp_ctl, *t_dp_worker; |
5bce33b3 RW |
30 | static struct confd_daemon_ctx *dctx; |
31 | static struct confd_notification_ctx *live_ctx; | |
32 | static bool confd_connected; | |
33 | static struct list *confd_spoints; | |
88a7d121 | 34 | static struct nb_transaction *transaction; |
5bce33b3 RW |
35 | |
36 | static void frr_confd_finish_cdb(void); | |
37 | static void frr_confd_finish_dp(void); | |
38 | static int frr_confd_finish(void); | |
39 | ||
40 | #define flog_err_confd(funcname) \ | |
41 | flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__, \ | |
42 | (funcname), confd_strerror(confd_errno), confd_errno, \ | |
43 | confd_lasterr()) | |
44 | ||
45 | ||
46 | /* ------------ Utils ------------ */ | |
47 | ||
48 | /* Get XPath string from ConfD hashed keypath. */ | |
49 | static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath, | |
50 | size_t len) | |
51 | { | |
52 | char *p; | |
53 | ||
54 | confd_xpath_pp_kpath(xpath, len, 0, kp); | |
55 | ||
56 | /* | |
57 | * Replace double quotes by single quotes (the format accepted by the | |
58 | * northbound API). | |
59 | */ | |
60 | p = xpath; | |
61 | while ((p = strchr(p, '"')) != NULL) | |
62 | *p++ = '\''; | |
63 | } | |
64 | ||
65 | /* Convert ConfD binary value to a string. */ | |
66 | static int frr_confd_val2str(const char *xpath, const confd_value_t *value, | |
67 | char *string, size_t string_size) | |
68 | { | |
69 | struct confd_cs_node *csp; | |
70 | ||
71 | csp = confd_cs_node_cd(NULL, xpath); | |
72 | if (!csp) { | |
73 | flog_err_confd("confd_cs_node_cd"); | |
74 | return -1; | |
75 | } | |
76 | if (confd_val2str(csp->info.type, value, string, string_size) | |
77 | == CONFD_ERR) { | |
78 | flog_err_confd("confd_val2str"); | |
79 | return -1; | |
80 | } | |
81 | ||
82 | return 0; | |
83 | } | |
84 | ||
1a4bc045 RW |
85 | /* Obtain list entry from ConfD hashed keypath. */ |
86 | static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp, | |
87 | struct nb_node *nb_node, | |
88 | const void **list_entry) | |
5bce33b3 | 89 | { |
1a4bc045 RW |
90 | struct nb_node *nb_node_list; |
91 | int parent_lists = 0; | |
92 | int curr_list = 0; | |
93 | ||
94 | *list_entry = NULL; | |
95 | ||
96 | /* | |
97 | * Count the number of YANG lists in the path, disconsidering the | |
98 | * last element. | |
99 | */ | |
100 | nb_node_list = nb_node; | |
101 | while (nb_node_list->parent_list) { | |
102 | nb_node_list = nb_node_list->parent_list; | |
103 | parent_lists++; | |
104 | } | |
105 | if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0) | |
106 | return 0; | |
107 | ||
108 | /* Start from the beginning and move down the tree. */ | |
109 | for (int i = kp->len; i >= 0; i--) { | |
110 | struct yang_list_keys keys; | |
111 | ||
112 | /* Not a YANG list. */ | |
5bce33b3 RW |
113 | if (kp->v[i][0].type != C_BUF) |
114 | continue; | |
115 | ||
1a4bc045 RW |
116 | /* Obtain list keys. */ |
117 | memset(&keys, 0, sizeof(keys)); | |
5bce33b3 | 118 | for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) { |
1a4bc045 | 119 | strlcpy(keys.key[keys.num], |
5bce33b3 | 120 | (char *)kp->v[i][j].val.buf.ptr, |
1a4bc045 RW |
121 | sizeof(keys.key[keys.num])); |
122 | keys.num++; | |
5bce33b3 | 123 | } |
1a4bc045 RW |
124 | |
125 | /* Obtain northbound node associated to the YANG list. */ | |
126 | nb_node_list = nb_node; | |
127 | for (int j = curr_list; j < parent_lists; j++) | |
128 | nb_node_list = nb_node_list->parent_list; | |
129 | ||
130 | /* Obtain list entry. */ | |
99fb518f | 131 | if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) { |
9eb2c0a1 RW |
132 | *list_entry = nb_callback_lookup_entry( |
133 | nb_node, *list_entry, &keys); | |
99fb518f RW |
134 | if (*list_entry == NULL) |
135 | return -1; | |
136 | } else { | |
137 | unsigned long ptr_ulong; | |
138 | ||
139 | /* Retrieve list entry from pseudo-key (string). */ | |
140 | if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1) | |
141 | return -1; | |
142 | *list_entry = (const void *)ptr_ulong; | |
143 | } | |
1a4bc045 RW |
144 | |
145 | curr_list++; | |
5bce33b3 | 146 | } |
1a4bc045 RW |
147 | |
148 | return 0; | |
5bce33b3 RW |
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: | |
95ce849b | 221 | nb_op = NB_OP_DESTROY; |
5bce33b3 RW |
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 | ||
cc9f21da | 273 | static void frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen) |
5bce33b3 | 274 | { |
13d6b9c1 | 275 | struct nb_context context = {}; |
5bce33b3 RW |
276 | struct nb_config *candidate; |
277 | struct cdb_iter_args iter_args; | |
df5eda3d | 278 | char errmsg[BUFSIZ] = {0}; |
5bce33b3 RW |
279 | int ret; |
280 | ||
8685be73 | 281 | candidate = nb_config_dup(running_config); |
5bce33b3 RW |
282 | |
283 | /* Iterate over all configuration changes. */ | |
284 | iter_args.candidate = candidate; | |
285 | iter_args.error = false; | |
286 | for (int i = 0; i < reslen; i++) { | |
287 | if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter, | |
288 | ITER_WANT_PREV, &iter_args) | |
289 | != CONFD_OK) { | |
290 | flog_err_confd("cdb_diff_iterate"); | |
291 | } | |
292 | } | |
293 | free(subp); | |
294 | ||
295 | if (iter_args.error) { | |
296 | nb_config_free(candidate); | |
297 | ||
298 | if (cdb_sub_abort_trans( | |
299 | cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0, | |
300 | 0, "Couldn't apply configuration changes") | |
301 | != CONFD_OK) { | |
302 | flog_err_confd("cdb_sub_abort_trans"); | |
cc9f21da | 303 | return; |
5bce33b3 | 304 | } |
cc9f21da | 305 | return; |
5bce33b3 RW |
306 | } |
307 | ||
88a7d121 RW |
308 | /* |
309 | * Validate the configuration changes and allocate all resources | |
310 | * required to apply them. | |
311 | */ | |
312 | transaction = NULL; | |
13d6b9c1 | 313 | context.client = NB_CLIENT_CONFD; |
41ef7327 | 314 | ret = nb_candidate_commit_prepare(context, candidate, NULL, |
7d65b7b7 CH |
315 | &transaction, false, false, errmsg, |
316 | sizeof(errmsg)); | |
5bce33b3 RW |
317 | if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) { |
318 | enum confd_errcode errcode; | |
5bce33b3 RW |
319 | |
320 | switch (ret) { | |
321 | case NB_ERR_LOCKED: | |
322 | errcode = CONFD_ERRCODE_IN_USE; | |
5bce33b3 RW |
323 | break; |
324 | case NB_ERR_RESOURCE: | |
325 | errcode = CONFD_ERRCODE_RESOURCE_DENIED; | |
5bce33b3 RW |
326 | break; |
327 | default: | |
df5eda3d | 328 | errcode = CONFD_ERRCODE_APPLICATION; |
5bce33b3 RW |
329 | break; |
330 | } | |
331 | ||
88a7d121 | 332 | /* Reject the configuration changes. */ |
5bce33b3 RW |
333 | if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s", |
334 | errmsg) | |
335 | != CONFD_OK) { | |
336 | flog_err_confd("cdb_sub_abort_trans"); | |
cc9f21da | 337 | return; |
5bce33b3 RW |
338 | } |
339 | } else { | |
88a7d121 | 340 | /* Acknowledge the notification. */ |
5bce33b3 RW |
341 | if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) |
342 | != CONFD_OK) { | |
343 | flog_err_confd("cdb_sync_subscription_socket"); | |
cc9f21da | 344 | return; |
5bce33b3 | 345 | } |
88a7d121 RW |
346 | |
347 | /* No configuration changes. */ | |
348 | if (!transaction) | |
349 | nb_config_free(candidate); | |
350 | } | |
88a7d121 RW |
351 | } |
352 | ||
cc9f21da | 353 | static void frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen) |
88a7d121 RW |
354 | { |
355 | /* | |
356 | * No need to process the configuration changes again as we're already | |
357 | * keeping track of them in the "transaction" variable. | |
358 | */ | |
359 | free(subp); | |
360 | ||
361 | /* Apply the transaction. */ | |
362 | if (transaction) { | |
363 | struct nb_config *candidate = transaction->config; | |
0fe5b904 | 364 | char errmsg[BUFSIZ] = {0}; |
88a7d121 | 365 | |
0fe5b904 RW |
366 | nb_candidate_commit_apply(transaction, true, NULL, errmsg, |
367 | sizeof(errmsg)); | |
88a7d121 RW |
368 | nb_config_free(candidate); |
369 | } | |
370 | ||
371 | /* Acknowledge the notification. */ | |
372 | if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { | |
373 | flog_err_confd("cdb_sync_subscription_socket"); | |
cc9f21da | 374 | return; |
88a7d121 | 375 | } |
88a7d121 RW |
376 | } |
377 | ||
378 | static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen) | |
379 | { | |
380 | /* | |
381 | * No need to process the configuration changes again as we're already | |
382 | * keeping track of them in the "transaction" variable. | |
383 | */ | |
384 | free(subp); | |
385 | ||
386 | /* Abort the transaction. */ | |
387 | if (transaction) { | |
388 | struct nb_config *candidate = transaction->config; | |
0fe5b904 | 389 | char errmsg[BUFSIZ] = {0}; |
88a7d121 | 390 | |
0fe5b904 | 391 | nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg)); |
88a7d121 RW |
392 | nb_config_free(candidate); |
393 | } | |
394 | ||
395 | /* Acknowledge the notification. */ | |
396 | if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) { | |
397 | flog_err_confd("cdb_sync_subscription_socket"); | |
398 | return -1; | |
5bce33b3 RW |
399 | } |
400 | ||
401 | return 0; | |
402 | } | |
403 | ||
e6685141 | 404 | static void frr_confd_cdb_read_cb(struct event *thread) |
88a7d121 | 405 | { |
e16d030c | 406 | int fd = EVENT_FD(thread); |
88a7d121 RW |
407 | enum cdb_sub_notification cdb_ev; |
408 | int flags; | |
409 | int *subp = NULL; | |
410 | int reslen = 0; | |
411 | ||
907a2395 | 412 | event_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &t_cdb_sub); |
88a7d121 RW |
413 | |
414 | if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen) | |
415 | != CONFD_OK) { | |
416 | flog_err_confd("cdb_read_subscription_socket2"); | |
cc9f21da | 417 | return; |
88a7d121 RW |
418 | } |
419 | ||
420 | switch (cdb_ev) { | |
421 | case CDB_SUB_PREPARE: | |
cc9f21da DS |
422 | frr_confd_cdb_read_cb_prepare(fd, subp, reslen); |
423 | break; | |
88a7d121 | 424 | case CDB_SUB_COMMIT: |
cc9f21da DS |
425 | frr_confd_cdb_read_cb_commit(fd, subp, reslen); |
426 | break; | |
88a7d121 | 427 | case CDB_SUB_ABORT: |
cc9f21da DS |
428 | frr_confd_cdb_read_cb_abort(fd, subp, reslen); |
429 | break; | |
88a7d121 RW |
430 | default: |
431 | flog_err_confd("unknown CDB event"); | |
cc9f21da | 432 | break; |
88a7d121 RW |
433 | } |
434 | } | |
435 | ||
5bce33b3 RW |
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 | ||
722e430f RB |
481 | static int frr_confd_subscribe(const struct lysc_node *snode, void *arg) |
482 | { | |
483 | struct yang_module *module = arg; | |
484 | struct nb_node *nb_node; | |
485 | int *spoint; | |
486 | int ret; | |
487 | ||
488 | switch (snode->nodetype) { | |
489 | case LYS_CONTAINER: | |
490 | case LYS_LEAF: | |
491 | case LYS_LEAFLIST: | |
492 | case LYS_LIST: | |
493 | break; | |
494 | default: | |
495 | return YANG_ITER_CONTINUE; | |
496 | } | |
497 | ||
498 | if (CHECK_FLAG(snode->flags, LYS_CONFIG_R)) | |
499 | return YANG_ITER_CONTINUE; | |
500 | ||
501 | nb_node = snode->priv; | |
502 | if (!nb_node) | |
503 | return YANG_ITER_CONTINUE; | |
504 | ||
505 | DEBUGD(&nb_dbg_client_confd, "%s: subscribing to '%s'", __func__, | |
506 | nb_node->xpath); | |
507 | ||
508 | spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint)); | |
509 | ret = cdb_subscribe2(cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE, | |
510 | CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint, | |
511 | module->confd_hash, nb_node->xpath); | |
512 | if (ret != CONFD_OK) { | |
513 | flog_err_confd("cdb_subscribe2"); | |
514 | XFREE(MTYPE_CONFD, spoint); | |
515 | return YANG_ITER_CONTINUE; | |
516 | } | |
517 | ||
518 | listnode_add(confd_spoints, spoint); | |
519 | return YANG_ITER_CONTINUE; | |
520 | } | |
521 | ||
5bce33b3 RW |
522 | static int frr_confd_init_cdb(void) |
523 | { | |
524 | struct yang_module *module; | |
525 | pthread_t cdb_trigger_thread; | |
526 | ||
527 | /* Create CDB subscription socket. */ | |
528 | cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0); | |
529 | if (cdb_sub_sock < 0) { | |
530 | flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", | |
531 | __func__, safe_strerror(errno)); | |
532 | return -1; | |
533 | } | |
534 | ||
535 | if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr, | |
536 | sizeof(struct sockaddr_in)) | |
537 | != CONFD_OK) { | |
538 | flog_err_confd("cdb_connect"); | |
539 | goto error; | |
540 | } | |
541 | ||
542 | /* Subscribe to all loaded YANG data modules. */ | |
543 | confd_spoints = list_new(); | |
544 | RB_FOREACH (module, yang_modules, &yang_modules) { | |
5bce33b3 RW |
545 | module->confd_hash = confd_str2hash(module->info->ns); |
546 | if (module->confd_hash == 0) { | |
547 | flog_err( | |
548 | EC_LIB_LIBCONFD, | |
549 | "%s: failed to find hash value for namespace %s", | |
550 | __func__, module->info->ns); | |
551 | goto error; | |
552 | } | |
553 | ||
554 | /* | |
555 | * The CDB API doesn't provide a mechanism to subscribe to an | |
556 | * entire YANG module. So we have to find the top level | |
557 | * nodes ourselves and subscribe to their paths. | |
558 | */ | |
722e430f RB |
559 | yang_snodes_iterate(module->info, frr_confd_subscribe, 0, |
560 | module); | |
5bce33b3 RW |
561 | } |
562 | ||
563 | if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) { | |
564 | flog_err_confd("cdb_subscribe_done"); | |
565 | goto error; | |
566 | } | |
567 | ||
568 | /* Create short lived pthread to trigger the CDB subscriptions. */ | |
569 | if (pthread_create(&cdb_trigger_thread, NULL, | |
570 | thread_cdb_trigger_subscriptions, NULL)) { | |
571 | flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s", | |
572 | __func__, safe_strerror(errno)); | |
573 | goto error; | |
574 | } | |
575 | pthread_detach(cdb_trigger_thread); | |
576 | ||
907a2395 DS |
577 | event_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock, |
578 | &t_cdb_sub); | |
5bce33b3 RW |
579 | |
580 | return 0; | |
581 | ||
582 | error: | |
583 | frr_confd_finish_cdb(); | |
584 | ||
585 | return -1; | |
586 | } | |
587 | ||
588 | static void frr_confd_finish_cdb(void) | |
589 | { | |
590 | if (cdb_sub_sock > 0) { | |
e16d030c | 591 | EVENT_OFF(t_cdb_sub); |
5bce33b3 RW |
592 | cdb_close(cdb_sub_sock); |
593 | } | |
594 | } | |
595 | ||
596 | /* ------------ DP code ------------ */ | |
597 | ||
598 | static int frr_confd_transaction_init(struct confd_trans_ctx *tctx) | |
599 | { | |
600 | confd_trans_set_fd(tctx, dp_worker_sock); | |
601 | ||
602 | return CONFD_OK; | |
603 | } | |
604 | ||
1a4bc045 RW |
605 | #define CONFD_MAX_CHILD_NODES 32 |
606 | ||
5bce33b3 RW |
607 | static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx, |
608 | confd_hkeypath_t *kp) | |
609 | { | |
1a4bc045 | 610 | struct nb_node *nb_node; |
78919336 | 611 | char xpath[XPATH_MAXLEN]; |
5bce33b3 RW |
612 | struct yang_data *data; |
613 | confd_value_t v; | |
614 | const void *list_entry = NULL; | |
615 | ||
616 | frr_confd_get_xpath(kp, xpath, sizeof(xpath)); | |
617 | ||
618 | nb_node = nb_node_find(xpath); | |
619 | if (!nb_node) { | |
620 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
621 | "%s: unknown data path: %s", __func__, xpath); | |
622 | confd_data_reply_not_found(tctx); | |
623 | return CONFD_OK; | |
624 | } | |
625 | ||
1a4bc045 RW |
626 | if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { |
627 | confd_data_reply_not_found(tctx); | |
628 | return CONFD_OK; | |
5bce33b3 RW |
629 | } |
630 | ||
9eb2c0a1 | 631 | data = nb_callback_get_elem(nb_node, xpath, list_entry); |
5bce33b3 RW |
632 | if (data) { |
633 | if (data->value) { | |
634 | CONFD_SET_STR(&v, data->value); | |
635 | confd_data_reply_value(tctx, &v); | |
636 | } else | |
637 | confd_data_reply_found(tctx); | |
638 | yang_data_free(data); | |
639 | } else | |
640 | confd_data_reply_not_found(tctx); | |
641 | ||
642 | return CONFD_OK; | |
643 | } | |
644 | ||
645 | static int frr_confd_data_get_next(struct confd_trans_ctx *tctx, | |
646 | confd_hkeypath_t *kp, long next) | |
647 | { | |
648 | struct nb_node *nb_node; | |
78919336 | 649 | char xpath[XPATH_MAXLEN]; |
1a4bc045 RW |
650 | struct yang_data *data; |
651 | const void *parent_list_entry, *nb_next; | |
5bce33b3 RW |
652 | confd_value_t v[LIST_MAXKEYS]; |
653 | ||
654 | frr_confd_get_xpath(kp, xpath, sizeof(xpath)); | |
655 | ||
656 | nb_node = nb_node_find(xpath); | |
657 | if (!nb_node) { | |
658 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
659 | "%s: unknown data path: %s", __func__, xpath); | |
660 | confd_data_reply_next_key(tctx, NULL, -1, -1); | |
661 | return CONFD_OK; | |
662 | } | |
663 | ||
1a4bc045 RW |
664 | if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) |
665 | != 0) { | |
666 | /* List entry doesn't exist anymore. */ | |
5bce33b3 RW |
667 | confd_data_reply_next_key(tctx, NULL, -1, -1); |
668 | return CONFD_OK; | |
669 | } | |
1a4bc045 | 670 | |
9eb2c0a1 RW |
671 | nb_next = nb_callback_get_next(nb_node, parent_list_entry, |
672 | (next == -1) ? NULL : (void *)next); | |
1a4bc045 RW |
673 | if (!nb_next) { |
674 | /* End of the list or leaf-list. */ | |
5bce33b3 RW |
675 | confd_data_reply_next_key(tctx, NULL, -1, -1); |
676 | return CONFD_OK; | |
677 | } | |
678 | ||
1a4bc045 RW |
679 | switch (nb_node->snode->nodetype) { |
680 | case LYS_LIST: | |
99fb518f RW |
681 | if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { |
682 | struct yang_list_keys keys; | |
683 | ||
684 | memset(&keys, 0, sizeof(keys)); | |
9eb2c0a1 RW |
685 | if (nb_callback_get_keys(nb_node, nb_next, &keys) |
686 | != NB_OK) { | |
99fb518f RW |
687 | flog_warn(EC_LIB_NB_CB_STATE, |
688 | "%s: failed to get list keys", | |
689 | __func__); | |
690 | confd_data_reply_next_key(tctx, NULL, -1, -1); | |
691 | return CONFD_OK; | |
692 | } | |
1a4bc045 | 693 | |
99fb518f RW |
694 | /* Feed keys to ConfD. */ |
695 | for (size_t i = 0; i < keys.num; i++) | |
696 | CONFD_SET_STR(&v[i], keys.key[i]); | |
697 | confd_data_reply_next_key(tctx, v, keys.num, | |
698 | (long)nb_next); | |
699 | } else { | |
fc3ebe12 | 700 | char pointer_str[32]; |
99fb518f RW |
701 | |
702 | /* | |
703 | * ConfD 6.6 user guide, chapter 6.11 (Operational data | |
704 | * lists without keys): | |
705 | * "To support this without having completely separate | |
706 | * APIs, we use a "pseudo" key in the ConfD APIs for | |
707 | * this type of list. This key is not part of the data | |
708 | * model, and completely hidden in the northbound agent | |
709 | * interfaces, but is used with e.g. the get_next() and | |
710 | * get_elem() callbacks as if it were a normal key. This | |
711 | * "pseudo" key is always a single signed 64-bit | |
712 | * integer, i.e. the confd_value_t type is C_INT64. The | |
713 | * values can be chosen arbitrarily by the application, | |
714 | * as long as a key value returned by get_next() can be | |
715 | * used to get the data for the corresponding list entry | |
716 | * with get_elem() or get_object() as usual. It could | |
717 | * e.g. be an index into an array that holds the data, | |
718 | * or even a memory address in integer form". | |
719 | * | |
720 | * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY | |
721 | * option, we must convert our pseudo-key (a void | |
722 | * pointer) to a string before sending it to confd. | |
723 | */ | |
724 | snprintf(pointer_str, sizeof(pointer_str), "%lu", | |
725 | (unsigned long)nb_next); | |
726 | CONFD_SET_STR(&v[0], pointer_str); | |
727 | confd_data_reply_next_key(tctx, v, 1, (long)nb_next); | |
728 | } | |
1a4bc045 RW |
729 | break; |
730 | case LYS_LEAFLIST: | |
9eb2c0a1 | 731 | data = nb_callback_get_elem(nb_node, xpath, nb_next); |
1a4bc045 RW |
732 | if (data) { |
733 | if (data->value) { | |
734 | CONFD_SET_STR(&v[0], data->value); | |
735 | confd_data_reply_next_key(tctx, v, 1, | |
736 | (long)nb_next); | |
737 | } | |
738 | yang_data_free(data); | |
739 | } else | |
740 | confd_data_reply_next_key(tctx, NULL, -1, -1); | |
741 | break; | |
742 | default: | |
743 | break; | |
744 | } | |
5bce33b3 RW |
745 | |
746 | return CONFD_OK; | |
747 | } | |
748 | ||
749 | /* | |
750 | * Optional callback - implemented for performance reasons. | |
751 | */ | |
752 | static int frr_confd_data_get_object(struct confd_trans_ctx *tctx, | |
753 | confd_hkeypath_t *kp) | |
754 | { | |
755 | struct nb_node *nb_node; | |
3bb513c3 | 756 | const struct lysc_node *child; |
78919336 RW |
757 | char xpath[XPATH_MAXLEN]; |
758 | char xpath_child[XPATH_MAXLEN * 2]; | |
5bce33b3 RW |
759 | struct list *elements; |
760 | struct yang_data *data; | |
761 | const void *list_entry; | |
1a4bc045 RW |
762 | confd_value_t values[CONFD_MAX_CHILD_NODES]; |
763 | size_t nvalues = 0; | |
5bce33b3 RW |
764 | |
765 | frr_confd_get_xpath(kp, xpath, sizeof(xpath)); | |
766 | ||
767 | nb_node = nb_node_find(xpath); | |
768 | if (!nb_node) { | |
769 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
770 | "%s: unknown data path: %s", __func__, xpath); | |
771 | confd_data_reply_not_found(tctx); | |
1a4bc045 | 772 | return CONFD_ERR; |
5bce33b3 RW |
773 | } |
774 | ||
1a4bc045 | 775 | if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) { |
5bce33b3 RW |
776 | confd_data_reply_not_found(tctx); |
777 | return CONFD_OK; | |
778 | } | |
779 | ||
5bce33b3 | 780 | elements = yang_data_list_new(); |
5bce33b3 RW |
781 | |
782 | /* Loop through list child nodes. */ | |
3bb513c3 | 783 | LY_LIST_FOR (lysc_node_child(nb_node->snode), child) { |
1a4bc045 RW |
784 | struct nb_node *nb_node_child = child->priv; |
785 | confd_value_t *v; | |
786 | ||
787 | if (nvalues > CONFD_MAX_CHILD_NODES) | |
788 | break; | |
789 | ||
790 | v = &values[nvalues++]; | |
5bce33b3 | 791 | |
1a4bc045 RW |
792 | /* Non-presence containers, lists and leaf-lists. */ |
793 | if (!nb_node_child->cbs.get_elem) { | |
794 | CONFD_SET_NOEXISTS(v); | |
795 | continue; | |
796 | } | |
5bce33b3 RW |
797 | |
798 | snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath, | |
799 | child->name); | |
9eb2c0a1 RW |
800 | data = nb_callback_get_elem(nb_node_child, xpath_child, |
801 | list_entry); | |
5bce33b3 RW |
802 | if (data) { |
803 | if (data->value) | |
1a4bc045 RW |
804 | CONFD_SET_STR(v, data->value); |
805 | else { | |
806 | /* Presence containers and empty leafs. */ | |
807 | CONFD_SET_XMLTAG( | |
808 | v, nb_node_child->confd_hash, | |
809 | confd_str2hash(nb_node_child->snode | |
810 | ->module->ns)); | |
811 | } | |
5bce33b3 RW |
812 | listnode_add(elements, data); |
813 | } else | |
1a4bc045 | 814 | CONFD_SET_NOEXISTS(v); |
5bce33b3 RW |
815 | } |
816 | ||
1a4bc045 | 817 | confd_data_reply_value_array(tctx, values, nvalues); |
5bce33b3 RW |
818 | |
819 | /* Release memory. */ | |
5bce33b3 RW |
820 | list_delete(&elements); |
821 | ||
822 | return CONFD_OK; | |
823 | } | |
824 | ||
825 | /* | |
826 | * Optional callback - implemented for performance reasons. | |
827 | */ | |
828 | static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx, | |
829 | confd_hkeypath_t *kp, long next) | |
830 | { | |
78919336 | 831 | char xpath[XPATH_MAXLEN]; |
5bce33b3 | 832 | struct nb_node *nb_node; |
5bce33b3 | 833 | struct list *elements; |
1a4bc045 | 834 | const void *parent_list_entry; |
5bce33b3 RW |
835 | const void *nb_next; |
836 | #define CONFD_OBJECTS_PER_TIME 100 | |
837 | struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1]; | |
fc3ebe12 | 838 | char pseudo_keys[CONFD_OBJECTS_PER_TIME][32]; |
5bce33b3 RW |
839 | int nobjects = 0; |
840 | ||
841 | frr_confd_get_xpath(kp, xpath, sizeof(xpath)); | |
842 | ||
843 | nb_node = nb_node_find(xpath); | |
844 | if (!nb_node) { | |
845 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
846 | "%s: unknown data path: %s", __func__, xpath); | |
847 | confd_data_reply_next_object_array(tctx, NULL, 0, 0); | |
848 | return CONFD_OK; | |
849 | } | |
850 | ||
1a4bc045 RW |
851 | if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry) |
852 | != 0) { | |
853 | confd_data_reply_next_object_array(tctx, NULL, 0, 0); | |
854 | return CONFD_OK; | |
5bce33b3 RW |
855 | } |
856 | ||
857 | elements = yang_data_list_new(); | |
858 | nb_next = (next == -1) ? NULL : (void *)next; | |
859 | ||
860 | memset(objects, 0, sizeof(objects)); | |
861 | for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) { | |
862 | struct confd_next_object *object; | |
722e430f | 863 | const struct lysc_node *child; |
5bce33b3 | 864 | struct yang_data *data; |
1a4bc045 | 865 | size_t nvalues = 0; |
5bce33b3 RW |
866 | |
867 | object = &objects[j]; | |
868 | ||
9eb2c0a1 RW |
869 | nb_next = nb_callback_get_next(nb_node, parent_list_entry, |
870 | nb_next); | |
5bce33b3 RW |
871 | if (!nb_next) |
872 | /* End of the list. */ | |
873 | break; | |
874 | ||
5bce33b3 RW |
875 | object->next = (long)nb_next; |
876 | ||
1a4bc045 RW |
877 | /* Leaf-lists require special handling. */ |
878 | if (nb_node->snode->nodetype == LYS_LEAFLIST) { | |
879 | object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t)); | |
9eb2c0a1 | 880 | data = nb_callback_get_elem(nb_node, xpath, nb_next); |
1a4bc045 RW |
881 | assert(data && data->value); |
882 | CONFD_SET_STR(object->v, data->value); | |
883 | nvalues++; | |
884 | listnode_add(elements, data); | |
885 | goto next; | |
5bce33b3 RW |
886 | } |
887 | ||
1a4bc045 RW |
888 | object->v = |
889 | XMALLOC(MTYPE_CONFD, | |
890 | CONFD_MAX_CHILD_NODES * sizeof(confd_value_t)); | |
5bce33b3 | 891 | |
99fb518f RW |
892 | /* |
893 | * ConfD 6.6 user guide, chapter 6.11 (Operational data lists | |
894 | * without keys): | |
895 | * "In the response to the get_next_object() callback, the data | |
896 | * provider is expected to provide the key values along with the | |
897 | * other leafs in an array that is populated according to the | |
898 | * data model. This must be done also for this type of list, | |
899 | * even though the key isn't actually in the data model. The | |
900 | * "pseudo" key must always be the first element in the array". | |
901 | */ | |
902 | if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) { | |
903 | confd_value_t *v; | |
904 | ||
905 | snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu", | |
906 | (unsigned long)nb_next); | |
907 | ||
908 | v = &object->v[nvalues++]; | |
909 | CONFD_SET_STR(v, pseudo_keys[j]); | |
910 | } | |
911 | ||
5bce33b3 | 912 | /* Loop through list child nodes. */ |
3bb513c3 | 913 | LY_LIST_FOR (lysc_node_child(nb_node->snode), child) { |
1a4bc045 | 914 | struct nb_node *nb_node_child = child->priv; |
78919336 | 915 | char xpath_child[XPATH_MAXLEN * 2]; |
1a4bc045 | 916 | confd_value_t *v; |
5bce33b3 | 917 | |
1a4bc045 RW |
918 | if (nvalues > CONFD_MAX_CHILD_NODES) |
919 | break; | |
5bce33b3 | 920 | |
1a4bc045 RW |
921 | v = &object->v[nvalues++]; |
922 | ||
923 | /* Non-presence containers, lists and leaf-lists. */ | |
924 | if (!nb_node_child->cbs.get_elem) { | |
925 | CONFD_SET_NOEXISTS(v); | |
926 | continue; | |
927 | } | |
5bce33b3 RW |
928 | |
929 | snprintf(xpath_child, sizeof(xpath_child), "%s/%s", | |
930 | xpath, child->name); | |
9eb2c0a1 RW |
931 | data = nb_callback_get_elem(nb_node_child, xpath_child, |
932 | nb_next); | |
5bce33b3 RW |
933 | if (data) { |
934 | if (data->value) | |
935 | CONFD_SET_STR(v, data->value); | |
1a4bc045 RW |
936 | else { |
937 | /* | |
938 | * Presence containers and empty leafs. | |
939 | */ | |
940 | CONFD_SET_XMLTAG( | |
941 | v, nb_node_child->confd_hash, | |
942 | confd_str2hash( | |
943 | nb_node_child->snode | |
944 | ->module->ns)); | |
945 | } | |
5bce33b3 RW |
946 | listnode_add(elements, data); |
947 | } else | |
948 | CONFD_SET_NOEXISTS(v); | |
949 | } | |
1a4bc045 RW |
950 | next: |
951 | object->n = nvalues; | |
5bce33b3 RW |
952 | nobjects++; |
953 | } | |
5bce33b3 RW |
954 | |
955 | if (nobjects == 0) { | |
956 | confd_data_reply_next_object_array(tctx, NULL, 0, 0); | |
957 | list_delete(&elements); | |
958 | return CONFD_OK; | |
959 | } | |
960 | ||
961 | /* Detect end of the list. */ | |
962 | if (!nb_next) { | |
963 | nobjects++; | |
964 | objects[nobjects].v = NULL; | |
965 | } | |
966 | ||
967 | /* Reply to ConfD. */ | |
968 | confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0); | |
969 | if (!nb_next) | |
970 | nobjects--; | |
971 | ||
972 | /* Release memory. */ | |
973 | list_delete(&elements); | |
974 | for (int j = 0; j < nobjects; j++) { | |
975 | struct confd_next_object *object; | |
976 | ||
977 | object = &objects[j]; | |
978 | XFREE(MTYPE_CONFD, object->v); | |
979 | } | |
980 | ||
981 | return CONFD_OK; | |
982 | } | |
983 | ||
984 | static int frr_confd_notification_send(const char *xpath, | |
985 | struct list *arguments) | |
986 | { | |
987 | struct nb_node *nb_node; | |
988 | struct yang_module *module; | |
989 | struct confd_datetime now; | |
990 | confd_tag_value_t *values; | |
991 | int nvalues; | |
992 | int i = 0; | |
993 | struct yang_data *data; | |
994 | struct listnode *node; | |
995 | int ret; | |
996 | ||
997 | nb_node = nb_node_find(xpath); | |
998 | if (!nb_node) { | |
999 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
1000 | "%s: unknown data path: %s", __func__, xpath); | |
1001 | return -1; | |
1002 | } | |
1003 | module = yang_module_find(nb_node->snode->module->name); | |
1004 | assert(module); | |
1005 | ||
1006 | nvalues = 2; | |
1007 | if (arguments) | |
1008 | nvalues += listcount(arguments); | |
1009 | ||
1010 | values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values)); | |
1011 | ||
1012 | CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash, | |
1013 | module->confd_hash); | |
1014 | for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) { | |
80243aef RW |
1015 | struct nb_node *nb_node_arg; |
1016 | ||
1017 | nb_node_arg = nb_node_find(data->xpath); | |
1018 | if (!nb_node_arg) { | |
1019 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
1020 | "%s: unknown data path: %s", __func__, | |
1021 | data->xpath); | |
1022 | XFREE(MTYPE_CONFD, values); | |
1023 | return NB_ERR; | |
1024 | } | |
5bce33b3 | 1025 | |
80243aef | 1026 | CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash, |
5bce33b3 RW |
1027 | data->value); |
1028 | } | |
1029 | CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash, | |
1030 | module->confd_hash); | |
1031 | ||
1032 | getdatetime(&now); | |
1033 | ret = confd_notification_send(live_ctx, &now, values, nvalues); | |
1034 | ||
1035 | /* Release memory. */ | |
1036 | XFREE(MTYPE_CONFD, values); | |
1037 | ||
1038 | /* Map ConfD return code to northbound return code. */ | |
1039 | switch (ret) { | |
1040 | case CONFD_OK: | |
1041 | return NB_OK; | |
1042 | default: | |
1043 | return NB_ERR; | |
1044 | } | |
1045 | } | |
1046 | ||
1047 | static int frr_confd_action_init(struct confd_user_info *uinfo) | |
1048 | { | |
1049 | confd_action_set_fd(uinfo, dp_worker_sock); | |
1050 | ||
1051 | return CONFD_OK; | |
1052 | } | |
1053 | ||
1054 | static int frr_confd_action_execute(struct confd_user_info *uinfo, | |
1055 | struct xml_tag *name, confd_hkeypath_t *kp, | |
1056 | confd_tag_value_t *params, int nparams) | |
1057 | { | |
78919336 | 1058 | char xpath[XPATH_MAXLEN]; |
5bce33b3 RW |
1059 | struct nb_node *nb_node; |
1060 | struct list *input; | |
1061 | struct list *output; | |
1062 | struct yang_data *data; | |
1063 | confd_tag_value_t *reply; | |
1064 | int ret = CONFD_OK; | |
f63f5f19 | 1065 | char errmsg[BUFSIZ] = {0}; |
5bce33b3 RW |
1066 | |
1067 | /* Getting the XPath is tricky. */ | |
1068 | if (kp) { | |
1069 | /* This is a YANG RPC. */ | |
1070 | frr_confd_get_xpath(kp, xpath, sizeof(xpath)); | |
1071 | strlcat(xpath, "/", sizeof(xpath)); | |
1072 | strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath)); | |
1073 | } else { | |
1074 | /* This is a YANG action. */ | |
1075 | snprintf(xpath, sizeof(xpath), "/%s:%s", | |
1076 | confd_ns2prefix(name->ns), confd_hash2str(name->tag)); | |
1077 | } | |
1078 | ||
1079 | nb_node = nb_node_find(xpath); | |
1080 | if (!nb_node) { | |
1081 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
1082 | "%s: unknown data path: %s", __func__, xpath); | |
1083 | return CONFD_ERR; | |
1084 | } | |
1085 | ||
1086 | input = yang_data_list_new(); | |
1087 | output = yang_data_list_new(); | |
1088 | ||
1089 | /* Process input nodes. */ | |
1090 | for (int i = 0; i < nparams; i++) { | |
78919336 | 1091 | char xpath_input[XPATH_MAXLEN * 2]; |
5bce33b3 RW |
1092 | char value_str[YANG_VALUE_MAXLEN]; |
1093 | ||
1094 | snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath, | |
1095 | confd_hash2str(params[i].tag.tag)); | |
1096 | ||
1097 | if (frr_confd_val2str(xpath_input, ¶ms[i].v, value_str, | |
1098 | sizeof(value_str)) | |
1099 | != 0) { | |
1100 | flog_err( | |
1101 | EC_LIB_CONFD_DATA_CONVERT, | |
1102 | "%s: failed to convert ConfD value to a string", | |
1103 | __func__); | |
1104 | ret = CONFD_ERR; | |
1105 | goto exit; | |
1106 | } | |
1107 | ||
1108 | data = yang_data_new(xpath_input, value_str); | |
1109 | listnode_add(input, data); | |
1110 | } | |
1111 | ||
1112 | /* Execute callback registered for this XPath. */ | |
f63f5f19 CS |
1113 | if (nb_callback_rpc(nb_node, xpath, input, output, errmsg, |
1114 | sizeof(errmsg)) | |
1115 | != NB_OK) { | |
5bce33b3 RW |
1116 | flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s", |
1117 | __func__, xpath); | |
1118 | ret = CONFD_ERR; | |
1119 | goto exit; | |
1120 | } | |
1121 | ||
1122 | /* Process output nodes. */ | |
1123 | if (listcount(output) > 0) { | |
1124 | struct listnode *node; | |
1125 | int i = 0; | |
1126 | ||
1127 | reply = XMALLOC(MTYPE_CONFD, | |
1128 | listcount(output) * sizeof(*reply)); | |
1129 | ||
1130 | for (ALL_LIST_ELEMENTS_RO(output, node, data)) { | |
80243aef | 1131 | struct nb_node *nb_node_output; |
5bce33b3 RW |
1132 | int hash; |
1133 | ||
80243aef RW |
1134 | nb_node_output = nb_node_find(data->xpath); |
1135 | if (!nb_node_output) { | |
1136 | flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH, | |
1137 | "%s: unknown data path: %s", __func__, | |
1138 | data->xpath); | |
1139 | goto exit; | |
1140 | } | |
1141 | ||
1142 | hash = confd_str2hash(nb_node_output->snode->name); | |
5bce33b3 RW |
1143 | CONFD_SET_TAG_STR(&reply[i++], hash, data->value); |
1144 | } | |
1145 | confd_action_reply_values(uinfo, reply, listcount(output)); | |
1146 | XFREE(MTYPE_CONFD, reply); | |
1147 | } | |
1148 | ||
1149 | exit: | |
1150 | /* Release memory. */ | |
1151 | list_delete(&input); | |
1152 | list_delete(&output); | |
1153 | ||
1154 | return ret; | |
1155 | } | |
1156 | ||
1157 | ||
7640e3c6 | 1158 | static int frr_confd_dp_read(struct confd_daemon_ctx *dctx, int fd) |
5bce33b3 | 1159 | { |
5bce33b3 RW |
1160 | int ret; |
1161 | ||
5bce33b3 RW |
1162 | ret = confd_fd_ready(dctx, fd); |
1163 | if (ret == CONFD_EOF) { | |
1164 | flog_err_confd("confd_fd_ready"); | |
a7d055e4 | 1165 | frr_confd_finish(); |
5bce33b3 RW |
1166 | return -1; |
1167 | } else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) { | |
1168 | flog_err_confd("confd_fd_ready"); | |
a7d055e4 | 1169 | frr_confd_finish(); |
5bce33b3 RW |
1170 | return -1; |
1171 | } | |
1172 | ||
1173 | return 0; | |
1174 | } | |
1175 | ||
e6685141 | 1176 | static void frr_confd_dp_ctl_read(struct event *thread) |
7640e3c6 | 1177 | { |
e16d030c DS |
1178 | struct confd_daemon_ctx *dctx = EVENT_ARG(thread); |
1179 | int fd = EVENT_FD(thread); | |
7640e3c6 | 1180 | |
907a2395 | 1181 | event_add_read(master, frr_confd_dp_ctl_read, dctx, fd, &t_dp_ctl); |
7640e3c6 IR |
1182 | |
1183 | frr_confd_dp_read(dctx, fd); | |
1184 | } | |
1185 | ||
e6685141 | 1186 | static void frr_confd_dp_worker_read(struct event *thread) |
7640e3c6 | 1187 | { |
e16d030c DS |
1188 | struct confd_daemon_ctx *dctx = EVENT_ARG(thread); |
1189 | int fd = EVENT_FD(thread); | |
7640e3c6 | 1190 | |
907a2395 DS |
1191 | event_add_read(master, frr_confd_dp_worker_read, dctx, fd, |
1192 | &t_dp_worker); | |
7640e3c6 IR |
1193 | |
1194 | frr_confd_dp_read(dctx, fd); | |
1195 | } | |
1196 | ||
3bb513c3 | 1197 | static int frr_confd_subscribe_state(const struct lysc_node *snode, void *arg) |
5bce33b3 RW |
1198 | { |
1199 | struct nb_node *nb_node = snode->priv; | |
e0ccfad2 | 1200 | struct confd_data_cbs *data_cbs = arg; |
5bce33b3 | 1201 | |
9bde0b25 | 1202 | if (!nb_node || !CHECK_FLAG(snode->flags, LYS_CONFIG_R)) |
e0ccfad2 | 1203 | return YANG_ITER_CONTINUE; |
5bce33b3 | 1204 | /* We only need to subscribe to the root of the state subtrees. */ |
db452508 | 1205 | if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R)) |
e0ccfad2 | 1206 | return YANG_ITER_CONTINUE; |
5bce33b3 | 1207 | |
9eb2c0a1 RW |
1208 | DEBUGD(&nb_dbg_client_confd, |
1209 | "%s: providing data to '%s' (callpoint %s)", __func__, | |
1210 | nb_node->xpath, snode->name); | |
5bce33b3 RW |
1211 | |
1212 | strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint)); | |
1213 | if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK) | |
1214 | flog_err_confd("confd_register_data_cb"); | |
e0ccfad2 RW |
1215 | |
1216 | return YANG_ITER_CONTINUE; | |
5bce33b3 RW |
1217 | } |
1218 | ||
1219 | static int frr_confd_init_dp(const char *program_name) | |
1220 | { | |
1221 | struct confd_trans_cbs trans_cbs; | |
1222 | struct confd_data_cbs data_cbs; | |
1223 | struct confd_notification_stream_cbs ncbs; | |
1224 | struct confd_action_cbs acbs; | |
1225 | ||
1226 | /* Initialize daemon context. */ | |
1227 | dctx = confd_init_daemon(program_name); | |
1228 | if (!dctx) { | |
1229 | flog_err_confd("confd_init_daemon"); | |
1230 | goto error; | |
1231 | } | |
1232 | ||
1233 | /* | |
1234 | * Inform we want to receive YANG values as raw strings, and that we | |
1235 | * want to provide only strings in the reply functions, regardless of | |
1236 | * the YANG type. | |
1237 | */ | |
1238 | confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY); | |
1239 | ||
1240 | /* Establish a control socket. */ | |
1241 | dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0); | |
1242 | if (dp_ctl_sock < 0) { | |
1243 | flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", | |
1244 | __func__, safe_strerror(errno)); | |
1245 | goto error; | |
1246 | } | |
1247 | ||
1248 | if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr, | |
1249 | sizeof(struct sockaddr_in)) | |
1250 | != CONFD_OK) { | |
1251 | flog_err_confd("confd_connect"); | |
1252 | goto error; | |
1253 | } | |
1254 | ||
1255 | /* | |
1256 | * Establish a worker socket (only one since this plugin runs on a | |
1257 | * single thread). | |
1258 | */ | |
1259 | dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0); | |
1260 | if (dp_worker_sock < 0) { | |
1261 | flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s", | |
1262 | __func__, safe_strerror(errno)); | |
1263 | goto error; | |
1264 | } | |
1265 | if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr, | |
1266 | sizeof(struct sockaddr_in)) | |
1267 | != CONFD_OK) { | |
1268 | flog_err_confd("confd_connect"); | |
1269 | goto error; | |
1270 | } | |
1271 | ||
1272 | /* Register transaction callback functions. */ | |
1273 | memset(&trans_cbs, 0, sizeof(trans_cbs)); | |
1274 | trans_cbs.init = frr_confd_transaction_init; | |
1275 | confd_register_trans_cb(dctx, &trans_cbs); | |
1276 | ||
1277 | /* Register our read/write callbacks. */ | |
1278 | memset(&data_cbs, 0, sizeof(data_cbs)); | |
1279 | data_cbs.get_elem = frr_confd_data_get_elem; | |
1280 | data_cbs.exists_optional = frr_confd_data_get_elem; | |
1281 | data_cbs.get_next = frr_confd_data_get_next; | |
1282 | data_cbs.get_object = frr_confd_data_get_object; | |
1283 | data_cbs.get_next_object = frr_confd_data_get_next_object; | |
1284 | ||
1285 | /* | |
1286 | * Iterate over all loaded YANG modules and subscribe to the paths | |
1287 | * referent to state data. | |
1288 | */ | |
8d869d37 | 1289 | yang_snodes_iterate(NULL, frr_confd_subscribe_state, 0, &data_cbs); |
5bce33b3 RW |
1290 | |
1291 | /* Register notification stream. */ | |
1292 | memset(&ncbs, 0, sizeof(ncbs)); | |
1293 | ncbs.fd = dp_worker_sock; | |
1294 | /* | |
1295 | * RFC 5277 - Section 3.2.3: | |
1296 | * A NETCONF server implementation supporting the notification | |
1297 | * capability MUST support the "NETCONF" notification event | |
1298 | * stream. This stream contains all NETCONF XML event notifications | |
1299 | * supported by the NETCONF server. | |
1300 | */ | |
1301 | strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname)); | |
1302 | if (confd_register_notification_stream(dctx, &ncbs, &live_ctx) | |
1303 | != CONFD_OK) { | |
1304 | flog_err_confd("confd_register_notification_stream"); | |
1305 | goto error; | |
1306 | } | |
1307 | ||
1308 | /* Register the action handler callback. */ | |
1309 | memset(&acbs, 0, sizeof(acbs)); | |
1310 | strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint)); | |
1311 | acbs.init = frr_confd_action_init; | |
1312 | acbs.action = frr_confd_action_execute; | |
1313 | if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) { | |
1314 | flog_err_confd("confd_register_action_cbs"); | |
1315 | goto error; | |
1316 | } | |
1317 | ||
1318 | /* Notify we registered all callbacks we wanted. */ | |
1319 | if (confd_register_done(dctx) != CONFD_OK) { | |
1320 | flog_err_confd("confd_register_done"); | |
1321 | goto error; | |
1322 | } | |
1323 | ||
907a2395 DS |
1324 | event_add_read(master, frr_confd_dp_ctl_read, dctx, dp_ctl_sock, |
1325 | &t_dp_ctl); | |
1326 | event_add_read(master, frr_confd_dp_worker_read, dctx, dp_worker_sock, | |
1327 | &t_dp_worker); | |
5bce33b3 RW |
1328 | |
1329 | return 0; | |
1330 | ||
1331 | error: | |
1332 | frr_confd_finish_dp(); | |
1333 | ||
1334 | return -1; | |
1335 | } | |
1336 | ||
1337 | static void frr_confd_finish_dp(void) | |
1338 | { | |
1339 | if (dp_worker_sock > 0) { | |
e16d030c | 1340 | EVENT_OFF(t_dp_worker); |
5bce33b3 RW |
1341 | close(dp_worker_sock); |
1342 | } | |
1343 | if (dp_ctl_sock > 0) { | |
e16d030c | 1344 | EVENT_OFF(t_dp_ctl); |
5bce33b3 RW |
1345 | close(dp_ctl_sock); |
1346 | } | |
1347 | if (dctx != NULL) | |
1348 | confd_release_daemon(dctx); | |
1349 | } | |
1350 | ||
9eb2c0a1 RW |
1351 | /* ------------ CLI ------------ */ |
1352 | ||
1353 | DEFUN (debug_nb_confd, | |
1354 | debug_nb_confd_cmd, | |
1355 | "[no] debug northbound client confd", | |
1356 | NO_STR | |
1357 | DEBUG_STR | |
1358 | "Northbound debugging\n" | |
1359 | "Client\n" | |
1360 | "ConfD\n") | |
1361 | { | |
1362 | uint32_t mode = DEBUG_NODE2MODE(vty->node); | |
1363 | bool no = strmatch(argv[0]->text, "no"); | |
1364 | ||
1365 | DEBUG_MODE_SET(&nb_dbg_client_confd, mode, !no); | |
1366 | ||
1367 | return CMD_SUCCESS; | |
1368 | } | |
1369 | ||
1370 | static int frr_confd_debug_config_write(struct vty *vty) | |
1371 | { | |
1372 | if (DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_CONF)) | |
1373 | vty_out(vty, "debug northbound client confd\n"); | |
1374 | ||
1375 | return 0; | |
1376 | } | |
1377 | ||
1378 | static int frr_confd_debug_set_all(uint32_t flags, bool set) | |
1379 | { | |
1380 | DEBUG_FLAGS_SET(&nb_dbg_client_confd, flags, set); | |
1381 | ||
1382 | /* If all modes have been turned off, don't preserve options. */ | |
1383 | if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_ALL)) | |
1384 | DEBUG_CLEAR(&nb_dbg_client_confd); | |
1385 | ||
1386 | return 0; | |
1387 | } | |
1388 | ||
1389 | static void frr_confd_cli_init(void) | |
1390 | { | |
1391 | hook_register(nb_client_debug_config_write, | |
1392 | frr_confd_debug_config_write); | |
1393 | hook_register(nb_client_debug_set_all, frr_confd_debug_set_all); | |
1394 | ||
1395 | install_element(ENABLE_NODE, &debug_nb_confd_cmd); | |
1396 | install_element(CONFIG_NODE, &debug_nb_confd_cmd); | |
1397 | } | |
1398 | ||
5bce33b3 RW |
1399 | /* ------------ Main ------------ */ |
1400 | ||
3bb513c3 | 1401 | static int frr_confd_calculate_snode_hash(const struct lysc_node *snode, |
e0ccfad2 | 1402 | void *arg) |
5bce33b3 RW |
1403 | { |
1404 | struct nb_node *nb_node = snode->priv; | |
1405 | ||
9bde0b25 RW |
1406 | if (nb_node) |
1407 | nb_node->confd_hash = confd_str2hash(snode->name); | |
e0ccfad2 RW |
1408 | |
1409 | return YANG_ITER_CONTINUE; | |
5bce33b3 RW |
1410 | } |
1411 | ||
1412 | static int frr_confd_init(const char *program_name) | |
1413 | { | |
1414 | struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr; | |
1415 | int debuglevel = CONFD_SILENT; | |
1416 | int ret = -1; | |
1417 | ||
1418 | /* Initialize ConfD library. */ | |
1419 | confd_init(program_name, stderr, debuglevel); | |
1420 | ||
1421 | confd_addr4->sin_family = AF_INET; | |
1422 | confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1"); | |
1423 | confd_addr4->sin_port = htons(CONFD_PORT); | |
1424 | if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in)) | |
1425 | != CONFD_OK) { | |
1426 | flog_err_confd("confd_load_schemas"); | |
1427 | return -1; | |
1428 | } | |
1429 | ||
1430 | ret = frr_confd_init_cdb(); | |
1431 | if (ret != 0) | |
1432 | goto error; | |
1433 | ||
1434 | ret = frr_confd_init_dp(program_name); | |
1435 | if (ret != 0) { | |
1436 | frr_confd_finish_cdb(); | |
1437 | goto error; | |
1438 | } | |
1439 | ||
8d869d37 | 1440 | yang_snodes_iterate(NULL, frr_confd_calculate_snode_hash, 0, NULL); |
5bce33b3 RW |
1441 | |
1442 | hook_register(nb_notification_send, frr_confd_notification_send); | |
1443 | ||
1444 | confd_connected = true; | |
1445 | return 0; | |
1446 | ||
1447 | error: | |
1448 | confd_free_schemas(); | |
1449 | ||
1450 | return ret; | |
1451 | } | |
1452 | ||
1453 | static int frr_confd_finish(void) | |
1454 | { | |
d8729f8c | 1455 | if (!confd_connected) |
5bce33b3 RW |
1456 | return 0; |
1457 | ||
1458 | frr_confd_finish_cdb(); | |
1459 | frr_confd_finish_dp(); | |
1460 | ||
1461 | confd_free_schemas(); | |
1462 | ||
1463 | confd_connected = false; | |
1464 | ||
1465 | return 0; | |
1466 | } | |
1467 | ||
cd9d0537 | 1468 | static int frr_confd_module_late_init(struct event_loop *tm) |
5bce33b3 RW |
1469 | { |
1470 | master = tm; | |
1471 | ||
1472 | if (frr_confd_init(frr_get_progname()) < 0) { | |
1473 | flog_err(EC_LIB_CONFD_INIT, | |
1474 | "failed to initialize the ConfD module"); | |
1475 | return -1; | |
1476 | } | |
1477 | ||
1478 | hook_register(frr_fini, frr_confd_finish); | |
9eb2c0a1 | 1479 | frr_confd_cli_init(); |
5bce33b3 RW |
1480 | |
1481 | return 0; | |
1482 | } | |
1483 | ||
1484 | static int frr_confd_module_init(void) | |
1485 | { | |
1486 | hook_register(frr_late_init, frr_confd_module_late_init); | |
1487 | ||
1488 | return 0; | |
1489 | } | |
1490 | ||
1491 | FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION, | |
1492 | .description = "FRR ConfD integration module", | |
80413c20 DL |
1493 | .init = frr_confd_module_init, |
1494 | ); |