Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NETCONF error tags #509

Merged
merged 4 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 15 additions & 19 deletions src/io.c
Original file line number Diff line number Diff line change
Expand Up @@ -919,27 +919,21 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
rpc_envp = va_arg(ap, struct lyd_node_opaq *);
reply = va_arg(ap, struct nc_server_reply *);

if (!rpc_envp) {
/* can be NULL if replying with a malformed-message error */
nc_write_clb((void *)&arg, "<rpc-reply xmlns=\"" NC_NS_BASE "\">", 18 + strlen(NC_NS_BASE) + 2, 0);

assert(reply->type == NC_RPL_ERROR);
if (lyd_print_clb(nc_write_xmlclb, (void *)&arg, ((struct nc_server_reply_error *)reply)->err, LYD_XML,
LYD_PRINT_SHRINK | LYD_PRINT_WITHSIBLINGS)) {
/* build a rpc-reply opaque node that can be simply printed */
if (rpc_envp) {
if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns,
&reply_envp)) {
ERRINT;
ret = NC_MSG_ERROR;
goto cleanup;
}
} else {
if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, NULL, NC_NS_BASE,
&reply_envp)) {
ERRINT;
ret = NC_MSG_ERROR;
goto cleanup;
}

nc_write_clb((void *)&arg, "</rpc-reply>", 12, 0);
break;
}

/* build a rpc-reply opaque node that can be simply printed */
if (lyd_new_opaq2(NULL, session->ctx, "rpc-reply", NULL, rpc_envp->name.prefix, rpc_envp->name.module_ns,
&reply_envp)) {
ERRINT;
ret = NC_MSG_ERROR;
goto cleanup;
}

switch (reply->type) {
Expand Down Expand Up @@ -988,7 +982,9 @@ nc_write_msg_io(struct nc_session *session, int io_timeout, int type, ...)
}

/* temporary */
((struct lyd_node_opaq *)reply_envp)->attr = rpc_envp->attr;
if (rpc_envp) {
((struct lyd_node_opaq *)reply_envp)->attr = rpc_envp->attr;
}

/* print */
lyrc = lyd_print_clb(nc_write_xmlclb, (void *)&arg, reply_envp, LYD_XML, LYD_PRINT_SHRINK | wd);
Expand Down
7 changes: 4 additions & 3 deletions src/messages_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -581,12 +581,13 @@ nc_err_set_msg(struct lyd_node *err, const char *error_message, const char *lang

NC_CHECK_ARG_RET(NULL, err, error_message, -1);

/* remove previous message */
lyd_find_sibling_opaq_next(lyd_child(err), "error-message", &match);
if (match) {
lyd_free_tree(match);
/* Change the value of error-message and keep order of elements to comply with appendix-B in RFC 6241. */
lydict_remove(LYD_CTX(err), ((struct lyd_node_opaq *)match)->value);
lydict_insert(LYD_CTX(err), error_message, 0, &(((struct lyd_node_opaq *)match)->value));
michalvasko marked this conversation as resolved.
Show resolved Hide resolved
return 0;
}

if (lyd_new_opaq2(err, NULL, "error-message", error_message, NULL, NC_NS_BASE, &match)) {
return -1;
}
Expand Down
199 changes: 184 additions & 15 deletions src/session_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -1400,6 +1400,186 @@ recv_rpc_check_msgid(struct nc_session *session, const struct lyd_node *envp)
return NC_MSG_RPC;
}

/**
* @brief Find lysc node mentioned in schema_path.
*
* @param[in] ctx libyang context.
* @param[in] ly_err last libyang error.
* @return lysc node.
*/
static const struct lysc_node *
nc_rpc_err_find_lysc_node(const struct ly_ctx *ctx, const struct ly_err_item *ly_err)
{
char *str, *last;
const struct lysc_node *cn;

if (!ly_err->schema_path) {
return NULL;
}

str = strdup(ly_err->schema_path);
if (!str) {
return NULL;
}
last = strrchr(str, '/');
if (strchr(last, '@')) {
/* ignore attribute part */
*last = '\0';
}
cn = lys_find_path(ctx, NULL, str, 0);
free(str);

return cn;
}

/**
* @brief Find the nth substring delimited by quotes.
*
* For example: abcd"ef"ghij"kl"mn -> index 0 is "ef", index 1 is "kl".
*
* @param[in] msg Input string with quoted substring.
* @param[in] index Number starting from 0 specifying the nth substring.
* @return Copied nth substring without quotes.
*/
static char *
nc_rpc_err_get_quoted_string(const char *msg, uint32_t index)
{
char *ret;
const char *start = NULL, *end = NULL, *iter, *tmp;
uint32_t quote_cnt = 0, last_quote;

assert(msg);

last_quote = (index + 1) * 2;
for (iter = msg; *iter; ++iter) {
if (*iter != '\"') {
continue;
}
/* updating the start and end pointers - swap */
tmp = end;
end = iter;
start = tmp;
if (++quote_cnt == last_quote) {
/* nth substring found */
break;
}
}

if (!start) {
return NULL;
}

/* Skip first quote */
++start;
/* Copy substring */
ret = strndup(start, end - start);

return ret;
}

/**
* @brief Check that the @p str starts with the @p prefix.
*
* @param[in] prefix Required prefix.
* @param[in] str Input string to check.
* @return True if @p str start with @p prefix otherwise False.
*/
static ly_bool
nc_strstarts(const char *prefix, const char *str)
{
return strncmp(str, prefix, strlen(prefix)) == 0;
}

