Clixon SNMP frontend update

* Integration of testhandler.c gives proper callback handling
* YANG `clixon-config@2022-03-21.yang` changes:
    * Added option:
      * `CLICON_SNMP_AGENT_SOCK`
This commit is contained in:
Olof hagsand 2022-04-27 15:18:25 +02:00
parent 1e05207fd5
commit 625cc76bde
5 changed files with 447 additions and 57 deletions

View file

@ -34,12 +34,19 @@
* [3.3.2](#332) Aug 27 2017
* [3.3.1](#331) June 7 2017
### New features
## SNMP branch
* Clixon SNMP frontend
* net-snmp and MIB to YANG translation
* Experimental work
* YANG `clixon-config@2022-03-21.yang` changes:
* Added option:
* `CLICON_SNMP_AGENT_SOCK`
## 5.7.0
Expected: May 2022
### New features
* Extended the Restconf implementation with a limited http-data static service
* Added two new config options to clixon-config.yang:
* `CLICON_HTTP_DATA_PATH`

View file

@ -58,59 +58,413 @@
/* Command line options to be passed to getopt(3) */
#define SNMP_OPTS "hD:f:l:o:"
#if 1 // XXX hardcoded MIB object from https://github.com/net-snmp/subagent-example/blob/master/example-demon.c
/* cp NET-SNMP-TUTORIAL-MIB.txt ~/.snmp/mibs/
* sudo /usr/local/sbin/snmpd -Lo -C --rwcommunity=public --master=agentx -f
* sudo example_demon
* snmpget -v 2c -c public localhost NET-SNMP-TUTORIAL-MIB::nstAgentSubagentObject.0
*/
/*!
* our initialization routine, automatically called by the agent
* (to get called, the function name must match init_FILENAME())
* the variable we want to tie an OID to. The agent will handle all
* * GET and SET requests to this variable changing it's value as needed.
*/
static long nstAgentSubagentObject = 2;
#if 1 // XXX hardcoded from https://github.com/net-snmp/net-snmp/blob/master/agent/mibgroup/testhandler.c
static void
init_nstAgentSubagentObject(clicon_handle h)
Netsnmp_Node_Handler my_test_handler;
Netsnmp_Node_Handler my_test_table_handler;
Netsnmp_Node_Handler my_data_table_handler;
Netsnmp_Node_Handler my_test_instance_handler;
static oid my_test_oid[4] = { 1, 2, 3, 4 };
static oid my_table_oid[4] = { 1, 2, 3, 5 };
static oid my_instance_oid[5] = { 1, 2, 3, 6, 1 };
static oid my_data_table_oid[4] = { 1, 2, 3, 7 };
static oid my_data_ulong_instance[4] = { 1, 2, 3, 9 };
u_long my_ulong = 42;
void
init_testhandler(void)
{
static oid nstAgentSubagentObject_oid[] =
{ 1, 3, 6, 1, 4, 1, 8072, 2, 4, 1, 1, 2, 0 };
/*
* we're registering at .1.2.3.4
*/
netsnmp_handler_registration *my_test;
netsnmp_table_registration_info *table_info;
u_long ind1;
netsnmp_table_data *table;
netsnmp_table_row *row;
clicon_debug(1, "%s", __FUNCTION__);
/*
* basic handler test
*/
netsnmp_register_handler(netsnmp_create_handler_registration
("myTest", my_test_handler, my_test_oid, 4,
HANDLER_CAN_RONLY));
/*
* instance handler test
*/
netsnmp_register_instance(netsnmp_create_handler_registration
("myInstance", my_test_instance_handler,
my_instance_oid, 5, HANDLER_CAN_RWRITE));
netsnmp_register_ulong_instance("myulong",
my_data_ulong_instance, 4,
&my_ulong, NULL);
/*
* table helper test
*/
my_test = netsnmp_create_handler_registration("myTable",
my_test_table_handler,
my_table_oid, 4,
HANDLER_CAN_RONLY);
if (!my_test)
return;
table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
if (table_info == NULL)
return;
netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER, ASN_INTEGER,
0);
table_info->min_column = 3;
table_info->max_column = 3;
netsnmp_register_table(my_test, table_info);
/*
* data table helper test
*/
/*
* we'll construct a simple table here with two indexes: an
* integer and a string (why not). It'll contain only one
* column so the data pointer is merely the data in that
* column.
*/
table = netsnmp_create_table_data("data_table_test");
netsnmp_table_data_add_index(table, ASN_INTEGER);
netsnmp_table_data_add_index(table, ASN_OCTET_STR);
/*
* 1 partridge in a pear tree
*/
row = netsnmp_create_table_data_row();
ind1 = 1;
netsnmp_table_row_add_index(row, ASN_INTEGER, &ind1, sizeof(ind1));
netsnmp_table_row_add_index(row, ASN_OCTET_STR, "partridge",
strlen("partridge"));
row->data = NETSNMP_REMOVE_CONST(void *, "pear tree");
netsnmp_table_data_add_row(table, row);
/*
* 2 turtle doves
*/
row = netsnmp_create_table_data_row();
ind1 = 2;
netsnmp_table_row_add_index(row, ASN_INTEGER, &ind1, sizeof(ind1));
netsnmp_table_row_add_index(row, ASN_OCTET_STR, "turtle",
strlen("turtle"));
row->data = NETSNMP_REMOVE_CONST(void *, "doves");
netsnmp_table_data_add_row(table, row);
/*
* we're going to register it as a normal table too, so we get the
* automatically parsed column and index information
*/
table_info = SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info);
if (table_info == NULL)
return;
netsnmp_table_helper_add_indexes(table_info, ASN_INTEGER,
ASN_OCTET_STR, 0);
table_info->min_column = 3;
table_info->max_column = 3;
netsnmp_register_read_only_table_data(
netsnmp_create_handler_registration("12days", my_data_table_handler, my_data_table_oid, 4,
HANDLER_CAN_RONLY),
table, table_info);
}
int
my_test_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
oid myoid1[] = { 1, 2, 3, 4, 5, 6 };
static u_long accesses = 0;
clicon_debug(1, "%s", __FUNCTION__);
/*
* a debugging statement. Run the agent with -DnstAgentSubagentObject to see
* the output of this debugging statement.
* loop through requests
*/
DEBUGMSGTL(("nstAgentSubagentObject",
"Initializing the nstAgentSubagentObject module\n"));
while (requests) {
netsnmp_variable_list *var = requests->requestvb;
/*
* the line below registers our variables defined above as
* accessible and makes it writable. A read only version of any
* of these registration would merely call
* register_read_only_long_instance() instead. The functions
* called below should be consistent with your MIB, however.
*
* If we wanted a callback when the value was retrieved or set
* (even though the details of doing this are handled for you),
* you could change the NULL pointer below to a valid handler
* function.
*/
DEBUGMSGTL(("nstAgentSubagentObject",
"Initalizing nstAgentSubagentObject scalar integer. Default value = %ld\n",
nstAgentSubagentObject));
DEBUGMSGTL(("testhandler", " oid:"));
DEBUGMSGOID(("testhandler", var->name, var->name_length));
DEBUGMSG(("testhandler", "\n"));
netsnmp_register_long_instance("nstAgentSubagentObject",
nstAgentSubagentObject_oid,
OID_LENGTH(nstAgentSubagentObject_oid),
&nstAgentSubagentObject, NULL);
switch (reqinfo->mode) {
case MODE_GET:
if (netsnmp_oid_equals(var->name, var->name_length, myoid1, 6)
== 0) {
snmp_set_var_typed_value(var, ASN_INTEGER,
(u_char *) & accesses,
sizeof(accesses));
return SNMP_ERR_NOERROR;
}
break;
DEBUGMSGTL(("nstAgentSubagentObject",
"Done initalizing nstAgentSubagentObject module\n"));
case MODE_GETNEXT:
if (snmp_oid_compare(var->name, var->name_length, myoid1, 6)
< 0) {
snmp_set_var_objid(var, myoid1, 6);
snmp_set_var_typed_value(var, ASN_INTEGER,
(u_char *) & accesses,
sizeof(accesses));
return SNMP_ERR_NOERROR;
}
break;
default:
netsnmp_set_request_error(reqinfo, requests, SNMP_ERR_GENERR);
break;
}
requests = requests->next;
}
return SNMP_ERR_NOERROR;
}
#endif // XXX Hardcoded
/*
* functionally this is a simply a multiplication table for 12x12
*/
#define MAX_COLONE 12
#define MAX_COLTWO 12
#define RESULT_COLUMN 3
int
my_test_table_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
netsnmp_table_registration_info
*handler_reg_info =
(netsnmp_table_registration_info *) handler->prev->myvoid;
netsnmp_table_request_info *table_info;
u_long result;
int x, y;
DEBUGMSGTL(("testhandler", "Got request:\n"));
while (requests) {
netsnmp_variable_list *var = requests->requestvb;
if (requests->processed != 0)
continue;
DEBUGMSGTL(("testhandler_table", "Got request:\n"));
DEBUGMSGTL(("testhandler_table", " oid:"));
DEBUGMSGOID(("testhandler_table", var->name, var->name_length));
DEBUGMSG(("testhandler_table", "\n"));
table_info = netsnmp_extract_table_info(requests);
if (table_info == NULL) {
requests = requests->next;
continue;
}
switch (reqinfo->mode) {
case MODE_GETNEXT:
/*
* beyond our search range?
*/
if (table_info->colnum > RESULT_COLUMN)
break;
/*
* below our minimum column?
*/
if (table_info->colnum < RESULT_COLUMN ||
/*
* or no index specified
*/
table_info->indexes->val.integer == NULL) {
table_info->colnum = RESULT_COLUMN;
x = 0;
y = 0;
} else {
x = *(table_info->indexes->val.integer);
y = *(table_info->indexes->next_variable->val.integer);
}
if (table_info->number_indexes ==
handler_reg_info->number_indexes) {
y++; /* GETNEXT is basically just y+1 for this table */
if (y > MAX_COLTWO) { /* (with wrapping) */
y = 0;
x++;
}
}
if (x <= MAX_COLONE) {
result = x * y;
*(table_info->indexes->val.integer) = x;
*(table_info->indexes->next_variable->val.integer) = y;
netsnmp_table_build_result(reginfo, requests,
table_info, ASN_INTEGER,
(u_char *) & result,
sizeof(result));
}
break;
case MODE_GET:
if (var->type == ASN_NULL) { /* valid request if ASN_NULL */
/*
* is it the right column?
*/
if (table_info->colnum == RESULT_COLUMN &&
/*
* and within the max boundries?
*/
*(table_info->indexes->val.integer) <= MAX_COLONE &&
*(table_info->indexes->next_variable->val.integer)
<= MAX_COLTWO) {
/*
* then, the result is column1 * column2
*/
result = *(table_info->indexes->val.integer) *
*(table_info->indexes->next_variable->val.integer);
snmp_set_var_typed_value(var, ASN_INTEGER,
(u_char *) & result,
sizeof(result));
}
}
break;
}
requests = requests->next;
}
return SNMP_ERR_NOERROR;
}
#define TESTHANDLER_SET_NAME "my_test"
int
my_test_instance_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
static u_long accesses = 42;
u_long *accesses_cache = NULL;
clicon_debug(1, "%s", __FUNCTION__);
switch (reqinfo->mode) {
case MODE_GET:
snmp_set_var_typed_value(requests->requestvb, ASN_UNSIGNED,
(u_char *) & accesses, sizeof(accesses));
break;
#ifndef NETSNMP_NO_WRITE_SUPPORT
case MODE_SET_RESERVE1:
if (requests->requestvb->type != ASN_UNSIGNED)
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_WRONGTYPE);
break;
case MODE_SET_RESERVE2:
/*
* store old info for undo later
*/
accesses_cache = netsnmp_memdup(&accesses, sizeof(accesses));
if (accesses_cache == NULL) {
netsnmp_set_request_error(reqinfo, requests,
SNMP_ERR_RESOURCEUNAVAILABLE);
return SNMP_ERR_NOERROR;
}
netsnmp_request_add_list_data(requests,
netsnmp_create_data_list
(TESTHANDLER_SET_NAME,
accesses_cache, free));
break;
case MODE_SET_ACTION:
/*
* update current
*/
accesses = *(requests->requestvb->val.integer);
DEBUGMSGTL(("testhandler", "updated accesses -> %lu\n", accesses));
break;
case MODE_SET_UNDO:
accesses =
*((u_long *) netsnmp_request_get_list_data(requests,
TESTHANDLER_SET_NAME));
break;
case MODE_SET_COMMIT:
case MODE_SET_FREE:
/*
* nothing to do
*/
break;
#endif /* NETSNMP_NO_WRITE_SUPPORT */
}
return SNMP_ERR_NOERROR;
}
int
my_data_table_handler(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
char *column3;
netsnmp_table_request_info *table_info;
netsnmp_table_row *row;
clicon_debug(1, "%s", __FUNCTION__);
while (requests) {
if (requests->processed) {
requests = requests->next;
continue;
}
/*
* extract our stored data and table info
*/
row = netsnmp_extract_table_row(requests);
table_info = netsnmp_extract_table_info(requests);
if (!table_info || !row || !row->data)
continue;
column3 = (char *) row->data;
/*
* there's only one column, we don't need to check if it's right
*/
netsnmp_table_data_build_result(reginfo, reqinfo, requests, row,
table_info->colnum,
ASN_OCTET_STR, (u_char*)column3,
strlen(column3));
requests = requests->next;
}
return SNMP_ERR_NOERROR;
}
#endif
/*! Signal terminates process
* Just set exit flag for proper exit in event loop
@ -194,7 +548,8 @@ static int
clixon_snmp_init(clicon_handle h,
int logdst)
{
int retval = -1;
int retval = -1;
char *sockpath = NULL;
clicon_debug(1, "%s", __FUNCTION__);
if (logdst == CLICON_LOG_SYSLOG)
@ -204,14 +559,22 @@ clixon_snmp_init(clicon_handle h,
/* make a agentx client. */
netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1);
if ((sockpath = clicon_option_str(h, "CLICON_SNMP_AGENT_SOCK")) == NULL){
clicon_err(OE_SNMP, 0, "CLICON_SNMP_AGENT_SOCK not set");
goto done;
}
/* XXX: This should be configurable. */
netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, "unix:/tmp/clixon_snmp.sock");
netsnmp_ds_set_string(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_X_SOCKET, sockpath);
/* initialize the agent library */
init_agent(__PROGRAM__);
/* XXX Hardcoded, replace this with generic MIB */
#if 1
init_testhandler();
#else
init_nstAgentSubagentObject(h);
#endif
/* example-demon will be used to read example-demon.conf files. */
init_snmp(__PROGRAM__);
@ -266,7 +629,6 @@ snmp_terminate(clicon_handle h)
return 0;
}
/*! Usage help routine
* @param[in] h Clixon handle
* @param[in] argv0 command line
@ -468,6 +830,7 @@ main(int argc,
if (dbg)
clicon_option_dump(h, dbg);
/* main event loop */
if (clixon_event_loop(h) < 0)
goto done;
retval = 0;

