1440 lines
48 KiB
C
1440 lines
48 KiB
C
/*
|
|
*
|
|
***** BEGIN LICENSE BLOCK *****
|
|
|
|
Copyright (C) 2022 Olof Hagsand and Kristofer Hallin
|
|
Sponsored by Siklu Communications LTD
|
|
|
|
This file is part of CLIXON.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
|
|
Alternatively, the contents of this file may be used under the terms of
|
|
the GNU General Public License Version 3 or later (the "GPL"),
|
|
in which case the provisions of the GPL are applicable instead
|
|
of those above. If you wish to allow use of your version of this file only
|
|
under the terms of the GPL, and not to allow others to
|
|
use your version of this file under the terms of Apache License version 2,
|
|
indicate your decision by deleting the provisions above and replace them with
|
|
the notice and other provisions required by the GPL. If you do not delete
|
|
the provisions above, a recipient may use your version of this file under
|
|
the terms of any one of the Apache License version 2 or the GPL.
|
|
|
|
***** END LICENSE BLOCK *****
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "clixon_config.h" /* generated by config & autoconf */
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <pwd.h>
|
|
#include <syslog.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <signal.h>
|
|
|
|
/* net-snmp */
|
|
#include <net-snmp/net-snmp-config.h>
|
|
#include <net-snmp/net-snmp-includes.h>
|
|
#include <net-snmp/agent/net-snmp-agent-includes.h>
|
|
|
|
/* cligen */
|
|
#include <cligen/cligen.h>
|
|
|
|
/* clixon */
|
|
#include <clixon/clixon.h>
|
|
|
|
#include "snmp_lib.h"
|
|
#include "snmp_register.h"
|
|
#include "snmp_handler.h"
|
|
|
|
/*! Common code for handling incoming SNMP request
|
|
*
|
|
* Get clixon handle from snmp request, print debug data
|
|
* @param[in] handler Registered MIB handler structure
|
|
* @param[in] nhreg Root registration info.
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @param[in] tablehandler 0:scalar, 1:table for debug
|
|
* @param[out] shp Clixon snmp handle
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_common_handler(netsnmp_mib_handler *handler,
|
|
netsnmp_handler_registration *nhreg,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request,
|
|
int tablehandler,
|
|
clixon_snmp_handle **shp)
|
|
{
|
|
int retval = -1;
|
|
netsnmp_variable_list *requestvb; /* sub of request */
|
|
cbuf *cb = NULL;
|
|
|
|
if (request == NULL || shp == NULL){
|
|
clixon_err(OE_XML, EINVAL, "request or shp is null");
|
|
goto done;
|
|
}
|
|
requestvb = request->requestvb;
|
|
if ((*shp = (clixon_snmp_handle*)handler->myvoid) == NULL){
|
|
clixon_err(OE_XML, 0, "No myvoid handler");
|
|
goto done;
|
|
}
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
oid_cbuf(cb, (*shp)->sh_oid, (*shp)->sh_oidlen);
|
|
if (oid_eq(requestvb->name, requestvb->name_length,
|
|
(*shp)->sh_oid, (*shp)->sh_oidlen) == 0){ /* equal */
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
|
|
cbuf_get(cb),
|
|
snmp_msg_int2str(reqinfo->mode),
|
|
request->inclusive, tablehandler?"table":"scalar");
|
|
}
|
|
else{ /* not equal */
|
|
cprintf(cb, " (");
|
|
oid_cbuf(cb, requestvb->name, requestvb->name_length);
|
|
cprintf(cb, ")");
|
|
// nhreg->rootoid same as shp
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s \"%s\" %s inclusive:%d %s", __FUNCTION__,
|
|
cbuf_get(cb),
|
|
snmp_msg_int2str(reqinfo->mode),
|
|
request->inclusive, tablehandler?"table":"scalar");
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
return retval;
|
|
}
|
|
|
|
/*! scalar return
|
|
*
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_scalar_return(cxobj *xs,
|
|
yang_stmt *ys,
|
|
oid *oidc,
|
|
size_t oidclen,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
int asn1type;
|
|
char *xmlstr = NULL;
|
|
char *defaultval = NULL;
|
|
u_char *snmpval = NULL;
|
|
size_t snmplen = 0;
|
|
char *reason = NULL;
|
|
netsnmp_variable_list *requestvb = request->requestvb;
|
|
int ret;
|
|
char *body = NULL;
|
|
|
|
/* SMI default value, How is this different from yang defaults?
|
|
*/
|
|
if (yang_extension_value_opt(ys, "smiv2:defval", NULL, &defaultval) < 0)
|
|
goto done;
|
|
if (xs != NULL && (body = xml_body(xs)) != NULL){
|
|
if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0) // XXX <---
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
}
|
|
else if (defaultval != NULL){
|
|
if ((xmlstr = strdup(defaultval)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
if (type_yang2asn1(ys, &asn1type, 1) < 0)
|
|
goto done;
|
|
if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s %s", __FUNCTION__, reason);
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
/* see snmplib/snmp_client. somewhat indirect
|
|
*/
|
|
if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
|
|
goto done;
|
|
}
|
|
if ((ret = snmp_set_var_objid(requestvb, oidc, oidclen)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "snmp_set_var_objid");
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (xmlstr)
|
|
free(xmlstr);
|
|
if (snmpval)
|
|
free(snmpval);
|
|
if (reason)
|
|
free(reason);
|
|
return retval;
|
|
}
|
|
|
|
/*! Scalar handler, set a value to clixon
|
|
*
|
|
* get xpath: see yang2api_path_fmt / api_path2xpath
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang node
|
|
* @param[in] cvk Vector of index/Key variables, if any
|
|
* @param[in] defaultval Default value
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_scalar_get(clixon_handle h,
|
|
yang_stmt *ys,
|
|
cvec *cvk,
|
|
char *defaultval,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
cvec *nsc = NULL;
|
|
char *xpath = NULL;
|
|
cxobj *xt = NULL;
|
|
cxobj *xerr;
|
|
cxobj *x = NULL;
|
|
char *xmlstr = NULL;
|
|
u_char *snmpval = NULL;
|
|
size_t snmplen = 0;
|
|
int ret;
|
|
int asn1type;
|
|
char *reason = NULL;
|
|
netsnmp_variable_list *requestvb = request->requestvb;
|
|
cxobj *xcache = NULL;
|
|
char *body = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
/* Prepare backend call by constructing namespace context */
|
|
if (xml_nsctx_yang(ys, &nsc) < 0)
|
|
goto done;
|
|
/* Create xpath from yang */
|
|
if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
|
|
goto done;
|
|
if (type_yang2asn1(ys, &asn1type, 1) < 0)
|
|
goto done;
|
|
/* First try cache */
|
|
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
|
|
if (xcache==NULL || (x = xpath_first(xcache, nsc, "%s", xpath)) == NULL){
|
|
/* If not found do the backend call */
|
|
if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
|
|
goto done;
|
|
/* Detect error XXX Error handling could improve */
|
|
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
|
|
if (clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get configuration") < 0)
|
|
goto done;
|
|
goto done;
|
|
}
|
|
x = xpath_first(xt, nsc, "%s", xpath);
|
|
}
|
|
/*
|
|
* The xml to snmp value conversion is done in two steps:
|
|
* 1. From XML to SNMP string, there is a special case for enumeration, and for default value
|
|
* 2. From SNMP string to SNMP binary value which invloves parsing
|
|
*/
|
|
if (x != NULL && (body = xml_body(x)) != NULL){
|
|
if ((ret = type_xml2snmp_pre(body, ys, &xmlstr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGVALUE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
}
|
|
else if (defaultval != NULL){
|
|
if ((xmlstr = strdup(defaultval)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
else{
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
if ((ret = type_xml2snmp(xmlstr, &asn1type, &snmpval, &snmplen, &reason)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s %s", __FUNCTION__, reason);
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
goto ok;
|
|
}
|
|
/* see snmplib/snmp_client. somewhat indirect
|
|
*/
|
|
if ((ret = snmp_set_var_typed_value(requestvb, asn1type, snmpval, snmplen)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "snmp_set_var_typed_value");
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (reason)
|
|
free(reason);
|
|
if (xmlstr)
|
|
free(xmlstr);
|
|
if (snmpval)
|
|
free(snmpval);
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (xpath)
|
|
free(xpath);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
return retval;
|
|
}
|
|
|
|
/*! Yang 2 xml via api-path lib functions
|
|
*/
|
|
int
|
|
snmp_yang2xml(cxobj *xtop,
|
|
yang_stmt *ys,
|
|
cvec *cvk,
|
|
cxobj **xbot)
|
|
{
|
|
int retval = -1;
|
|
cvec *cvk1 = NULL;
|
|
char *api_path = NULL;
|
|
char *api_path_fmt = NULL;
|
|
int i;
|
|
int ret;
|
|
yang_stmt *yspec;
|
|
|
|
yspec = ys_spec(ys);
|
|
if (yang2api_path_fmt(ys, 0, &api_path_fmt) < 0)
|
|
goto done;
|
|
/* Need to prepend an element to fit api_path_fmt2api_path cvv parameter */
|
|
if ((cvk1 = cvec_new(1)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_new");
|
|
goto done;
|
|
}
|
|
for (i=0; i<cvec_len(cvk); i++)
|
|
cvec_append_var(cvk1, cvec_i(cvk,i));
|
|
if (api_path_fmt2api_path(api_path_fmt, cvk1, &api_path, NULL) < 0)
|
|
goto done;
|
|
if ((ret = api_path2xml(api_path, yspec, xtop, YC_DATANODE, 1, xbot, NULL, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_err(OE_XML, 0, "api_path2xml %s invalid", api_path);
|
|
goto done;
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (api_path_fmt)
|
|
free(api_path_fmt);
|
|
if (api_path)
|
|
free(api_path);
|
|
if (cvk1)
|
|
cvec_free(cvk1);
|
|
return retval;
|
|
}
|
|
|
|
/*! Scalar handler, get a value from clixon
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang node
|
|
* @param[in] cvk Vector of index/Key variables, if any
|
|
* @param[in] valstr0 Pre-set value string, ignore requestvb
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @note contains special logic for rowstatus handling
|
|
*/
|
|
static int
|
|
snmp_scalar_set(clixon_handle h,
|
|
yang_stmt *ys,
|
|
cvec *cvk,
|
|
char *valstr0,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xtop = NULL;
|
|
cxobj *xbot = NULL;
|
|
cxobj *xb;
|
|
int ret;
|
|
char *valstr = NULL;
|
|
cbuf *cb = NULL;
|
|
netsnmp_variable_list *requestvb = request->requestvb;
|
|
int asn1_type;
|
|
enum operation_type op = OP_MERGE;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
|
|
goto done;
|
|
if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
|
|
goto done;
|
|
/* Extended */
|
|
if (type_yang2asn1(ys, &asn1_type, 1) < 0)
|
|
goto done;
|
|
if (valstr0){
|
|
if (xml_value_set(xb, valstr0) < 0)
|
|
goto done;
|
|
}
|
|
else {
|
|
if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto ok;
|
|
if (xml_value_set(xb, valstr) < 0)
|
|
goto done;
|
|
}
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (clixon_xml2cbuf(cb, xtop, 0, 0, NULL, -1, 0) < 0)
|
|
goto done;
|
|
if (clicon_rpc_edit_config(h, "candidate", op, cbuf_get(cb)) < 0)
|
|
goto done;
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (xtop)
|
|
xml_free(xtop);
|
|
if (valstr)
|
|
free(valstr);
|
|
return retval;
|
|
}
|
|
|
|
/* Make cache row operation: move to backend, remove altogether
|
|
*
|
|
* Remove row from cache, then make merge or delete operation on backend.
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yp Yang statement of list / parent of leaf
|
|
* @param[in] cvk Vector of index/Key variables
|
|
* @param[in] opstr Operation on row: merge or delete
|
|
* @param[in] rpc If 0: do not make backend RPC, 1: do make backend RPC
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_cache_row_op(clixon_handle h,
|
|
yang_stmt *yp,
|
|
cvec *cvk,
|
|
char *opstr,
|
|
int rpc)
|
|
{
|
|
int retval = -1;
|
|
char *xpath;
|
|
cxobj *xrow = NULL;
|
|
cvec *nsc = NULL;
|
|
cbuf *cb = NULL;
|
|
cxobj *xtop = NULL;
|
|
cxobj *xbot = NULL;
|
|
cxobj *xa;
|
|
cxobj *xcache;
|
|
|
|
if (xml_nsctx_yang(yp, &nsc) < 0)
|
|
goto done;
|
|
/* Create xpath from yang */
|
|
if (snmp_yang2xpath(yp, cvk, &xpath) < 0)
|
|
goto done;
|
|
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
|
|
if (xcache && (xrow = xpath_first(xcache, nsc, "%s", xpath)) != NULL){
|
|
if (xml_rm(xrow) < 0)
|
|
goto done;
|
|
if (rpc){
|
|
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (snmp_yang2xml(xtop, yang_parent_get(yp), cvk, &xbot) < 0)
|
|
goto done;
|
|
if ((xa = xml_new("operation", xrow, CX_ATTR)) == NULL)
|
|
goto done;
|
|
if (xml_value_set(xa, opstr) < 0)
|
|
goto done;
|
|
if (xml_namespace_change(xa, NETCONF_BASE_NAMESPACE, NETCONF_BASE_PREFIX) < 0)
|
|
goto done;
|
|
if (xml_addsub(xbot, xrow) < 0)
|
|
goto done;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
if (clixon_xml2cbuf(cb, xtop, 0, 0, NULL, -1, 0) < 0)
|
|
goto done;
|
|
if (clicon_rpc_edit_config(h, "candidate", OP_NONE, cbuf_get(cb)) < 0)
|
|
goto done;
|
|
}
|
|
else
|
|
xml_free(xrow);
|
|
}
|
|
retval = 0;
|
|
done:
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
if (xtop)
|
|
xml_free(xtop);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
}
|
|
|
|
/*! Set internal cache instead of RPC to backend
|
|
*
|
|
* Always applies to rowstatus settings, as well as rows with notInService(2) status
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yp Yang statement of list / parent of leaf
|
|
* @param[in] cvk Vector of index/Key variables
|
|
* @param[in] rowstatus Existing value of rowstatus
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_cache_set(clixon_handle h,
|
|
yang_stmt *ys,
|
|
cvec *cvk,
|
|
int rowstatus,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
cxobj *xtop = NULL;
|
|
cxobj *xbot = NULL;
|
|
cxobj *xb;
|
|
int ret;
|
|
char *valstr = NULL;
|
|
netsnmp_variable_list *requestvb = request->requestvb;
|
|
int asn1_type;
|
|
yang_stmt *yspec;
|
|
int isrowstatus = 0;
|
|
cxobj *xcache = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
|
clixon_err(OE_FATAL, 0, "No DB_SPEC");
|
|
goto done;
|
|
}
|
|
if ((xtop = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
if (snmp_yang2xml(xtop, ys, cvk, &xbot) < 0)
|
|
goto done;
|
|
if ((xb = xml_new("body", xbot, CX_BODY)) == NULL)
|
|
goto done;
|
|
/* Extended */
|
|
if (type_yang2asn1(ys, &asn1_type, 1) < 0)
|
|
goto done;
|
|
if (asn1_type == CLIXON_ASN_ROWSTATUS)
|
|
isrowstatus++;
|
|
if ((ret = type_snmp2xml(ys, &asn1_type, requestvb, reqinfo, request, &valstr)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto ok;
|
|
if (isrowstatus){
|
|
/* Special case translation of rowstatus values:
|
|
* createAndGo -> active, createAndWait -> notInService
|
|
*/
|
|
if (strcmp(valstr, "createAndGo") == 0){
|
|
free(valstr);
|
|
if ((valstr = strdup("active")) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
else if (strcmp(valstr, "createAndWait") == 0){
|
|
free(valstr);
|
|
if ((valstr = strdup("notInService")) == NULL){
|
|
clixon_err(OE_UNIX, errno, "strdup");
|
|
goto done;
|
|
}
|
|
}
|
|
else if (strcmp(valstr, "active") == 0){
|
|
if (rowstatus == 5) /* createAndWait */
|
|
if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "merge", 1) < 0)
|
|
goto done;
|
|
}
|
|
else if (strcmp(valstr, "destroy") == 0){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s %d", __FUNCTION__, rowstatus);
|
|
/* Dont send delete to backend if notInService(2) */
|
|
if (snmp_cache_row_op(h, yang_parent_get(ys), cvk, "delete", rowstatus!=2) < 0)
|
|
goto done;
|
|
}
|
|
}
|
|
if (xml_value_set(xb, valstr) < 0)
|
|
goto done;
|
|
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xcache);
|
|
if (xcache == NULL){
|
|
if ((xcache = xml_new(NETCONF_INPUT_CONFIG, NULL, CX_ELMNT)) == NULL)
|
|
goto done;
|
|
clicon_ptr_set(h, "snmp-rowstatus-tree", xcache);
|
|
}
|
|
if (!isrowstatus || strcmp(valstr, "destroy") != 0)
|
|
if (xml_merge(xcache, xtop, yspec, NULL) < 0)
|
|
goto done;
|
|
/* Special case: push active to clixon */
|
|
if (isrowstatus && strcmp(valstr, "active") == 0){
|
|
if (snmp_scalar_set(h, ys,
|
|
cvk,
|
|
valstr,
|
|
reqinfo,
|
|
request) < 0)
|
|
goto done;
|
|
}
|
|
ok:
|
|
retval = 0;
|
|
done:
|
|
if (xtop)
|
|
xml_free(xtop);
|
|
if (valstr)
|
|
free(valstr);
|
|
return retval;
|
|
}
|
|
|
|
/*! Specialized get-config to get value of row-status, if any
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ys Yang node
|
|
* @param[in] cvk Vector of index/Key variables, if any
|
|
* @param[out] rowstatus Enmu rowstatus: 0 invalid, 1 active, etc
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_table_rowstatus_get(clixon_handle h,
|
|
yang_stmt *ys,
|
|
yang_stmt *yrestype,
|
|
cvec *cvk,
|
|
int32_t *rowstatus)
|
|
{
|
|
int retval = -1;
|
|
cvec *nsc = NULL;
|
|
cxobj *xt = NULL;
|
|
char *xpath = NULL;
|
|
cxobj *xr;
|
|
int ret;
|
|
char *body;
|
|
char *intstr;
|
|
char *reason = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
/* Prepare backend call by constructing namespace context */
|
|
if (xml_nsctx_yang(ys, &nsc) < 0)
|
|
goto done;
|
|
/* Create xpath from yang */
|
|
if (snmp_yang2xpath(ys, cvk, &xpath) < 0)
|
|
goto done;
|
|
clicon_ptr_get(h, "snmp-rowstatus-tree", (void**)&xt);
|
|
if (xt && (xr = xpath_first(xt, nsc, "%s", xpath)) != NULL &&
|
|
(body = xml_body(xr)) != NULL) {
|
|
if ((ret = yang_enum2valstr(yrestype, body, &intstr)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s %s invalid or not found", __FUNCTION__, body);
|
|
*rowstatus = 0;
|
|
}
|
|
else {
|
|
if ((ret = parse_int32(intstr, rowstatus, &reason)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s parse_int32: %s", __FUNCTION__, reason);
|
|
*rowstatus = 0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
*rowstatus = 0;
|
|
retval = 0;
|
|
done:
|
|
if (xpath)
|
|
free(xpath);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
if (reason)
|
|
free(reason);
|
|
return retval;
|
|
}
|
|
|
|
/*! Single SNMP Scalar operation handler
|
|
*
|
|
* @param[in] handler Registered MIB handler structure
|
|
* @param[in] nhreg Root registration info.
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
clixon_snmp_scalar_handler1(netsnmp_mib_handler *handler,
|
|
netsnmp_handler_registration *nhreg,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
clixon_snmp_handle *sh = NULL;
|
|
int asn1_type;
|
|
netsnmp_variable_list *requestvb = request->requestvb;
|
|
int ret;
|
|
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__);
|
|
if (snmp_common_handler(handler, nhreg, reqinfo, request, 0, &sh) < 0)
|
|
goto done;
|
|
/* see net-snmp/agent/snmp_agent.h / net-snmp/library/snmp.h */
|
|
switch (reqinfo->mode) {
|
|
case MODE_GET: /* 160 */
|
|
if (snmp_scalar_get(sh->sh_h, sh->sh_ys, sh->sh_cvk_orig,
|
|
sh->sh_default, reqinfo, request) < 0)
|
|
goto done;
|
|
break;
|
|
case MODE_GETNEXT: /* 161 */
|
|
break;
|
|
case MODE_SET_RESERVE1: /* 0 */
|
|
if (!yang_config_ancestor(sh->sh_ys) ||
|
|
nhreg->modes == HANDLER_CAN_RONLY){
|
|
netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE);
|
|
goto done;
|
|
}
|
|
/* Translate from YANG ys leaf type to SNMP asn1.1 type ids (not value), also cvtype */
|
|
if (type_yang2asn1(sh->sh_ys, &asn1_type, 0) < 0)
|
|
goto done;
|
|
if (requestvb->type != asn1_type){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto ok;
|
|
}
|
|
}
|
|
break;
|
|
case MODE_SET_RESERVE2: /* 1 */
|
|
break;
|
|
case MODE_SET_ACTION: /* 2 */
|
|
if (snmp_scalar_set(sh->sh_h, sh->sh_ys, NULL, NULL, reqinfo, request) < 0)
|
|
goto done;
|
|
/*
|
|
* There does not seem to be a separate validation action and commit does not
|
|
* return an error.
|
|
* Therefore validation is done here directly as well as discard if it fails.
|
|
*/
|
|
if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clicon_rpc_discard_changes(sh->sh_h);
|
|
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
|
|
goto done;
|
|
}
|
|
break;
|
|
case MODE_SET_COMMIT: /* 3 */
|
|
if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
/* Note that error given in commit is not propagated to the snmp client,
|
|
* therefore validation is in the ACTION instead
|
|
*/
|
|
clicon_rpc_discard_changes(sh->sh_h);
|
|
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
|
|
goto done;
|
|
}
|
|
break;
|
|
case MODE_SET_FREE: /* 4 */
|
|
break;
|
|
case MODE_SET_UNDO: /* 5 */
|
|
if (clicon_rpc_discard_changes(sh->sh_h) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
ok:
|
|
retval = SNMP_ERR_NOERROR;
|
|
done:
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s retval:%d", __FUNCTION__, retval);
|
|
return retval;
|
|
}
|
|
|
|
/*! Top level scalar request handler, loop over individual request
|
|
*
|
|
* @param[in] handler Registered MIB handler structure
|
|
* @param[in] nhreg Root registration info.
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] requests The netsnmp request info structure.
|
|
* @retval SNMP_ERR_NOERROR OK
|
|
* @retval -1 Error
|
|
* @see clixon_snmp_table_handler
|
|
*/
|
|
int
|
|
clixon_snmp_scalar_handler(netsnmp_mib_handler *handler,
|
|
netsnmp_handler_registration *nhreg,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *requests)
|
|
{
|
|
int retval = -1;
|
|
netsnmp_request_info *req;
|
|
int ret;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
for (req = requests; req; req = req->next){
|
|
ret = clixon_snmp_scalar_handler1(handler, nhreg, reqinfo, req);
|
|
if (ret != SNMP_ERR_NOERROR){
|
|
retval = ret;
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
retval = SNMP_ERR_NOERROR;
|
|
done:
|
|
return retval;
|
|
}
|
|
|
|
/*! Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name
|
|
*
|
|
* Get yang of leaf from first part of OID
|
|
* Create xpath with right keys from later part of OID
|
|
* Query clixon if object exists, if so return value
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yt Yang of table (of list type)
|
|
* @param[in] oidt OID of registered top container object (above list), may be different from yt
|
|
* @param[in] oidtlen OID length of list object OID
|
|
* @param[in] oids OID of ultimate scalar value
|
|
* @param[in] oidslen OID length of scalar
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 1 OK
|
|
* @retval 0 Object not found
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_table_get(clixon_handle h,
|
|
yang_stmt *yt,
|
|
oid *oidt,
|
|
size_t oidtlen,
|
|
oid *oids,
|
|
size_t oidslen,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
|
|
size_t oidleaflen = MAX_OID_LEN;
|
|
oid *oidi;
|
|
size_t oidilen;
|
|
yang_stmt *ys;
|
|
yang_stmt *yk;
|
|
char *xpath = NULL;
|
|
cvec *cvk_orig;
|
|
cvec *cvk_val = NULL;
|
|
int i;
|
|
cg_var *cv;
|
|
char *defaultval = NULL;
|
|
int ret;
|
|
|
|
/* Get yang of leaf from first part of OID */
|
|
ys = NULL;
|
|
while ((ys = yn_each(yt, ys)) != NULL) {
|
|
if (yang_keyword_get(ys) != Y_LEAF)
|
|
continue;
|
|
/* reset oid */
|
|
oidleaflen = MAX_OID_LEN;
|
|
if ((ret = yangext_oid_get(ys, oidleaf, &oidleaflen, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue;
|
|
if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
|
|
continue;
|
|
if (oids[oidleaflen-1] == oidleaf[oidleaflen-1])
|
|
break;
|
|
}
|
|
if (ys == NULL){
|
|
/* No leaf with matching OID */
|
|
goto fail;
|
|
}
|
|
/* SMI default value, How is this different from yang defaults?
|
|
*/
|
|
if (yang_extension_value_opt(ys, "smiv2:defval", NULL, &defaultval) < 0)
|
|
goto done;
|
|
|
|
/* Create xpath with right keys from later part of OID
|
|
* Inverse of snmp_str2oid
|
|
*/
|
|
if ((cvk_orig = yang_cvec_get(yt)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No keys");
|
|
goto done;
|
|
}
|
|
if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_dup");
|
|
goto done;
|
|
}
|
|
/* read through keys and create cvk */
|
|
oidilen = oidslen-(oidtlen+1);
|
|
oidi = oids+oidtlen+1;
|
|
/* Add keys */
|
|
for (i=0; i<cvec_len(cvk_val); i++){
|
|
cv = cvec_i(cvk_val, i);
|
|
if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
|
|
clixon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
|
|
goto done;
|
|
}
|
|
if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0)
|
|
goto done;
|
|
}
|
|
if (oidilen != 0){
|
|
clixon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
|
|
goto fail;
|
|
}
|
|
/* Get scalar value */
|
|
if (snmp_scalar_get(h, ys, cvk_val,
|
|
defaultval,
|
|
reqinfo,
|
|
request) < 0)
|
|
goto done;
|
|
// ok:
|
|
retval = 1;
|
|
done:
|
|
if (cvk_val)
|
|
cvec_free(cvk_val);
|
|
if (xpath)
|
|
free(xpath);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Set value in table
|
|
*
|
|
* Get yang of leaf from first part of OID
|
|
* Create xpath with right keys from later part of OID
|
|
* Query clixon if object exists, if so return value
|
|
* @param[in] h Clixon handle
|
|
* @param[in] yt Yang of table (of list type)
|
|
* @param[in] oids OID of ultimate scalar value
|
|
* @param[in] oidslen OID length of scalar
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @param[out] err Error code if failed (retval = 0)
|
|
* @retval 1 OK
|
|
* @retval 0 Failed, err set
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
snmp_table_set(clixon_handle h,
|
|
yang_stmt *yt,
|
|
oid *oids,
|
|
size_t oidslen,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request,
|
|
int *err)
|
|
{
|
|
int retval = -1;
|
|
oid oidt[MAX_OID_LEN] = {0,}; /* Table / list oid */
|
|
size_t oidtlen = MAX_OID_LEN;
|
|
oid oidleaf[MAX_OID_LEN] = {0,}; /* Leaf */
|
|
size_t oidleaflen = MAX_OID_LEN;
|
|
oid *oidi;
|
|
size_t oidilen;
|
|
yang_stmt *yi;
|
|
yang_stmt *ys;
|
|
yang_stmt *yrowst;
|
|
yang_stmt *yk;
|
|
yang_stmt *yrestype = NULL;
|
|
char *xpath = NULL;
|
|
cvec *cvk_orig;
|
|
cvec *cvk_val = NULL;
|
|
int i;
|
|
cg_var *cv;
|
|
int ret;
|
|
int asn1_type;
|
|
netsnmp_variable_list *requestvb;
|
|
int rowstatus = 0;
|
|
char *origtype;
|
|
|
|
/* Get OID from table /list */
|
|
if ((ret = yangext_oid_get(yt, oidt, &oidtlen, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
goto done;
|
|
/* Get yang of leaf from first part of OID
|
|
* and also leaf with rowstatus type
|
|
*/
|
|
ys = NULL;
|
|
yrowst = NULL;
|
|
yi = NULL;
|
|
while ((yi = yn_each(yt, yi)) != NULL) {
|
|
if (yang_keyword_get(yi) != Y_LEAF)
|
|
continue;
|
|
/* reset oid */
|
|
oidleaflen = MAX_OID_LEN;
|
|
if ((ret = yangext_oid_get(yi, oidleaf, &oidleaflen, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue;
|
|
if (oidtlen + 1 != oidleaflen) /* Indexes may be from other OID scope, skip those */
|
|
continue;
|
|
if (oids[oidleaflen-1] == oidleaf[oidleaflen-1]){
|
|
ys = yi;
|
|
}
|
|
origtype = NULL;
|
|
if (snmp_yang_type_get(yi, NULL, &origtype, &yrestype, NULL) < 0)
|
|
goto done;
|
|
if (strcmp(origtype, "RowStatus") == 0){
|
|
yrowst = yi;
|
|
}
|
|
if (origtype){
|
|
free(origtype);
|
|
origtype = NULL;
|
|
}
|
|
}
|
|
if (ys == NULL){
|
|
/* No leaf with matching OID */
|
|
*err = SNMP_NOSUCHOBJECT;
|
|
goto fail;
|
|
}
|
|
if (!yang_config_ancestor(ys)){
|
|
*err = SNMP_ERR_NOTWRITABLE;
|
|
goto fail;
|
|
}
|
|
{
|
|
char *modes_str = NULL;
|
|
int modes;
|
|
|
|
if (yang_extension_value_opt(ys, "smiv2:max-access", NULL, &modes_str) < 0)
|
|
goto done;
|
|
if (modes_str){
|
|
modes = snmp_access_str2int(modes_str);
|
|
if (modes == HANDLER_CAN_RONLY){
|
|
*err = SNMP_ERR_NOTWRITABLE;
|
|
goto fail;
|
|
}
|
|
}
|
|
}
|
|
if (type_yang2asn1(ys, &asn1_type, 0) < 0)
|
|
goto done;
|
|
requestvb = request->requestvb;
|
|
if (requestvb->type != asn1_type){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s Expected type:%d, got: %d", __FUNCTION__, requestvb->type, asn1_type);
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_WRONGTYPE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto ok;
|
|
}
|
|
}
|
|
/* Create xpath with right keys from later part of OID
|
|
* Inverse of snmp_str2oid
|
|
*/
|
|
if ((cvk_orig = yang_cvec_get(yt)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No keys");
|
|
goto done;
|
|
}
|
|
if ((cvk_val = cvec_dup(cvk_orig)) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cvec_dup");
|
|
goto done;
|
|
}
|
|
/* read through keys and create cvk */
|
|
oidilen = oidslen-(oidtlen+1);
|
|
oidi = oids+oidtlen+1;
|
|
/* Add keys */
|
|
for (i=0; i<cvec_len(cvk_val); i++){
|
|
cv = cvec_i(cvk_val, i);
|
|
if ((yk = yang_find(yt, Y_LEAF, cv_string_get(cv))) == NULL){
|
|
clixon_err(OE_YANG, 0, "List key %s not found", cv_string_get(cv));
|
|
goto done;
|
|
}
|
|
if (snmp_oid2str(&oidi, &oidilen, yk, cv) < 0)
|
|
goto done;
|
|
}
|
|
if (oidilen != 0){
|
|
clixon_err(OE_YANG, 0, "Expected oidlen 0 but is %zu", oidilen);
|
|
*err = SNMP_NOSUCHOBJECT;
|
|
goto fail;
|
|
}
|
|
if (yrowst){
|
|
/* Get rowstatus from cache */
|
|
if ((ret = snmp_table_rowstatus_get(h,
|
|
yrowst,
|
|
yrestype,
|
|
cvk_val,
|
|
&rowstatus)) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
/* If no rowstatus object, default to active */
|
|
rowstatus = 1;
|
|
}
|
|
if (ys == yrowst){
|
|
/* Set a rowstatus value to cache */
|
|
if (snmp_cache_set(h, ys,
|
|
cvk_val,
|
|
rowstatus,
|
|
reqinfo,
|
|
request) < 0)
|
|
goto done;
|
|
}
|
|
else{
|
|
switch (rowstatus){
|
|
case 0: /* not found, invalid */
|
|
case 3: /* notReady */
|
|
case 6: /* destroy */
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_ERR_INCONSISTENTVALUE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto ok;
|
|
}
|
|
break;
|
|
case 1: /* active */
|
|
case 4: /* createAndGo */
|
|
/* Set backend */
|
|
if (snmp_scalar_set(h, ys,
|
|
cvk_val,
|
|
NULL,
|
|
reqinfo,
|
|
request) < 0)
|
|
goto done;
|
|
break;
|
|
case 2: /* notInService */
|
|
case 5: /* createAndWait */
|
|
/* Set cache */
|
|
if (snmp_cache_set(h, ys,
|
|
cvk_val,
|
|
rowstatus,
|
|
reqinfo,
|
|
request) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
ok:
|
|
retval = 1;
|
|
done:
|
|
if (cvk_val)
|
|
cvec_free(cvk_val);
|
|
if (xpath)
|
|
free(xpath);
|
|
if (origtype)
|
|
free(origtype);
|
|
return retval;
|
|
fail:
|
|
retval = 0;
|
|
goto done;
|
|
}
|
|
|
|
/*! Find "next" object from oids minus key and return that.
|
|
*
|
|
* @param[in] h Clixon handle
|
|
* @param[in] ylist Yang of table (of list type)
|
|
* @param[in] oids OID of ultimate scalar value
|
|
* @param[in] oidslen OID length of scalar
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 1 OK
|
|
* @retval 0 Failed
|
|
* @retval -1 Error
|
|
* XXX: merge with cache
|
|
*/
|
|
static int
|
|
snmp_table_getnext(clixon_handle h,
|
|
yang_stmt *ylist,
|
|
oid *oids,
|
|
size_t oidslen,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
cvec *nsc = NULL;
|
|
char *xpath = NULL;
|
|
cxobj *xt = NULL;
|
|
cxobj *xerr;
|
|
cxobj *xtable;
|
|
cxobj *xrow;
|
|
cxobj *xcol;
|
|
yang_stmt *ycol;
|
|
yang_stmt *ys;
|
|
int ret;
|
|
cvec *cvk_name;
|
|
oid oidc[MAX_OID_LEN] = {0,}; /* Table / list oid */
|
|
size_t oidclen = MAX_OID_LEN;
|
|
oid oidk[MAX_OID_LEN] = {0,}; /* Key oid */
|
|
size_t oidklen = MAX_OID_LEN;
|
|
oid oidnext[MAX_OID_LEN] = {0x7fffffff,}; /* Next oid: start with high value */
|
|
size_t oidnextlen = MAX_OID_LEN;
|
|
int found = 0;
|
|
cxobj *xnext = NULL;
|
|
yang_stmt *ynext = NULL;
|
|
cbuf *cb = NULL;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
if ((ys = yang_parent_get(ylist)) == NULL ||
|
|
yang_keyword_get(ys) != Y_CONTAINER){
|
|
clixon_err(OE_YANG, EINVAL, "ylist parent is not list");
|
|
goto done;
|
|
}
|
|
if (xml_nsctx_yang(ys, &nsc) < 0)
|
|
goto done;
|
|
if (snmp_yang2xpath(ys, NULL, &xpath) < 0)
|
|
goto done;
|
|
if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, NULL, &xt) < 0)
|
|
goto done;
|
|
if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){
|
|
clixon_err_netconf(h, OE_NETCONF, 0, xerr, "Get configuration");
|
|
goto done;
|
|
}
|
|
if ((xtable = xpath_first(xt, nsc, "%s", xpath)) != NULL) {
|
|
/* Make a clone of key-list, but replace names with values */
|
|
if ((cvk_name = yang_cvec_get(ylist)) == NULL){
|
|
clixon_err(OE_YANG, 0, "No keys");
|
|
goto done;
|
|
}
|
|
xrow = NULL;
|
|
while ((xrow = xml_child_each(xtable, xrow, CX_ELMNT)) != NULL) {
|
|
/* Get key part of OID from XML list entry */
|
|
if ((ret = snmp_xmlkey2val_oid(xrow, cvk_name, NULL, /*&cvk_oid,*/ oidk, &oidklen)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue; /* skip row, not all indexes */
|
|
xcol = NULL;
|
|
while ((xcol = xml_child_each(xrow, xcol, CX_ELMNT)) != NULL) {
|
|
if ((ycol = xml_spec(xcol)) == NULL)
|
|
continue;
|
|
if (yang_keyword_get(ycol) != Y_LEAF)
|
|
continue;
|
|
if ((ret = yangext_oid_get(ycol, oidc, &oidclen, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0)
|
|
continue;
|
|
/* Append key oid */
|
|
if (oid_append(oidc, &oidclen, oidk, oidklen) < 0)
|
|
goto done;
|
|
/* Get smallest larger */
|
|
if (oid_eq(oidc, oidclen, oids, oidslen) > 0 &&
|
|
oid_eq(oidc, oidclen, oidnext, oidnextlen) < 0){
|
|
memcpy(oidnext, oidc, oidclen*sizeof(*oidnext));
|
|
oidnextlen = oidclen;
|
|
xnext = xcol;
|
|
ynext = ycol;
|
|
found++;
|
|
}
|
|
} /* while xcol */
|
|
} /* while xrow */
|
|
}
|
|
if (found){
|
|
if (snmp_scalar_return(xnext, ynext, oidnext, oidnextlen, reqinfo, request) < 0)
|
|
goto done;
|
|
if ((cb = cbuf_new()) == NULL){
|
|
clixon_err(OE_UNIX, errno, "cbuf_new");
|
|
goto done;
|
|
}
|
|
oid_cbuf(cb, oidnext, oidnextlen);
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s next: %s", __FUNCTION__, cbuf_get(cb));
|
|
}
|
|
retval = found;
|
|
done:
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (xpath)
|
|
free(xpath);
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
return retval;
|
|
}
|
|
|
|
/*! SNMP table operation handler
|
|
*
|
|
* @param[in] handler Registered MIB handler structure
|
|
* @param[in] nhreg Root registration info.
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] request The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
*/
|
|
static int
|
|
clixon_snmp_table_handler1(netsnmp_mib_handler *handler,
|
|
netsnmp_handler_registration *nhreg,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *request)
|
|
{
|
|
int retval = -1;
|
|
clixon_snmp_handle *sh = NULL;
|
|
cvec *nsc = NULL;
|
|
cxobj *xt = NULL;
|
|
cbuf *cb = NULL;
|
|
int ret;
|
|
netsnmp_variable_list *requestvb;
|
|
int err = 0;
|
|
|
|
clixon_debug(CLIXON_DBG_DETAIL, "%s", __FUNCTION__);
|
|
if ((ret = snmp_common_handler(handler, nhreg, reqinfo, request, 1, &sh)) < 0)
|
|
goto done;
|
|
if (sh->sh_ys == NULL){
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s Error table not registered", __FUNCTION__);
|
|
goto ok;
|
|
}
|
|
requestvb = request->requestvb;
|
|
switch(reqinfo->mode){
|
|
case MODE_GET: // 160
|
|
/* Create xpath from YANG table OID + 1 + n + cvk/key = requestvb->name
|
|
*/
|
|
if ((ret = snmp_table_get(sh->sh_h, sh->sh_ys,
|
|
sh->sh_oid2, sh->sh_oid2len,
|
|
requestvb->name, requestvb->name_length,
|
|
reqinfo, request)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHINSTANCE)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s Nosuchinstance", __FUNCTION__);
|
|
}
|
|
break;
|
|
case MODE_GETNEXT: // 161
|
|
/* Register table sub-oid:s of existing entries in clixon */
|
|
if ((ret = snmp_table_getnext(sh->sh_h, sh->sh_ys,
|
|
requestvb->name, requestvb->name_length,
|
|
reqinfo, request)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((ret = netsnmp_request_set_error(request, SNMP_NOSUCHOBJECT)) != SNMPERR_SUCCESS){
|
|
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s No such object", __FUNCTION__);
|
|
}
|
|
break;
|
|
case MODE_SET_RESERVE1: // 0
|
|
if (!yang_config_ancestor(sh->sh_ys)){
|
|
netsnmp_request_set_error(request, SNMP_ERR_NOTWRITABLE);
|
|
goto done;
|
|
}
|
|
// Check types: compare type in requestvb to yang type (or do later)
|
|
break;
|
|
case MODE_SET_RESERVE2: // 1
|
|
break;
|
|
case MODE_SET_ACTION: // 2
|
|
if ((ret = snmp_table_set(sh->sh_h, sh->sh_ys,
|
|
requestvb->name, requestvb->name_length,
|
|
reqinfo, request, &err)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
if ((ret = netsnmp_request_set_error(request, err)) != SNMPERR_SUCCESS){
|
|
clixon_err(OE_SNMP, ret, "netsnmp_request_set_error");
|
|
goto done;
|
|
}
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s Nosuchinstance", __FUNCTION__);
|
|
}
|
|
/*
|
|
* There does not seem to be a separate validation action and commit does not
|
|
* return an error.
|
|
* Therefore validation is done here directly as well as discard if it fails.
|
|
*/
|
|
if ((ret = clicon_rpc_validate(sh->sh_h, "candidate")) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clicon_rpc_discard_changes(sh->sh_h);
|
|
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
|
|
goto done;
|
|
}
|
|
break;
|
|
case MODE_SET_COMMIT: // 3
|
|
if ((ret = clicon_rpc_commit(sh->sh_h, 0, 0, 0, NULL, NULL)) < 0)
|
|
goto done;
|
|
if (ret == 0){
|
|
clicon_rpc_discard_changes(sh->sh_h);
|
|
netsnmp_request_set_error(request, SNMP_ERR_COMMITFAILED);
|
|
goto done;
|
|
}
|
|
break;
|
|
case MODE_SET_FREE: // 4
|
|
break;
|
|
case MODE_SET_UNDO : // 5
|
|
if (clicon_rpc_discard_changes(sh->sh_h) < 0)
|
|
goto done;
|
|
break;
|
|
}
|
|
ok:
|
|
retval = SNMP_ERR_NOERROR;
|
|
done:
|
|
if (xt)
|
|
xml_free(xt);
|
|
if (cb)
|
|
cbuf_free(cb);
|
|
if (nsc)
|
|
xml_nsctx_free(nsc);
|
|
return retval;
|
|
}
|
|
|
|
/*! Top level table request handler, loop over individual requests
|
|
*
|
|
* @param[in] handler Registered MIB handler structure
|
|
* @param[in] nhreg Root registration info.
|
|
* @param[in] reqinfo Agent transaction request structure
|
|
* @param[in] requests The netsnmp request info structure.
|
|
* @retval 0 OK
|
|
* @retval -1 Error
|
|
* @see clixon_snmp_scalar_handler
|
|
*/
|
|
int
|
|
clixon_snmp_table_handler(netsnmp_mib_handler *handler,
|
|
netsnmp_handler_registration *nhreg,
|
|
netsnmp_agent_request_info *reqinfo,
|
|
netsnmp_request_info *requests)
|
|
{
|
|
int retval = -1;
|
|
netsnmp_request_info *req;
|
|
int ret;
|
|
|
|
clixon_debug(CLIXON_DBG_DEFAULT, "%s", __FUNCTION__);
|
|
for (req = requests; req; req = req->next){
|
|
ret = clixon_snmp_table_handler1(handler, nhreg, reqinfo, req);
|
|
if (ret != SNMP_ERR_NOERROR){
|
|
retval = ret;
|
|
goto done;
|
|
break;
|
|
}
|
|
}
|
|
retval = SNMP_ERR_NOERROR;
|
|
done:
|
|
return retval;
|
|
}
|