mirror of
				https://git.proxmox.com/git/mirror_frr
				synced 2025-11-04 06:38:04 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			1495 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1495 lines
		
	
	
		
			37 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
						|
/*
 | 
						|
 * Copyright (C) 2018  NetDEF, Inc.
 | 
						|
 *                     Renato Westphal
 | 
						|
 */
 | 
						|
 | 
						|
#include <zebra.h>
 | 
						|
 | 
						|
#include "log.h"
 | 
						|
#include "lib_errors.h"
 | 
						|
#include "command.h"
 | 
						|
#include "debug.h"
 | 
						|
#include "libfrr.h"
 | 
						|
#include "lib/version.h"
 | 
						|
#include "northbound.h"
 | 
						|
 | 
						|
#include <confd_lib.h>
 | 
						|
#include <confd_cdb.h>
 | 
						|
#include <confd_dp.h>
 | 
						|
#include <confd_maapi.h>
 | 
						|
 | 
						|
DEFINE_MTYPE_STATIC(LIB, CONFD, "ConfD module");
 | 
						|
 | 
						|
static struct debug nb_dbg_client_confd = {0, "Northbound client: ConfD"};
 | 
						|
 | 
						|
static struct event_loop *master;
 | 
						|
static struct sockaddr confd_addr;
 | 
						|
static int cdb_sub_sock, dp_ctl_sock, dp_worker_sock;
 | 
						|
static struct event *t_cdb_sub, *t_dp_ctl, *t_dp_worker;
 | 
						|
static struct confd_daemon_ctx *dctx;
 | 
						|
static struct confd_notification_ctx *live_ctx;
 | 
						|
static bool confd_connected;
 | 
						|
static struct list *confd_spoints;
 | 
						|
static struct nb_transaction *transaction;
 | 
						|
 | 
						|
static void frr_confd_finish_cdb(void);
 | 
						|
static void frr_confd_finish_dp(void);
 | 
						|
static int frr_confd_finish(void);
 | 
						|
 | 
						|
#define flog_err_confd(funcname)                                               \
 | 
						|
	flog_err(EC_LIB_LIBCONFD, "%s: %s() failed: %s (%d): %s", __func__,    \
 | 
						|
		 (funcname), confd_strerror(confd_errno), confd_errno,         \
 | 
						|
		 confd_lasterr())
 | 
						|
 | 
						|
 | 
						|
/* ------------ Utils ------------ */
 | 
						|
 | 
						|
/* Get XPath string from ConfD hashed keypath. */
 | 
						|
static void frr_confd_get_xpath(const confd_hkeypath_t *kp, char *xpath,
 | 
						|
				size_t len)
 | 
						|
{
 | 
						|
	char *p;
 | 
						|
 | 
						|
	confd_xpath_pp_kpath(xpath, len, 0, kp);
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Replace double quotes by single quotes (the format accepted by the
 | 
						|
	 * northbound API).
 | 
						|
	 */
 | 
						|
	p = xpath;
 | 
						|
	while ((p = strchr(p, '"')) != NULL)
 | 
						|
		*p++ = '\'';
 | 
						|
}
 | 
						|
 | 
						|
/* Convert ConfD binary value to a string. */
 | 
						|