View file

@ -182,6 +182,8 @@ BUSER=clicon
: ${clixon_backend:=clixon_backend}
: ${clixon_snmp:=$(type -p clixon_snmp)}
# Source the site-specific definitions for test script variables, if site.sh
# exists. The variables defined in site.sh override any variables of the same
# names in the environment in the current execution.

View file

@ -17,12 +17,15 @@ if [ ${WITH_NETSNMP} != "yes" ]; then
fi
snmpd=$(type -p snmpd)
snmpget="$(type -p snmpget) -c public -v2c localhost:1161 "
snmpset="$(type -p snmpset) -c public -v2c localhost:1161 "
clixon_snmp="/usr/local/sbin/clixon_snmp"
snmpget="$(type -p snmpget) -On -c public -v2c localhost:1161 "
snmpset="$(type -p snmpset) -On -c public -v2c localhost:1161 "
cfg=$dir/conf_startup.xml
fyang=$dir/clixon-example.yang
# AgentX unix socket
SOCK=/tmp/clixon_snmp.sock
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
@ -31,6 +34,7 @@ cat <<EOF > $cfg
<CLICON_SOCK>$dir/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/var/tmp/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_SNMP_AGENT_SOCK>unix:$SOCK</CLICON_SNMP_AGENT_SOCK>
</clixon-config>
EOF
@ -47,8 +51,8 @@ function testinit(){
new "kill old snmp daemons"
sudo killall snmpd
new "Starting snmpd"
$snmpd --rwcommunity=public --master=agentx --agentXSocket=unix:/tmp/clixon_snmp.sock udp:127.0.0.1:1161
new "Starting $snmpd --rwcommunity=public --master=agentx --agentXSocket=unix:/tmp/clixon_snmp.sock udp:127.0.0.1:1161"
$snmpd --rwcommunity=public --master=agentx --agentXSocket=unix:$SOCK udp:127.0.0.1:1161
pgrep snmpd
if [ $? != 0 ]; then
@ -65,14 +69,14 @@ function testinit(){
sudo pkill -f clixon_backend
new "Starting backend"
start_backend -s init -f $cfg -- -s
start_backend -s init -f $cfg
# Kill old clixon_snmp, if any
new "Terminating any old clixon_snmp processes"
sudo killall clixon_snmp
new "Starting clixon_snmp"
$clixon_snmp -f $cfg &
$clixon_snmp -f $cfg -D $DBG -l s &
sleep 1
@ -89,14 +93,16 @@ function testexit(){
new "SNMP tests"
testinit
OID=".1.2.3.6.1"
new "Test SNMP get for default value"
expectpart "$($snmpget .1.3.6.1.4.1.8072.2.4.1.1.2.0)" 0 "NET-SNMP-EXAMPLES-MIB::netSnmpExamples.4.1.1.2.0 = INTEGER: 2"
expectpart "$($snmpget $OID)" 0 "$OID = Gauge32: 42"
new "Set new value to OID"
expectpart "$($snmpset .1.3.6.1.4.1.8072.2.4.1.1.2.0 i 1234)" 0 "NET-SNMP-EXAMPLES-MIB::netSnmpExamples.4.1.1.2.0 = INTEGER: 1234"
expectpart "$($snmpset $OID u 1234)" 0 "$OID = Gauge32: 1234"
new "Get new value"
expectpart "$($snmpget .1.3.6.1.4.1.8072.2.4.1.1.2.0)" 0 "NET-SNMP-EXAMPLES-MIB::netSnmpExamples.4.1.1.2.0 = INTEGER: 1234"
expectpart "$($snmpget $OID)" 0 "$OID = Gauge32: 1234"
new "Cleaning up"
testexit

View file

@ -1132,5 +1132,17 @@ module clixon-config {
0 means no limit";
}
leaf CLICON_SNMP_AGENT_SOCK {
type string;
default "unix:/tmp/clixon_snmp.sock";
description
"String description of AgentX socket that clixon_snmp listens to.
For example, for net-snmpd, the socket is created by using the following:
--agentXSocket=unix:<path>
This string currently only supports UNIX socket path.
Note also that the user should consider setting permissions appropriately
XXX: This should be in later yang revision and documented as added when
merged with master";
}
}
}