/**
* @brief Prepare reply for rpc error.
*
* @param[in] session NETCONF session.
* @param[in] envp NETCONF-specific RPC envelope. Can be NULL.
* @return rpc-reply object or NULL.
*/
static struct nc_server_reply *
nc_server_prepare_rpc_err(struct nc_session *session, struct lyd_node *envp)
{
struct lyd_node *reply = NULL;
const struct lysc_node *cn;
const struct ly_err_item *ly_err;
NC_ERR_TYPE errtype;
const char *attr;
char *str = NULL, *errmsg = NULL, *schema_path = NULL;
LY_ERR errcode;

/* envelope was not parsed */
if (!envp && (session->version != NC_VERSION_11)) {
return NULL;
}
ly_err = ly_err_last(session->ctx);
if (!envp && !strcmp("Missing XML namespace.", ly_err->msg)) {
reply = nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "xmlns", "rpc");
goto cleanup;
} else if (!envp) {
/* completely malformed message, NETCONF version 1.1 defines sending error reply from
* the server (RFC 6241 sec. 3) */
reply = nc_err(session->ctx, NC_ERR_MALFORMED_MSG);
return nc_server_reply_err(reply);
}
/* at least the envelopes were parsed */
assert(envp);

/* store strings, to avoid overwriting ly_err */
errmsg = strdup(ly_err->msg);
if (!errmsg) {
reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto cleanup;
}
if (ly_err->schema_path) {
schema_path = strdup(ly_err->schema_path);
if (!schema_path) {
reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
goto cleanup;
}
}
errcode = ly_err->err;

/* find out in which layer the error occurred */
cn = nc_rpc_err_find_lysc_node(session->ctx, ly_err);
if (cn && ((cn->nodetype & LYS_RPC) || (cn->nodetype & LYS_INPUT))) {
errtype = NC_ERR_TYPE_PROT;
} else {
errtype = NC_ERR_TYPE_APP;
}

/* deciding which error to prepare */
if (cn && (nc_strstarts("Missing mandatory prefix", errmsg) ||
nc_strstarts("Unknown XML prefix", errmsg))) {
str = nc_rpc_err_get_quoted_string(errmsg, 1);
reply = str ? nc_err(session->ctx, NC_ERR_UNKNOWN_ATTR, errtype, str, cn->name) :
nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
} else if (cn && nc_strstarts("Annotation definition for attribute", errmsg)) {
attr = strrchr(schema_path, ':') + 1;
reply = nc_err(session->ctx, NC_ERR_UNKNOWN_ATTR, errtype, attr, cn->name);
} else if (nc_strstarts("Invalid character sequence", errmsg)) {
reply = nc_err(session->ctx, NC_ERR_MALFORMED_MSG);
} else if (errcode == LY_EMEM) {
/* <error-tag>resource-denied</error-tag> */
reply = nc_err(session->ctx, NC_ERR_RES_DENIED, errtype);
} else {
/* prepare some generic error */
reply = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
}

cleanup:
nc_err_set_msg(reply, errmsg, "en");

/* clear for other errors */
ly_err_clean(session->ctx, NULL);

free(errmsg);
free(schema_path);
free(str);

return nc_server_reply_err(reply);
}

/* should be called holding the session RPC lock! IO lock will be acquired as needed
* returns: NC_PSPOLL_ERROR,
* NC_PSPOLL_TIMEOUT,
Expand All @@ -1411,7 +1591,6 @@ nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_serv
{
struct ly_in *msg;
struct nc_server_reply *reply = NULL;
struct lyd_node *e;
int r, ret = 0;

NC_CHECK_ARG_RET(session, session, rpc, NC_PSPOLL_ERROR);
Expand Down Expand Up @@ -1448,22 +1627,12 @@ nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_serv
} else {
/* no message-id */
reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MISSING_ATTR, NC_ERR_TYPE_RPC, "message-id", "rpc"));
ret = NC_PSPOLL_BAD_RPC;
}
} else {
/* bad RPC received */
if ((*rpc)->envp) {
/* at least the envelopes were parsed */
e = nc_err(session->ctx, NC_ERR_OP_FAILED, NC_ERR_TYPE_APP);
nc_err_set_msg(e, ly_err_last(session->ctx)->msg, "en");
reply = nc_server_reply_err(e);
} else if (session->version == NC_VERSION_11) {
/* completely malformed message, NETCONF version 1.1 defines sending error reply from
* the server (RFC 6241 sec. 3) */
reply = nc_server_reply_err(nc_err(session->ctx, NC_ERR_MALFORMED_MSG));
} else {
/* at least set the return value */
ret = NC_PSPOLL_BAD_RPC;
}
reply = nc_server_prepare_rpc_err(session, (*rpc)->envp);
ret = NC_PSPOLL_BAD_RPC;
}

cleanup:
Expand All @@ -1480,7 +1649,7 @@ nc_server_recv_rpc_io(struct nc_session *session, int io_timeout, struct nc_serv
}

/* bad RPC and an error reply sent */
ret = NC_PSPOLL_BAD_RPC | NC_PSPOLL_REPLY_ERROR;
ret |= NC_PSPOLL_REPLY_ERROR;
}

ly_in_free(msg, 1);
Expand Down