static int frr_confd_val2str(const char *xpath, const confd_value_t *value,
 | 
						|
			     char *string, size_t string_size)
 | 
						|
{
 | 
						|
	struct confd_cs_node *csp;
 | 
						|
 | 
						|
	csp = confd_cs_node_cd(NULL, xpath);
 | 
						|
	if (!csp) {
 | 
						|
		flog_err_confd("confd_cs_node_cd");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	if (confd_val2str(csp->info.type, value, string, string_size)
 | 
						|
	    == CONFD_ERR) {
 | 
						|
		flog_err_confd("confd_val2str");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Obtain list entry from ConfD hashed keypath. */
 | 
						|
static int frr_confd_hkeypath_get_list_entry(const confd_hkeypath_t *kp,
 | 
						|
					     struct nb_node *nb_node,
 | 
						|
					     const void **list_entry)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node_list;
 | 
						|
	int parent_lists = 0;
 | 
						|
	int curr_list = 0;
 | 
						|
 | 
						|
	*list_entry = NULL;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Count the number of YANG lists in the path, disconsidering the
 | 
						|
	 * last element.
 | 
						|
	 */
 | 
						|
	nb_node_list = nb_node;
 | 
						|
	while (nb_node_list->parent_list) {
 | 
						|
		nb_node_list = nb_node_list->parent_list;
 | 
						|
		parent_lists++;
 | 
						|
	}
 | 
						|
	if (nb_node->snode->nodetype != LYS_LIST && parent_lists == 0)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	/* Start from the beginning and move down the tree. */
 | 
						|
	for (int i = kp->len; i >= 0; i--) {
 | 
						|
		struct yang_list_keys keys;
 | 
						|
 | 
						|
		/* Not a YANG list. */
 | 
						|
		if (kp->v[i][0].type != C_BUF)
 | 
						|
			continue;
 | 
						|
 | 
						|
		/* Obtain list keys. */
 | 
						|
		memset(&keys, 0, sizeof(keys));
 | 
						|
		for (int j = 0; kp->v[i][j].type != C_NOEXISTS; j++) {
 | 
						|
			strlcpy(keys.key[keys.num],
 | 
						|
				(char *)kp->v[i][j].val.buf.ptr,
 | 
						|
				sizeof(keys.key[keys.num]));
 | 
						|
			keys.num++;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Obtain northbound node associated to the YANG list. */
 | 
						|
		nb_node_list = nb_node;
 | 
						|
		for (int j = curr_list; j < parent_lists; j++)
 | 
						|
			nb_node_list = nb_node_list->parent_list;
 | 
						|
 | 
						|
		/* Obtain list entry. */
 | 
						|
		if (!CHECK_FLAG(nb_node_list->flags, F_NB_NODE_KEYLESS_LIST)) {
 | 
						|
			*list_entry = nb_callback_lookup_entry(
 | 
						|
				nb_node, *list_entry, &keys);
 | 
						|
			if (*list_entry == NULL)
 | 
						|
				return -1;
 | 
						|
		} else {
 | 
						|
			unsigned long ptr_ulong;
 | 
						|
 | 
						|
			/* Retrieve list entry from pseudo-key (string). */
 | 
						|
			if (sscanf(keys.key[0], "%lu", &ptr_ulong) != 1)
 | 
						|
				return -1;
 | 
						|
			*list_entry = (const void *)ptr_ulong;
 | 
						|
		}
 | 
						|
 | 
						|
		curr_list++;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Fill the current date and time into a confd_datetime structure. */
 | 
						|
static void getdatetime(struct confd_datetime *datetime)
 | 
						|
{
 | 
						|
	struct tm tm;
 | 
						|
	struct timeval tv;
 | 
						|
 | 
						|
	gettimeofday(&tv, NULL);
 | 
						|
	gmtime_r(&tv.tv_sec, &tm);
 | 
						|
 | 
						|
	memset(datetime, 0, sizeof(*datetime));
 | 
						|
	datetime->year = 1900 + tm.tm_year;
 | 
						|
	datetime->month = tm.tm_mon + 1;
 | 
						|
	datetime->day = tm.tm_mday;
 | 
						|
	datetime->sec = tm.tm_sec;
 | 
						|
	datetime->micro = tv.tv_usec;
 | 
						|
	datetime->timezone = 0;
 | 
						|
	datetime->timezone_minutes = 0;
 | 
						|
	datetime->hour = tm.tm_hour;
 | 
						|
	datetime->min = tm.tm_min;
 | 
						|
}
 | 
						|
 | 
						|
/* ------------ CDB code ------------ */
 | 
						|
 | 
						|
struct cdb_iter_args {
 | 
						|
	struct nb_config *candidate;
 | 
						|
	bool error;
 | 
						|
};
 | 
						|
 | 
						|
static enum cdb_iter_ret
 | 
						|
frr_confd_cdb_diff_iter(confd_hkeypath_t *kp, enum cdb_iter_op cdb_op,
 | 
						|
			confd_value_t *oldv, confd_value_t *newv, void *args)
 | 
						|
{
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	enum nb_operation nb_op;
 | 
						|
	struct cdb_iter_args *iter_args = args;
 | 
						|
	char value_str[YANG_VALUE_MAXLEN];
 | 
						|
	struct yang_data *data;
 | 
						|
	char *sb1, *sb2;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * HACK: obtain value of leaf-list elements from the XPath due to
 | 
						|
	 * a bug in the ConfD API.
 | 
						|
	 */
 | 
						|
	value_str[0] = '\0';
 | 
						|
	sb1 = strrchr(xpath, '[');
 | 
						|
	sb2 = strrchr(xpath, ']');
 | 
						|
	if (sb1 && sb2 && !strchr(sb1, '=')) {
 | 
						|
		*sb2 = '\0';
 | 
						|
		strlcpy(value_str, sb1 + 1, sizeof(value_str));
 | 
						|
		*sb1 = '\0';
 | 
						|
	}
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		iter_args->error = true;
 | 
						|
		return ITER_STOP;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Map operation values. */
 | 
						|
	switch (cdb_op) {
 | 
						|
	case MOP_CREATED:
 | 
						|
		nb_op = NB_OP_CREATE;
 | 
						|
		break;
 | 
						|
	case MOP_DELETED:
 | 
						|
		nb_op = NB_OP_DESTROY;
 | 
						|
		break;
 | 
						|
	case MOP_VALUE_SET:
 | 
						|
		if (nb_operation_is_valid(NB_OP_MODIFY, nb_node->snode))
 | 
						|
			nb_op = NB_OP_MODIFY;
 | 
						|
		else
 | 
						|
			/* Ignore list keys modifications. */
 | 
						|
			return ITER_RECURSE;
 | 
						|
		break;
 | 
						|
	case MOP_MOVED_AFTER:
 | 
						|
		nb_op = NB_OP_MOVE;
 | 
						|
		break;
 | 
						|
	case MOP_MODIFIED:
 | 
						|
		/* We're not interested on this. */
 | 
						|
		return ITER_RECURSE;
 | 
						|
	default:
 | 
						|
		flog_err(EC_LIB_DEVELOPMENT,
 | 
						|
			 "%s: unexpected operation %u [xpath %s]", __func__,
 | 
						|
			 cdb_op, xpath);
 | 
						|
		iter_args->error = true;
 | 
						|
		return ITER_STOP;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Convert ConfD value to a string. */
 | 
						|
	if (nb_node->snode->nodetype != LYS_LEAFLIST && newv
 | 
						|
	    && frr_confd_val2str(nb_node->xpath, newv, value_str,
 | 
						|
				 sizeof(value_str))
 | 
						|
		       != 0) {
 | 
						|
		flog_err(EC_LIB_CONFD_DATA_CONVERT,
 | 
						|
			 "%s: failed to convert ConfD value to a string",
 | 
						|
			 __func__);
 | 
						|
		iter_args->error = true;
 | 
						|
		return ITER_STOP;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Edit the candidate configuration. */
 | 
						|
	data = yang_data_new(xpath, value_str);
 | 
						|
	ret = nb_candidate_edit(iter_args->candidate, nb_node, nb_op, xpath,
 | 
						|
				NULL, data);
 | 
						|
	yang_data_free(data);
 | 
						|
	if (ret != NB_OK) {
 | 
						|
		flog_warn(
 | 
						|
			EC_LIB_NB_CANDIDATE_EDIT_ERROR,
 | 
						|
			"%s: failed to edit candidate configuration: operation [%s] xpath [%s]",
 | 
						|
			__func__, nb_operation_name(nb_op), xpath);
 | 
						|
		iter_args->error = true;
 | 
						|
		return ITER_STOP;
 | 
						|
	}
 | 
						|
 | 
						|
	return ITER_RECURSE;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_cdb_read_cb_prepare(int fd, int *subp, int reslen)
 | 
						|
{
 | 
						|
	struct nb_context context = {};
 | 
						|
	struct nb_config *candidate;
 | 
						|
	struct cdb_iter_args iter_args;
 | 
						|
	char errmsg[BUFSIZ] = {0};
 | 
						|
	int ret;
 | 
						|
 | 
						|
	candidate = nb_config_dup(running_config);
 | 
						|
 | 
						|
	/* Iterate over all configuration changes. */
 | 
						|
	iter_args.candidate = candidate;
 | 
						|
	iter_args.error = false;
 | 
						|
	for (int i = 0; i < reslen; i++) {
 | 
						|
		if (cdb_diff_iterate(fd, subp[i], frr_confd_cdb_diff_iter,
 | 
						|
				     ITER_WANT_PREV, &iter_args)
 | 
						|
		    != CONFD_OK) {
 | 
						|
			flog_err_confd("cdb_diff_iterate");
 | 
						|
		}
 | 
						|
	}
 | 
						|
	free(subp);
 | 
						|
 | 
						|
	if (iter_args.error) {
 | 
						|
		nb_config_free(candidate);
 | 
						|
 | 
						|
		if (cdb_sub_abort_trans(
 | 
						|
			    cdb_sub_sock, CONFD_ERRCODE_APPLICATION_INTERNAL, 0,
 | 
						|
			    0, "Couldn't apply configuration changes")
 | 
						|
		    != CONFD_OK) {
 | 
						|
			flog_err_confd("cdb_sub_abort_trans");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Validate the configuration changes and allocate all resources
 | 
						|
	 * required to apply them.
 | 
						|
	 */
 | 
						|
	transaction = NULL;
 | 
						|
	context.client = NB_CLIENT_CONFD;
 | 
						|
	ret = nb_candidate_commit_prepare(context, candidate, NULL,
 | 
						|
					  &transaction, false, false, errmsg,
 | 
						|
					  sizeof(errmsg));
 | 
						|
	if (ret != NB_OK && ret != NB_ERR_NO_CHANGES) {
 | 
						|
		enum confd_errcode errcode;
 | 
						|
 | 
						|
		switch (ret) {
 | 
						|
		case NB_ERR_LOCKED:
 | 
						|
			errcode = CONFD_ERRCODE_IN_USE;
 | 
						|
			break;
 | 
						|
		case NB_ERR_RESOURCE:
 | 
						|
			errcode = CONFD_ERRCODE_RESOURCE_DENIED;
 | 
						|
			break;
 | 
						|
		default:
 | 
						|
			errcode = CONFD_ERRCODE_APPLICATION;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
 | 
						|
		/* Reject the configuration changes. */
 | 
						|
		if (cdb_sub_abort_trans(cdb_sub_sock, errcode, 0, 0, "%s",
 | 
						|
					errmsg)
 | 
						|
		    != CONFD_OK) {
 | 
						|
			flog_err_confd("cdb_sub_abort_trans");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		/* Acknowledge the notification. */
 | 
						|
		if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY)
 | 
						|
		    != CONFD_OK) {
 | 
						|
			flog_err_confd("cdb_sync_subscription_socket");
 | 
						|
			return;
 | 
						|
		}
 | 
						|
 | 
						|
		/* No configuration changes. */
 | 
						|
		if (!transaction)
 | 
						|
			nb_config_free(candidate);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_cdb_read_cb_commit(int fd, int *subp, int reslen)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * No need to process the configuration changes again as we're already
 | 
						|
	 * keeping track of them in the "transaction" variable.
 | 
						|
	 */
 | 
						|
	free(subp);
 | 
						|
 | 
						|
	/* Apply the transaction. */
 | 
						|
	if (transaction) {
 | 
						|
		struct nb_config *candidate = transaction->config;
 | 
						|
		char errmsg[BUFSIZ] = {0};
 | 
						|
 | 
						|
		nb_candidate_commit_apply(transaction, true, NULL, errmsg,
 | 
						|
					  sizeof(errmsg));
 | 
						|
		nb_config_free(candidate);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Acknowledge the notification. */
 | 
						|
	if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_sync_subscription_socket");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_cdb_read_cb_abort(int fd, int *subp, int reslen)
 | 
						|
{
 | 
						|
	/*
 | 
						|
	 * No need to process the configuration changes again as we're already
 | 
						|
	 * keeping track of them in the "transaction" variable.
 | 
						|
	 */
 | 
						|
	free(subp);
 | 
						|
 | 
						|
	/* Abort the transaction. */
 | 
						|
	if (transaction) {
 | 
						|
		struct nb_config *candidate = transaction->config;
 | 
						|
		char errmsg[BUFSIZ] = {0};
 | 
						|
 | 
						|
		nb_candidate_commit_abort(transaction, errmsg, sizeof(errmsg));
 | 
						|
		nb_config_free(candidate);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Acknowledge the notification. */
 | 
						|
	if (cdb_sync_subscription_socket(fd, CDB_DONE_PRIORITY) != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_sync_subscription_socket");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_cdb_read_cb(struct event *thread)
 | 
						|
{
 | 
						|
	int fd = EVENT_FD(thread);
 | 
						|
	enum cdb_sub_notification cdb_ev;
 | 
						|
	int flags;
 | 
						|
	int *subp = NULL;
 | 
						|
	int reslen = 0;
 | 
						|
 | 
						|
	event_add_read(master, frr_confd_cdb_read_cb, NULL, fd, &t_cdb_sub);
 | 
						|
 | 
						|
	if (cdb_read_subscription_socket2(fd, &cdb_ev, &flags, &subp, &reslen)
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_read_subscription_socket2");
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (cdb_ev) {
 | 
						|
	case CDB_SUB_PREPARE:
 | 
						|
		frr_confd_cdb_read_cb_prepare(fd, subp, reslen);
 | 
						|
		break;
 | 
						|
	case CDB_SUB_COMMIT:
 | 
						|
		frr_confd_cdb_read_cb_commit(fd, subp, reslen);
 | 
						|
		break;
 | 
						|
	case CDB_SUB_ABORT:
 | 
						|
		frr_confd_cdb_read_cb_abort(fd, subp, reslen);
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		flog_err_confd("unknown CDB event");
 | 
						|
		break;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* Trigger CDB subscriptions to read the startup configuration. */
 | 
						|
static void *thread_cdb_trigger_subscriptions(void *data)
 | 
						|
{
 | 
						|
	int sock;
 | 
						|
	int *sub_points = NULL, len = 0;
 | 
						|
	struct listnode *node;
 | 
						|
	int *spoint;
 | 
						|
	int i = 0;
 | 
						|
 | 
						|
	/* Create CDB data socket. */
 | 
						|
	sock = socket(PF_INET, SOCK_STREAM, 0);
 | 
						|
	if (sock < 0) {
 | 
						|
		flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
 | 
						|
			 __func__, safe_strerror(errno));
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (cdb_connect(sock, CDB_DATA_SOCKET, &confd_addr,
 | 
						|
			sizeof(struct sockaddr_in))
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_connect");
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Fill array containing the subscription point of all loaded YANG
 | 
						|
	 * modules.
 | 
						|
	 */
 | 
						|
	len = listcount(confd_spoints);
 | 
						|
	sub_points = XCALLOC(MTYPE_CONFD, len * sizeof(int));
 | 
						|
	for (ALL_LIST_ELEMENTS_RO(confd_spoints, node, spoint))
 | 
						|
		sub_points[i++] = *spoint;
 | 
						|
 | 
						|
	if (cdb_trigger_subscriptions(sock, sub_points, len) != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_trigger_subscriptions");
 | 
						|
		return NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Cleanup and exit thread. */
 | 
						|
	XFREE(MTYPE_CONFD, sub_points);
 | 
						|
	cdb_close(sock);
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_subscribe(const struct lysc_node *snode, void *arg)
 | 
						|
{
 | 
						|
	struct yang_module *module = arg;
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	int *spoint;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	switch (snode->nodetype) {
 | 
						|
	case LYS_CONTAINER:
 | 
						|
	case LYS_LEAF:
 | 
						|
	case LYS_LEAFLIST:
 | 
						|
	case LYS_LIST:
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	if (CHECK_FLAG(snode->flags, LYS_CONFIG_R))
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
 | 
						|
	nb_node = snode->priv;
 | 
						|
	if (!nb_node)
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
 | 
						|
	DEBUGD(&nb_dbg_client_confd, "%s: subscribing to '%s'", __func__,
 | 
						|
	       nb_node->xpath);
 | 
						|
 | 
						|
	spoint = XMALLOC(MTYPE_CONFD, sizeof(*spoint));
 | 
						|
	ret = cdb_subscribe2(cdb_sub_sock, CDB_SUB_RUNNING_TWOPHASE,
 | 
						|
			     CDB_SUB_WANT_ABORT_ON_ABORT, 3, spoint,
 | 
						|
			     module->confd_hash, nb_node->xpath);
 | 
						|
	if (ret != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_subscribe2");
 | 
						|
		XFREE(MTYPE_CONFD, spoint);
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
	}
 | 
						|
 | 
						|
	listnode_add(confd_spoints, spoint);
 | 
						|
	return YANG_ITER_CONTINUE;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_init_cdb(void)
 | 
						|
{
 | 
						|
	struct yang_module *module;
 | 
						|
	pthread_t cdb_trigger_thread;
 | 
						|
 | 
						|
	/* Create CDB subscription socket. */
 | 
						|
	cdb_sub_sock = socket(PF_INET, SOCK_STREAM, 0);
 | 
						|
	if (cdb_sub_sock < 0) {
 | 
						|
		flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
 | 
						|
			 __func__, safe_strerror(errno));
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	if (cdb_connect(cdb_sub_sock, CDB_SUBSCRIPTION_SOCKET, &confd_addr,
 | 
						|
			sizeof(struct sockaddr_in))
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_connect");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Subscribe to all loaded YANG data modules. */
 | 
						|
	confd_spoints = list_new();
 | 
						|
	RB_FOREACH (module, yang_modules, &yang_modules) {
 | 
						|
		module->confd_hash = confd_str2hash(module->info->ns);
 | 
						|
		if (module->confd_hash == 0) {
 | 
						|
			flog_err(
 | 
						|
				EC_LIB_LIBCONFD,
 | 
						|
				"%s: failed to find hash value for namespace %s",
 | 
						|
				__func__, module->info->ns);
 | 
						|
			goto error;
 | 
						|
		}
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The CDB API doesn't provide a mechanism to subscribe to an
 | 
						|
		 * entire YANG module. So we have to find the top level
 | 
						|
		 * nodes ourselves and subscribe to their paths.
 | 
						|
		 */
 | 
						|
		yang_snodes_iterate(module->info, frr_confd_subscribe, 0,
 | 
						|
				    module);
 | 
						|
	}
 | 
						|
 | 
						|
	if (cdb_subscribe_done(cdb_sub_sock) != CONFD_OK) {
 | 
						|
		flog_err_confd("cdb_subscribe_done");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Create short lived pthread to trigger the CDB subscriptions. */
 | 
						|
	if (pthread_create(&cdb_trigger_thread, NULL,
 | 
						|
			   thread_cdb_trigger_subscriptions, NULL)) {
 | 
						|
		flog_err(EC_LIB_SYSTEM_CALL, "%s: error creating pthread: %s",
 | 
						|
			 __func__, safe_strerror(errno));
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
	pthread_detach(cdb_trigger_thread);
 | 
						|
 | 
						|
	event_add_read(master, frr_confd_cdb_read_cb, NULL, cdb_sub_sock,
 | 
						|
		       &t_cdb_sub);
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
error:
 | 
						|
	frr_confd_finish_cdb();
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_finish_cdb(void)
 | 
						|
{
 | 
						|
	if (cdb_sub_sock > 0) {
 | 
						|
		EVENT_OFF(t_cdb_sub);
 | 
						|
		cdb_close(cdb_sub_sock);
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/* ------------ DP code ------------ */
 | 
						|
 | 
						|
static int frr_confd_transaction_init(struct confd_trans_ctx *tctx)
 | 
						|
{
 | 
						|
	confd_trans_set_fd(tctx, dp_worker_sock);
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
#define CONFD_MAX_CHILD_NODES 32
 | 
						|
 | 
						|
static int frr_confd_data_get_elem(struct confd_trans_ctx *tctx,
 | 
						|
				   confd_hkeypath_t *kp)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	struct yang_data *data;
 | 
						|
	confd_value_t v;
 | 
						|
	const void *list_entry = NULL;
 | 
						|
 | 
						|
	frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		confd_data_reply_not_found(tctx);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
 | 
						|
		confd_data_reply_not_found(tctx);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	data = nb_callback_get_elem(nb_node, xpath, list_entry);
 | 
						|
	if (data) {
 | 
						|
		if (data->value) {
 | 
						|
			CONFD_SET_STR(&v, data->value);
 | 
						|
			confd_data_reply_value(tctx, &v);
 | 
						|
		} else
 | 
						|
			confd_data_reply_found(tctx);
 | 
						|
		yang_data_free(data);
 | 
						|
	} else
 | 
						|
		confd_data_reply_not_found(tctx);
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_data_get_next(struct confd_trans_ctx *tctx,
 | 
						|
				   confd_hkeypath_t *kp, long next)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	struct yang_data *data;
 | 
						|
	const void *parent_list_entry, *nb_next;
 | 
						|
	confd_value_t v[LIST_MAXKEYS];
 | 
						|
 | 
						|
	frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		confd_data_reply_next_key(tctx, NULL, -1, -1);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
 | 
						|
	    != 0) {
 | 
						|
		/* List entry doesn't exist anymore. */
 | 
						|
		confd_data_reply_next_key(tctx, NULL, -1, -1);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	nb_next = nb_callback_get_next(nb_node, parent_list_entry,
 | 
						|
				       (next == -1) ? NULL : (void *)next);
 | 
						|
	if (!nb_next) {
 | 
						|
		/* End of the list or leaf-list. */
 | 
						|
		confd_data_reply_next_key(tctx, NULL, -1, -1);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	switch (nb_node->snode->nodetype) {
 | 
						|
	case LYS_LIST:
 | 
						|
		if (!CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
 | 
						|
			struct yang_list_keys keys;
 | 
						|
 | 
						|
			memset(&keys, 0, sizeof(keys));
 | 
						|
			if (nb_callback_get_keys(nb_node, nb_next, &keys)
 | 
						|
			    != NB_OK) {
 | 
						|
				flog_warn(EC_LIB_NB_CB_STATE,
 | 
						|
					  "%s: failed to get list keys",
 | 
						|
					  __func__);
 | 
						|
				confd_data_reply_next_key(tctx, NULL, -1, -1);
 | 
						|
				return CONFD_OK;
 | 
						|
			}
 | 
						|
 | 
						|
			/* Feed keys to ConfD. */
 | 
						|
			for (size_t i = 0; i < keys.num; i++)
 | 
						|
				CONFD_SET_STR(&v[i], keys.key[i]);
 | 
						|
			confd_data_reply_next_key(tctx, v, keys.num,
 | 
						|
						  (long)nb_next);
 | 
						|
		} else {
 | 
						|
			char pointer_str[32];
 | 
						|
 | 
						|
			/*
 | 
						|
			 * ConfD 6.6 user guide, chapter 6.11 (Operational data
 | 
						|
			 * lists without keys):
 | 
						|
			 * "To support this without having completely separate
 | 
						|
			 * APIs, we use a "pseudo" key in the ConfD APIs for
 | 
						|
			 * this type of list. This key is not part of the data
 | 
						|
			 * model, and completely hidden in the northbound agent
 | 
						|
			 * interfaces, but is used with e.g. the get_next() and
 | 
						|
			 * get_elem() callbacks as if it were a normal key. This
 | 
						|
			 * "pseudo" key is always a single signed 64-bit
 | 
						|
			 * integer, i.e. the confd_value_t type is C_INT64. The
 | 
						|
			 * values can be chosen arbitrarily by the application,
 | 
						|
			 * as long as a key value returned by get_next() can be
 | 
						|
			 * used to get the data for the corresponding list entry
 | 
						|
			 * with get_elem() or get_object() as usual. It could
 | 
						|
			 * e.g. be an index into an array that holds the data,
 | 
						|
			 * or even a memory address in integer form".
 | 
						|
			 *
 | 
						|
			 * Since we're using the CONFD_DAEMON_FLAG_STRINGSONLY
 | 
						|
			 * option, we must convert our pseudo-key (a void
 | 
						|
			 * pointer) to a string before sending it to confd.
 | 
						|
			 */
 | 
						|
			snprintf(pointer_str, sizeof(pointer_str), "%lu",
 | 
						|
				 (unsigned long)nb_next);
 | 
						|
			CONFD_SET_STR(&v[0], pointer_str);
 | 
						|
			confd_data_reply_next_key(tctx, v, 1, (long)nb_next);
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	case LYS_LEAFLIST:
 | 
						|
		data = nb_callback_get_elem(nb_node, xpath, nb_next);
 | 
						|
		if (data) {
 | 
						|
			if (data->value) {
 | 
						|
				CONFD_SET_STR(&v[0], data->value);
 | 
						|
				confd_data_reply_next_key(tctx, v, 1,
 | 
						|
							  (long)nb_next);
 | 
						|
			}
 | 
						|
			yang_data_free(data);
 | 
						|
		} else
 | 
						|
			confd_data_reply_next_key(tctx, NULL, -1, -1);
 | 
						|
		break;
 | 
						|
	default:
 | 
						|
		break;
 | 
						|
	}
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Optional callback - implemented for performance reasons.
 | 
						|
 */
 | 
						|
static int frr_confd_data_get_object(struct confd_trans_ctx *tctx,
 | 
						|
				     confd_hkeypath_t *kp)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	const struct lysc_node *child;
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	char xpath_child[XPATH_MAXLEN * 2];
 | 
						|
	struct list *elements;
 | 
						|
	struct yang_data *data;
 | 
						|
	const void *list_entry;
 | 
						|
	confd_value_t values[CONFD_MAX_CHILD_NODES];
 | 
						|
	size_t nvalues = 0;
 | 
						|
 | 
						|
	frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		confd_data_reply_not_found(tctx);
 | 
						|
		return CONFD_ERR;
 | 
						|
	}
 | 
						|
 | 
						|
	if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &list_entry) != 0) {
 | 
						|
		confd_data_reply_not_found(tctx);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	elements = yang_data_list_new();
 | 
						|
 | 
						|
	/* Loop through list child nodes. */
 | 
						|
	LY_LIST_FOR (lysc_node_child(nb_node->snode), child) {
 | 
						|
		struct nb_node *nb_node_child = child->priv;
 | 
						|
		confd_value_t *v;
 | 
						|
 | 
						|
		if (nvalues > CONFD_MAX_CHILD_NODES)
 | 
						|
			break;
 | 
						|
 | 
						|
		v = &values[nvalues++];
 | 
						|
 | 
						|
		/* Non-presence containers, lists and leaf-lists. */
 | 
						|
		if (!nb_node_child->cbs.get_elem) {
 | 
						|
			CONFD_SET_NOEXISTS(v);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
 | 
						|
		snprintf(xpath_child, sizeof(xpath_child), "%s/%s", xpath,
 | 
						|
			 child->name);
 | 
						|
		data = nb_callback_get_elem(nb_node_child, xpath_child,
 | 
						|
					    list_entry);
 | 
						|
		if (data) {
 | 
						|
			if (data->value)
 | 
						|
				CONFD_SET_STR(v, data->value);
 | 
						|
			else {
 | 
						|
				/* Presence containers and empty leafs. */
 | 
						|
				CONFD_SET_XMLTAG(
 | 
						|
					v, nb_node_child->confd_hash,
 | 
						|
					confd_str2hash(nb_node_child->snode
 | 
						|
							       ->module->ns));
 | 
						|
			}
 | 
						|
			listnode_add(elements, data);
 | 
						|
		} else
 | 
						|
			CONFD_SET_NOEXISTS(v);
 | 
						|
	}
 | 
						|
 | 
						|
	confd_data_reply_value_array(tctx, values, nvalues);
 | 
						|
 | 
						|
	/* Release memory. */
 | 
						|
	list_delete(&elements);
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Optional callback - implemented for performance reasons.
 | 
						|
 */
 | 
						|
static int frr_confd_data_get_next_object(struct confd_trans_ctx *tctx,
 | 
						|
					  confd_hkeypath_t *kp, long next)
 | 
						|
{
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	struct list *elements;
 | 
						|
	const void *parent_list_entry;
 | 
						|
	const void *nb_next;
 | 
						|
#define CONFD_OBJECTS_PER_TIME 100
 | 
						|
	struct confd_next_object objects[CONFD_OBJECTS_PER_TIME + 1];
 | 
						|
	char pseudo_keys[CONFD_OBJECTS_PER_TIME][32];
 | 
						|
	int nobjects = 0;
 | 
						|
 | 
						|
	frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		confd_data_reply_next_object_array(tctx, NULL, 0, 0);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	if (frr_confd_hkeypath_get_list_entry(kp, nb_node, &parent_list_entry)
 | 
						|
	    != 0) {
 | 
						|
		confd_data_reply_next_object_array(tctx, NULL, 0, 0);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	elements = yang_data_list_new();
 | 
						|
	nb_next = (next == -1) ? NULL : (void *)next;
 | 
						|
 | 
						|
	memset(objects, 0, sizeof(objects));
 | 
						|
	for (int j = 0; j < CONFD_OBJECTS_PER_TIME; j++) {
 | 
						|
		struct confd_next_object *object;
 | 
						|
		const struct lysc_node *child;
 | 
						|
		struct yang_data *data;
 | 
						|
		size_t nvalues = 0;
 | 
						|
 | 
						|
		object = &objects[j];
 | 
						|
 | 
						|
		nb_next = nb_callback_get_next(nb_node, parent_list_entry,
 | 
						|
					       nb_next);
 | 
						|
		if (!nb_next)
 | 
						|
			/* End of the list. */
 | 
						|
			break;
 | 
						|
 | 
						|
		object->next = (long)nb_next;
 | 
						|
 | 
						|
		/* Leaf-lists require special handling. */
 | 
						|
		if (nb_node->snode->nodetype == LYS_LEAFLIST) {
 | 
						|
			object->v = XMALLOC(MTYPE_CONFD, sizeof(confd_value_t));
 | 
						|
			data = nb_callback_get_elem(nb_node, xpath, nb_next);
 | 
						|
			assert(data && data->value);
 | 
						|
			CONFD_SET_STR(object->v, data->value);
 | 
						|
			nvalues++;
 | 
						|
			listnode_add(elements, data);
 | 
						|
			goto next;
 | 
						|
		}
 | 
						|
 | 
						|
		object->v =
 | 
						|
			XMALLOC(MTYPE_CONFD,
 | 
						|
				CONFD_MAX_CHILD_NODES * sizeof(confd_value_t));
 | 
						|
 | 
						|
		/*
 | 
						|
		 * ConfD 6.6 user guide, chapter 6.11 (Operational data lists
 | 
						|
		 * without keys):
 | 
						|
		 * "In the response to the get_next_object() callback, the data
 | 
						|
		 * provider is expected to provide the key values along with the
 | 
						|
		 * other leafs in an array that is populated according to the
 | 
						|
		 * data model. This must be done also for this type of list,
 | 
						|
		 * even though the key isn't actually in the data model. The
 | 
						|
		 * "pseudo" key must always be the first element in the array".
 | 
						|
		 */
 | 
						|
		if (CHECK_FLAG(nb_node->flags, F_NB_NODE_KEYLESS_LIST)) {
 | 
						|
			confd_value_t *v;
 | 
						|
 | 
						|
			snprintf(pseudo_keys[j], sizeof(pseudo_keys[j]), "%lu",
 | 
						|
				 (unsigned long)nb_next);
 | 
						|
 | 
						|
			v = &object->v[nvalues++];
 | 
						|
			CONFD_SET_STR(v, pseudo_keys[j]);
 | 
						|
		}
 | 
						|
 | 
						|
		/* Loop through list child nodes. */
 | 
						|
		LY_LIST_FOR (lysc_node_child(nb_node->snode), child) {
 | 
						|
			struct nb_node *nb_node_child = child->priv;
 | 
						|
			char xpath_child[XPATH_MAXLEN * 2];
 | 
						|
			confd_value_t *v;
 | 
						|
 | 
						|
			if (nvalues > CONFD_MAX_CHILD_NODES)
 | 
						|
				break;
 | 
						|
 | 
						|
			v = &object->v[nvalues++];
 | 
						|
 | 
						|
			/* Non-presence containers, lists and leaf-lists. */
 | 
						|
			if (!nb_node_child->cbs.get_elem) {
 | 
						|
				CONFD_SET_NOEXISTS(v);
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
 | 
						|
			snprintf(xpath_child, sizeof(xpath_child), "%s/%s",
 | 
						|
				 xpath, child->name);
 | 
						|
			data = nb_callback_get_elem(nb_node_child, xpath_child,
 | 
						|
						    nb_next);
 | 
						|
			if (data) {
 | 
						|
				if (data->value)
 | 
						|
					CONFD_SET_STR(v, data->value);
 | 
						|
				else {
 | 
						|
					/*
 | 
						|
					 * Presence containers and empty leafs.
 | 
						|
					 */
 | 
						|
					CONFD_SET_XMLTAG(
 | 
						|
						v, nb_node_child->confd_hash,
 | 
						|
						confd_str2hash(
 | 
						|
							nb_node_child->snode
 | 
						|
								->module->ns));
 | 
						|
				}
 | 
						|
				listnode_add(elements, data);
 | 
						|
			} else
 | 
						|
				CONFD_SET_NOEXISTS(v);
 | 
						|
		}
 | 
						|
	next:
 | 
						|
		object->n = nvalues;
 | 
						|
		nobjects++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (nobjects == 0) {
 | 
						|
		confd_data_reply_next_object_array(tctx, NULL, 0, 0);
 | 
						|
		list_delete(&elements);
 | 
						|
		return CONFD_OK;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Detect end of the list. */
 | 
						|
	if (!nb_next) {
 | 
						|
		nobjects++;
 | 
						|
		objects[nobjects].v = NULL;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Reply to ConfD. */
 | 
						|
	confd_data_reply_next_object_arrays(tctx, objects, nobjects, 0);
 | 
						|
	if (!nb_next)
 | 
						|
		nobjects--;
 | 
						|
 | 
						|
	/* Release memory. */
 | 
						|
	list_delete(&elements);
 | 
						|
	for (int j = 0; j < nobjects; j++) {
 | 
						|
		struct confd_next_object *object;
 | 
						|
 | 
						|
		object = &objects[j];
 | 
						|
		XFREE(MTYPE_CONFD, object->v);
 | 
						|
	}
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_notification_send(const char *xpath,
 | 
						|
				       struct list *arguments)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	struct yang_module *module;
 | 
						|
	struct confd_datetime now;
 | 
						|
	confd_tag_value_t *values;
 | 
						|
	int nvalues;
 | 
						|
	int i = 0;
 | 
						|
	struct yang_data *data;
 | 
						|
	struct listnode *node;
 | 
						|
	int ret;
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	module = yang_module_find(nb_node->snode->module->name);
 | 
						|
	assert(module);
 | 
						|
 | 
						|
	nvalues = 2;
 | 
						|
	if (arguments)
 | 
						|
		nvalues += listcount(arguments);
 | 
						|
 | 
						|
	values = XMALLOC(MTYPE_CONFD, nvalues * sizeof(*values));
 | 
						|
 | 
						|
	CONFD_SET_TAG_XMLBEGIN(&values[i++], nb_node->confd_hash,
 | 
						|
			       module->confd_hash);
 | 
						|
	for (ALL_LIST_ELEMENTS_RO(arguments, node, data)) {
 | 
						|
		struct nb_node *nb_node_arg;
 | 
						|
 | 
						|
		nb_node_arg = nb_node_find(data->xpath);
 | 
						|
		if (!nb_node_arg) {
 | 
						|
			flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
				  "%s: unknown data path: %s", __func__,
 | 
						|
				  data->xpath);
 | 
						|
			XFREE(MTYPE_CONFD, values);
 | 
						|
			return NB_ERR;
 | 
						|
		}
 | 
						|
 | 
						|
		CONFD_SET_TAG_STR(&values[i++], nb_node_arg->confd_hash,
 | 
						|
				  data->value);
 | 
						|
	}
 | 
						|
	CONFD_SET_TAG_XMLEND(&values[i++], nb_node->confd_hash,
 | 
						|
			     module->confd_hash);
 | 
						|
 | 
						|
	getdatetime(&now);
 | 
						|
	ret = confd_notification_send(live_ctx, &now, values, nvalues);
 | 
						|
 | 
						|
	/* Release memory. */
 | 
						|
	XFREE(MTYPE_CONFD, values);
 | 
						|
 | 
						|
	/* Map ConfD return code to northbound return code. */
 | 
						|
	switch (ret) {
 | 
						|
	case CONFD_OK:
 | 
						|
		return NB_OK;
 | 
						|
	default:
 | 
						|
		return NB_ERR;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_action_init(struct confd_user_info *uinfo)
 | 
						|
{
 | 
						|
	confd_action_set_fd(uinfo, dp_worker_sock);
 | 
						|
 | 
						|
	return CONFD_OK;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_action_execute(struct confd_user_info *uinfo,
 | 
						|
				    struct xml_tag *name, confd_hkeypath_t *kp,
 | 
						|
				    confd_tag_value_t *params, int nparams)
 | 
						|
{
 | 
						|
	char xpath[XPATH_MAXLEN];
 | 
						|
	struct nb_node *nb_node;
 | 
						|
	struct list *input;
 | 
						|
	struct list *output;
 | 
						|
	struct yang_data *data;
 | 
						|
	confd_tag_value_t *reply;
 | 
						|
	int ret = CONFD_OK;
 | 
						|
	char errmsg[BUFSIZ] = {0};
 | 
						|
 | 
						|
	/* Getting the XPath is tricky. */
 | 
						|
	if (kp) {
 | 
						|
		/* This is a YANG RPC. */
 | 
						|
		frr_confd_get_xpath(kp, xpath, sizeof(xpath));
 | 
						|
		strlcat(xpath, "/", sizeof(xpath));
 | 
						|
		strlcat(xpath, confd_hash2str(name->tag), sizeof(xpath));
 | 
						|
	} else {
 | 
						|
		/* This is a YANG action. */
 | 
						|
		snprintf(xpath, sizeof(xpath), "/%s:%s",
 | 
						|
			 confd_ns2prefix(name->ns), confd_hash2str(name->tag));
 | 
						|
	}
 | 
						|
 | 
						|
	nb_node = nb_node_find(xpath);
 | 
						|
	if (!nb_node) {
 | 
						|
		flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
			  "%s: unknown data path: %s", __func__, xpath);
 | 
						|
		return CONFD_ERR;
 | 
						|
	}
 | 
						|
 | 
						|
	input = yang_data_list_new();
 | 
						|
	output = yang_data_list_new();
 | 
						|
 | 
						|
	/* Process input nodes. */
 | 
						|
	for (int i = 0; i < nparams; i++) {
 | 
						|
		char xpath_input[XPATH_MAXLEN * 2];
 | 
						|
		char value_str[YANG_VALUE_MAXLEN];
 | 
						|
 | 
						|
		snprintf(xpath_input, sizeof(xpath_input), "%s/%s", xpath,
 | 
						|
			 confd_hash2str(params[i].tag.tag));
 | 
						|
 | 
						|
		if (frr_confd_val2str(xpath_input, ¶ms[i].v, value_str,
 | 
						|
				      sizeof(value_str))
 | 
						|
		    != 0) {
 | 
						|
			flog_err(
 | 
						|
				EC_LIB_CONFD_DATA_CONVERT,
 | 
						|
				"%s: failed to convert ConfD value to a string",
 | 
						|
				__func__);
 | 
						|
			ret = CONFD_ERR;
 | 
						|
			goto exit;
 | 
						|
		}
 | 
						|
 | 
						|
		data = yang_data_new(xpath_input, value_str);
 | 
						|
		listnode_add(input, data);
 | 
						|
	}
 | 
						|
 | 
						|
	/* Execute callback registered for this XPath. */
 | 
						|
	if (nb_callback_rpc(nb_node, xpath, input, output, errmsg,
 | 
						|
			    sizeof(errmsg))
 | 
						|
	    != NB_OK) {
 | 
						|
		flog_warn(EC_LIB_NB_CB_RPC, "%s: rpc callback failed: %s",
 | 
						|
			  __func__, xpath);
 | 
						|
		ret = CONFD_ERR;
 | 
						|
		goto exit;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Process output nodes. */
 | 
						|
	if (listcount(output) > 0) {
 | 
						|
		struct listnode *node;
 | 
						|
		int i = 0;
 | 
						|
 | 
						|
		reply = XMALLOC(MTYPE_CONFD,
 | 
						|
				listcount(output) * sizeof(*reply));
 | 
						|
 | 
						|
		for (ALL_LIST_ELEMENTS_RO(output, node, data)) {
 | 
						|
			struct nb_node *nb_node_output;
 | 
						|
			int hash;
 | 
						|
 | 
						|
			nb_node_output = nb_node_find(data->xpath);
 | 
						|
			if (!nb_node_output) {
 | 
						|
				flog_warn(EC_LIB_YANG_UNKNOWN_DATA_PATH,
 | 
						|
					  "%s: unknown data path: %s", __func__,
 | 
						|
					  data->xpath);
 | 
						|
				goto exit;
 | 
						|
			}
 | 
						|
 | 
						|
			hash = confd_str2hash(nb_node_output->snode->name);
 | 
						|
			CONFD_SET_TAG_STR(&reply[i++], hash, data->value);
 | 
						|
		}
 | 
						|
		confd_action_reply_values(uinfo, reply, listcount(output));
 | 
						|
		XFREE(MTYPE_CONFD, reply);
 | 
						|
	}
 | 
						|
 | 
						|
exit:
 | 
						|
	/* Release memory. */
 | 
						|
	list_delete(&input);
 | 
						|
	list_delete(&output);
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static int frr_confd_dp_read(struct confd_daemon_ctx *dctx, int fd)
 | 
						|
{
 | 
						|
	int ret;
 | 
						|
 | 
						|
	ret = confd_fd_ready(dctx, fd);
 | 
						|
	if (ret == CONFD_EOF) {
 | 
						|
		flog_err_confd("confd_fd_ready");
 | 
						|
		frr_confd_finish();
 | 
						|
		return -1;
 | 
						|
	} else if (ret == CONFD_ERR && confd_errno != CONFD_ERR_EXTERNAL) {
 | 
						|
		flog_err_confd("confd_fd_ready");
 | 
						|
		frr_confd_finish();
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_dp_ctl_read(struct event *thread)
 | 
						|
{
 | 
						|
	struct confd_daemon_ctx *dctx = EVENT_ARG(thread);
 | 
						|
	int fd = EVENT_FD(thread);
 | 
						|
 | 
						|
	event_add_read(master, frr_confd_dp_ctl_read, dctx, fd, &t_dp_ctl);
 | 
						|
 | 
						|
	frr_confd_dp_read(dctx, fd);
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_dp_worker_read(struct event *thread)
 | 
						|
{
 | 
						|
	struct confd_daemon_ctx *dctx = EVENT_ARG(thread);
 | 
						|
	int fd = EVENT_FD(thread);
 | 
						|
 | 
						|
	event_add_read(master, frr_confd_dp_worker_read, dctx, fd,
 | 
						|
		       &t_dp_worker);
 | 
						|
 | 
						|
	frr_confd_dp_read(dctx, fd);
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_subscribe_state(const struct lysc_node *snode, void *arg)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node = snode->priv;
 | 
						|
	struct confd_data_cbs *data_cbs = arg;
 | 
						|
 | 
						|
	if (!nb_node || !CHECK_FLAG(snode->flags, LYS_CONFIG_R))
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
	/* We only need to subscribe to the root of the state subtrees. */
 | 
						|
	if (snode->parent && CHECK_FLAG(snode->parent->flags, LYS_CONFIG_R))
 | 
						|
		return YANG_ITER_CONTINUE;
 | 
						|
 | 
						|
	DEBUGD(&nb_dbg_client_confd,
 | 
						|
	       "%s: providing data to '%s' (callpoint %s)", __func__,
 | 
						|
	       nb_node->xpath, snode->name);
 | 
						|
 | 
						|
	strlcpy(data_cbs->callpoint, snode->name, sizeof(data_cbs->callpoint));
 | 
						|
	if (confd_register_data_cb(dctx, data_cbs) != CONFD_OK)
 | 
						|
		flog_err_confd("confd_register_data_cb");
 | 
						|
 | 
						|
	return YANG_ITER_CONTINUE;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_init_dp(const char *program_name)
 | 
						|
{
 | 
						|
	struct confd_trans_cbs trans_cbs;
 | 
						|
	struct confd_data_cbs data_cbs;
 | 
						|
	struct confd_notification_stream_cbs ncbs;
 | 
						|
	struct confd_action_cbs acbs;
 | 
						|
 | 
						|
	/* Initialize daemon context. */
 | 
						|
	dctx = confd_init_daemon(program_name);
 | 
						|
	if (!dctx) {
 | 
						|
		flog_err_confd("confd_init_daemon");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Inform we want to receive YANG values as raw strings, and that we
 | 
						|
	 * want to provide only strings in the reply functions, regardless of
 | 
						|
	 * the YANG type.
 | 
						|
	 */
 | 
						|
	confd_set_daemon_flags(dctx, CONFD_DAEMON_FLAG_STRINGSONLY);
 | 
						|
 | 
						|
	/* Establish a control socket. */
 | 
						|
	dp_ctl_sock = socket(PF_INET, SOCK_STREAM, 0);
 | 
						|
	if (dp_ctl_sock < 0) {
 | 
						|
		flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
 | 
						|
			 __func__, safe_strerror(errno));
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	if (confd_connect(dctx, dp_ctl_sock, CONTROL_SOCKET, &confd_addr,
 | 
						|
			  sizeof(struct sockaddr_in))
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_connect");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Establish a worker socket (only one since this plugin runs on a
 | 
						|
	 * single thread).
 | 
						|
	 */
 | 
						|
	dp_worker_sock = socket(PF_INET, SOCK_STREAM, 0);
 | 
						|
	if (dp_worker_sock < 0) {
 | 
						|
		flog_err(EC_LIB_SOCKET, "%s: failed to create socket: %s",
 | 
						|
			 __func__, safe_strerror(errno));
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
	if (confd_connect(dctx, dp_worker_sock, WORKER_SOCKET, &confd_addr,
 | 
						|
			  sizeof(struct sockaddr_in))
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_connect");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Register transaction callback functions. */
 | 
						|
	memset(&trans_cbs, 0, sizeof(trans_cbs));
 | 
						|
	trans_cbs.init = frr_confd_transaction_init;
 | 
						|
	confd_register_trans_cb(dctx, &trans_cbs);
 | 
						|
 | 
						|
	/* Register our read/write callbacks. */
 | 
						|
	memset(&data_cbs, 0, sizeof(data_cbs));
 | 
						|
	data_cbs.get_elem = frr_confd_data_get_elem;
 | 
						|
	data_cbs.exists_optional = frr_confd_data_get_elem;
 | 
						|
	data_cbs.get_next = frr_confd_data_get_next;
 | 
						|
	data_cbs.get_object = frr_confd_data_get_object;
 | 
						|
	data_cbs.get_next_object = frr_confd_data_get_next_object;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Iterate over all loaded YANG modules and subscribe to the paths
 | 
						|
	 * referent to state data.
 | 
						|
	 */
 | 
						|
	yang_snodes_iterate(NULL, frr_confd_subscribe_state, 0, &data_cbs);
 | 
						|
 | 
						|
	/* Register notification stream. */
 | 
						|
	memset(&ncbs, 0, sizeof(ncbs));
 | 
						|
	ncbs.fd = dp_worker_sock;
 | 
						|
	/*
 | 
						|
	 * RFC 5277 - Section 3.2.3:
 | 
						|
	 * A NETCONF server implementation supporting the notification
 | 
						|
	 * capability MUST support the "NETCONF" notification event
 | 
						|
	 * stream. This stream contains all NETCONF XML event notifications
 | 
						|
	 * supported by the NETCONF server.
 | 
						|
	 */
 | 
						|
	strlcpy(ncbs.streamname, "NETCONF", sizeof(ncbs.streamname));
 | 
						|
	if (confd_register_notification_stream(dctx, &ncbs, &live_ctx)
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_register_notification_stream");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Register the action handler callback. */
 | 
						|
	memset(&acbs, 0, sizeof(acbs));
 | 
						|
	strlcpy(acbs.actionpoint, "actionpoint", sizeof(acbs.actionpoint));
 | 
						|
	acbs.init = frr_confd_action_init;
 | 
						|
	acbs.action = frr_confd_action_execute;
 | 
						|
	if (confd_register_action_cbs(dctx, &acbs) != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_register_action_cbs");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Notify we registered all callbacks we wanted. */
 | 
						|
	if (confd_register_done(dctx) != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_register_done");
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	event_add_read(master, frr_confd_dp_ctl_read, dctx, dp_ctl_sock,
 | 
						|
		       &t_dp_ctl);
 | 
						|
	event_add_read(master, frr_confd_dp_worker_read, dctx, dp_worker_sock,
 | 
						|
		       &t_dp_worker);
 | 
						|
 | 
						|
	return 0;
 | 
						|
 | 
						|
error:
 | 
						|
	frr_confd_finish_dp();
 | 
						|
 | 
						|
	return -1;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_finish_dp(void)
 | 
						|
{
 | 
						|
	if (dp_worker_sock > 0) {
 | 
						|
		EVENT_OFF(t_dp_worker);
 | 
						|
		close(dp_worker_sock);
 | 
						|
	}
 | 
						|
	if (dp_ctl_sock > 0) {
 | 
						|
		EVENT_OFF(t_dp_ctl);
 | 
						|
		close(dp_ctl_sock);
 | 
						|
	}
 | 
						|
	if (dctx != NULL)
 | 
						|
		confd_release_daemon(dctx);
 | 
						|
}
 | 
						|
 | 
						|
/* ------------ CLI ------------ */
 | 
						|
 | 
						|
DEFUN (debug_nb_confd,
 | 
						|
       debug_nb_confd_cmd,
 | 
						|
       "[no] debug northbound client confd",
 | 
						|
       NO_STR
 | 
						|
       DEBUG_STR
 | 
						|
       "Northbound debugging\n"
 | 
						|
       "Client\n"
 | 
						|
       "ConfD\n")
 | 
						|
{
 | 
						|
	uint32_t mode = DEBUG_NODE2MODE(vty->node);
 | 
						|
	bool no = strmatch(argv[0]->text, "no");
 | 
						|
 | 
						|
	DEBUG_MODE_SET(&nb_dbg_client_confd, mode, !no);
 | 
						|
 | 
						|
	return CMD_SUCCESS;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_debug_config_write(struct vty *vty)
 | 
						|
{
 | 
						|
	if (DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_CONF))
 | 
						|
		vty_out(vty, "debug northbound client confd\n");
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_debug_set_all(uint32_t flags, bool set)
 | 
						|
{
 | 
						|
	DEBUG_FLAGS_SET(&nb_dbg_client_confd, flags, set);
 | 
						|
 | 
						|
	/* If all modes have been turned off, don't preserve options. */
 | 
						|
	if (!DEBUG_MODE_CHECK(&nb_dbg_client_confd, DEBUG_MODE_ALL))
 | 
						|
		DEBUG_CLEAR(&nb_dbg_client_confd);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void frr_confd_cli_init(void)
 | 
						|
{
 | 
						|
	hook_register(nb_client_debug_config_write,
 | 
						|
		      frr_confd_debug_config_write);
 | 
						|
	hook_register(nb_client_debug_set_all, frr_confd_debug_set_all);
 | 
						|
 | 
						|
	install_element(ENABLE_NODE, &debug_nb_confd_cmd);
 | 
						|
	install_element(CONFIG_NODE, &debug_nb_confd_cmd);
 | 
						|
}
 | 
						|
 | 
						|
/* ------------ Main ------------ */
 | 
						|
 | 
						|
static int frr_confd_calculate_snode_hash(const struct lysc_node *snode,
 | 
						|
					  void *arg)
 | 
						|
{
 | 
						|
	struct nb_node *nb_node = snode->priv;
 | 
						|
 | 
						|
	if (nb_node)
 | 
						|
		nb_node->confd_hash = confd_str2hash(snode->name);
 | 
						|
 | 
						|
	return YANG_ITER_CONTINUE;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_init(const char *program_name)
 | 
						|
{
 | 
						|
	struct sockaddr_in *confd_addr4 = (struct sockaddr_in *)&confd_addr;
 | 
						|
	int debuglevel = CONFD_SILENT;
 | 
						|
	int ret = -1;
 | 
						|
 | 
						|
	/* Initialize ConfD library. */
 | 
						|
	confd_init(program_name, stderr, debuglevel);
 | 
						|
 | 
						|
	confd_addr4->sin_family = AF_INET;
 | 
						|
	confd_addr4->sin_addr.s_addr = inet_addr("127.0.0.1");
 | 
						|
	confd_addr4->sin_port = htons(CONFD_PORT);
 | 
						|
	if (confd_load_schemas(&confd_addr, sizeof(struct sockaddr_in))
 | 
						|
	    != CONFD_OK) {
 | 
						|
		flog_err_confd("confd_load_schemas");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = frr_confd_init_cdb();
 | 
						|
	if (ret != 0)
 | 
						|
		goto error;
 | 
						|
 | 
						|
	ret = frr_confd_init_dp(program_name);
 | 
						|
	if (ret != 0) {
 | 
						|
		frr_confd_finish_cdb();
 | 
						|
		goto error;
 | 
						|
	}
 | 
						|
 | 
						|
	yang_snodes_iterate(NULL, frr_confd_calculate_snode_hash, 0, NULL);
 | 
						|
 | 
						|
	hook_register(nb_notification_send, frr_confd_notification_send);
 | 
						|
 | 
						|
	confd_connected = true;
 | 
						|
	return 0;
 | 
						|
 | 
						|
error:
 | 
						|
	confd_free_schemas();
 | 
						|
 | 
						|
	return ret;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_finish(void)
 | 
						|
{
 | 
						|
	if (!confd_connected)
 | 
						|
		return 0;
 | 
						|
 | 
						|
	frr_confd_finish_cdb();
 | 
						|
	frr_confd_finish_dp();
 | 
						|
 | 
						|
	confd_free_schemas();
 | 
						|
 | 
						|
	confd_connected = false;
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_module_late_init(struct event_loop *tm)
 | 
						|
{
 | 
						|
	master = tm;
 | 
						|
 | 
						|
	if (frr_confd_init(frr_get_progname()) < 0) {
 | 
						|
		flog_err(EC_LIB_CONFD_INIT,
 | 
						|
			 "failed to initialize the ConfD module");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	hook_register(frr_fini, frr_confd_finish);
 | 
						|
	frr_confd_cli_init();
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int frr_confd_module_init(void)
 | 
						|
{
 | 
						|
	hook_register(frr_late_init, frr_confd_module_late_init);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
FRR_MODULE_SETUP(.name = "frr_confd", .version = FRR_VERSION,
 | 
						|
		 .description = "FRR ConfD integration module",
 | 
						|
		 .init = frr_confd_module_init,
 | 
						|
);
 |