Add support for snmp v2 traps
This commit is contained in:
parent
af29a0f974
commit
4920b9d32b
4 changed files with 527 additions and 0 deletions
|
|
@ -93,6 +93,7 @@ APPSRC += snmp_main.c
|
|||
APPSRC += snmp_register.c
|
||||
APPSRC += snmp_handler.c
|
||||
APPSRC += snmp_lib.c
|
||||
APPSRC += snmp_stream.c
|
||||
|
||||
APPOBJ = $(APPSRC:.c=.o)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@
|
|||
|
||||
#include "snmp_lib.h"
|
||||
#include "snmp_register.h"
|
||||
#include "snmp_stream.h"
|
||||
|
||||
/* Command line options to be passed to getopt(3) */
|
||||
#define SNMP_OPTS "hVD:f:l:C:o:z"
|
||||
|
|
@ -112,6 +113,7 @@ snmp_terminate(clixon_handle h)
|
|||
cxobj *x = NULL;
|
||||
char *pidfile = clicon_snmp_pidfile(h);
|
||||
|
||||
clixon_snmp_stream_shutdown(h);
|
||||
snmp_shutdown(__FUNCTION__);
|
||||
shutdown_agent();
|
||||
clixon_snmp_api_agent_cleanup();
|
||||
|
|
@ -582,6 +584,9 @@ main(int argc,
|
|||
/* Init and traverse mib-translated yangs and register callbacks */
|
||||
if (clixon_snmp_traverse_mibyangs(h) < 0)
|
||||
goto done;
|
||||
/* init snmp stream (traps) */
|
||||
if (clixon_snmp_stream_init(h) < 0)
|
||||
goto done;
|
||||
|
||||
/* Write pid-file */
|
||||
if (pidfile_write(pidfile) < 0)
|
||||
|
|
|
|||
475
apps/snmp/snmp_stream.c
Normal file
475
apps/snmp/snmp_stream.c
Normal file
|
|
@ -0,0 +1,475 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2024 Olof Hagsand
|
||||
2024 Mico Micic and Moser-Baer AG
|
||||
|
||||
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 <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* clixon */
|
||||
#include <clixon/clixon.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>
|
||||
|
||||
#include "snmp_lib.h"
|
||||
|
||||
/*
|
||||
* SNMP v2 notification OID
|
||||
*/
|
||||
static oid notificationOid[] = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0};
|
||||
|
||||
struct stream_socket{
|
||||
qelem_t sc_q; /* queue header */
|
||||
int socket; /* socket */
|
||||
};
|
||||
/* Linked list of sockets used to listen for events
|
||||
*/
|
||||
static struct stream_socket *STREAM_SOCKETS = NULL;
|
||||
|
||||
/*! Read smiv2:oid for given YANG node
|
||||
*
|
||||
* @param[in] ys YANG node
|
||||
* @param[out] objid oid
|
||||
* @param[out] objidlen oid length
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
get_oid_for_yang_node(yang_stmt *ys, oid *objid, size_t *objidlen)
|
||||
{
|
||||
int retval = -1;
|
||||
int exist = 0;
|
||||
char *oidstr = NULL;
|
||||
|
||||
if (yang_extension_value_opt(ys, "smiv2:oid", &exist, &oidstr) < 0)
|
||||
goto done;
|
||||
if (exist == 0 || oidstr == NULL){
|
||||
clixon_debug(CLIXON_DBG_SNMP, "oid not found as SMIv2 yang extension of %s", yang_argument_get(ys));
|
||||
goto done;
|
||||
}
|
||||
if (snmp_parse_oid(oidstr, objid, objidlen) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "snmp_parse_oid");
|
||||
goto done;
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Add snmp trap v2 oid to the variable list and bind the given notification oid as value.
|
||||
*
|
||||
* @param[in] var_list Variable list
|
||||
* @param[in] notify_oid OID of the notification to set as value
|
||||
* @param[in] oid_len Length of notify_oid
|
||||
*/
|
||||
void
|
||||
add_snmp_trapv2_oid(netsnmp_variable_list **var_list, oid *notify_oid, int oid_len)
|
||||
{
|
||||
snmp_varlist_add_variable(
|
||||
var_list,
|
||||
notificationOid,
|
||||
OID_LENGTH(notificationOid),
|
||||
ASN_OBJECT_ID,
|
||||
(const unsigned char*)notify_oid, oid_len * sizeof(oid)
|
||||
);
|
||||
}
|
||||
|
||||
/*! Add snmp var binding for all child nodes of cxparent.
|
||||
*
|
||||
* All child elements are read recursively. A var binding is created for each LEAF element
|
||||
* with an existing oid and added to the snmp variable list.
|
||||
*
|
||||
* @param[in] var_list Variable list
|
||||
* @param[in] cxparent Parent xml node
|
||||
* @param[in] ys YANG node
|
||||
*/
|
||||
int
|
||||
add_snmp_var_bindings(netsnmp_variable_list **var_list, cxobj *cxparent, yang_stmt *ys)
|
||||
{
|
||||
int retval = -1;
|
||||
int ret;
|
||||
cxobj *xncont = NULL; /* notification context xml */
|
||||
yang_stmt *ychild;
|
||||
char *xmlstr = NULL;
|
||||
char *body = NULL;
|
||||
int asn1type;
|
||||
char *reason = NULL;
|
||||
unsigned char *snmpval = NULL;
|
||||
size_t snmplen = 0;
|
||||
oid objid[MAX_OID_LEN] = {0,}; /* Leaf */
|
||||
size_t objidlen = MAX_OID_LEN;
|
||||
|
||||
while ((xncont = xml_child_each(cxparent, xncont, CX_ELMNT)) != NULL){
|
||||
char *node_name = xml_name(xncont);
|
||||
if ((ychild = yang_find(ys, 0, node_name)) != NULL) {
|
||||
switch (yang_keyword_get(ychild)){
|
||||
case Y_LEAF: /* only Y_LEAF type is supported here */
|
||||
if (get_oid_for_yang_node(ychild, objid, &objidlen) < 0)
|
||||
goto done;
|
||||
if ((body = xml_body(xncont)) != NULL){
|
||||
if ((ret = type_xml2snmp_pre(body, ychild, &xmlstr)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clixon_debug(CLIXON_DBG_SNMP, "invalid data type for %s", node_name);
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
if (type_yang2asn1(ychild, &asn1type, 1) < 0)
|
||||
goto done;
|
||||
if ((ret = type_xml2snmp(xmlstr, ychild, &asn1type, &snmpval, &snmplen, &reason)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clixon_debug(CLIXON_DBG_SNMP, "%s", reason);
|
||||
goto done;
|
||||
}
|
||||
snmp_varlist_add_variable(var_list, objid, objidlen, asn1type, snmpval, snmplen);
|
||||
break;
|
||||
case Y_CONTAINER: /* process child nodes */
|
||||
add_snmp_var_bindings(var_list, xncont, ychild);
|
||||
break;
|
||||
default:
|
||||
clixon_debug(CLIXON_DBG_SNMP, "type %s not supported in snmp trap", yang_key2str(yang_keyword_get(ychild)));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
clixon_debug(CLIXON_DBG_SNMP, "no yang def found for %s", node_name);
|
||||
}
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
done:
|
||||
if (reason)
|
||||
free(reason);
|
||||
if (xmlstr)
|
||||
free(xmlstr);
|
||||
if (snmpval)
|
||||
free(snmpval);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Publish snmp notification (V2 trap) for the given notification data (xml node).
|
||||
*
|
||||
* The OID is read from the YANG definition. If the notification contains data, all values
|
||||
* are bound to the corresponding OIDs and sent with the snmp trap.
|
||||
*
|
||||
* @param[in] s Socket
|
||||
* @param[in] arg expected to be clixon_handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
snmp_publish_notification(clixon_handle h, cxobj *xn)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xncont = NULL; /* notification content xml */
|
||||
yang_stmt *ymoddata = NULL; /* notification yang module */
|
||||
yang_stmt *yspec;
|
||||
yang_stmt *ydef;
|
||||
oid objid[MAX_OID_LEN] = {0,};
|
||||
size_t objidlen = MAX_OID_LEN;
|
||||
netsnmp_variable_list *var_list = NULL;
|
||||
|
||||
if ((yspec = clicon_dbspec_yang(h)) == NULL){
|
||||
clixon_err(OE_FATAL, 0, "No DB_SPEC");
|
||||
goto done;
|
||||
}
|
||||
while ((xncont = xml_child_each(xn, xncont, CX_ELMNT)) != NULL){
|
||||
char *node_name = xml_name(xncont);
|
||||
/* Skip eventTime. It is the only pre-defined child node as defined in RFC 5277 */
|
||||
if (strcmp(node_name, "eventTime") != 0){
|
||||
if (ys_module_by_xml(yspec, xncont, &ymoddata) < 0) /* get yang module for notification */
|
||||
goto done;
|
||||
if ((ydef = yang_find(ymoddata, Y_NOTIFICATION, node_name)) != NULL) {
|
||||
if (get_oid_for_yang_node(ydef, objid, &objidlen) < 0)
|
||||
goto done;
|
||||
add_snmp_trapv2_oid(&var_list, objid, objidlen);
|
||||
add_snmp_var_bindings(&var_list, xncont, ydef);
|
||||
clixon_debug(CLIXON_DBG_SNMP, "sending snmp trap for %s", yang_argument_get(ydef));
|
||||
send_v2trap(var_list);
|
||||
}
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
|
||||
done:
|
||||
if (var_list)
|
||||
snmp_free_varbind(var_list);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Callback when stream notifications arrive from backend
|
||||
*
|
||||
* @param[in] s Socket
|
||||
* @param[in] arg expected to be clixon_handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
snmp_stream_cb(int s, void *arg)
|
||||
{
|
||||
int retval = -1;
|
||||
clixon_handle h = (clixon_handle)arg;
|
||||
int eof;
|
||||
cxobj *xtop = NULL; /* top xml */
|
||||
cxobj *xn; /* notification xml */
|
||||
cbuf *cbmsg = NULL;
|
||||
int ret;
|
||||
|
||||
clixon_debug(CLIXON_DBG_SNMP, "");
|
||||
if (clixon_msg_rcv11(s, NULL, 0, &cbmsg, &eof) < 0)
|
||||
goto done;
|
||||
/* handle close from remote end: this will exit the client */
|
||||
if (eof){
|
||||
clixon_debug(CLIXON_DBG_SNMP, "eof");
|
||||
clixon_err(OE_PROTO, ESHUTDOWN, "Socket unexpected close");
|
||||
errno = ESHUTDOWN;
|
||||
clixon_exit_set(1);
|
||||
goto done;
|
||||
}
|
||||
if ((ret = clixon_xml_parse_string(cbuf_get(cbmsg), YB_NONE, NULL, &xtop, NULL)) < 0)
|
||||
goto done;
|
||||
if (ret == 0){
|
||||
clixon_err(OE_XML, EFAULT, "Invalid notification");
|
||||
goto done;
|
||||
}
|
||||
if ((xn = xpath_first(xtop, NULL, "notification")) == NULL)
|
||||
goto ok;
|
||||
/* forward notification as snmp trap */
|
||||
if(snmp_publish_notification(h, xn) < 0)
|
||||
goto done;
|
||||
|
||||
ok:
|
||||
retval = 0;
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_SNMP, "retval: %d", retval);
|
||||
if (xtop != NULL)
|
||||
xml_free(xtop);
|
||||
if (cbmsg)
|
||||
cbuf_free(cbmsg);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Subscribe to all backend notifications and create streaming socket
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] stream Name of the stream to subscribe
|
||||
* @param[out] socket Socket to receive backend notifications
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
static int
|
||||
snmp_stream_subscribe(clixon_handle h, char *stream, int *socket)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xe;
|
||||
cbuf *cb = NULL;
|
||||
int s; /* socket */
|
||||
|
||||
clixon_debug(CLIXON_DBG_SNMP, "Subscribing stream: %s", stream);
|
||||
*socket = -1;
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
cprintf(cb, "<rpc xmlns=\"%s\" %s><create-subscription xmlns=\"%s\"><stream>%s</stream>",
|
||||
NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR, EVENT_RFC5277_NAMESPACE, stream);
|
||||
cprintf(cb, "</create-subscription></rpc>]]>]]>");
|
||||
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, &s) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL){
|
||||
goto done;
|
||||
}
|
||||
*socket = s;
|
||||
retval = 0;
|
||||
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_SNMP, "retval: %d", retval);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
int
|
||||
clixon_snmp_stream_shutdown(clixon_handle h)
|
||||
{
|
||||
struct stream_socket *sc;
|
||||
|
||||
while ((sc = STREAM_SOCKETS) != NULL){
|
||||
DELQ(sc, STREAM_SOCKETS, struct stream_socket *);
|
||||
clixon_event_unreg_fd(sc->socket, snmp_stream_cb);
|
||||
close(sc->socket);
|
||||
free(sc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*! Read all streams from backend by calling the corresponding RFC5277 get RPC
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[out] streams List of stream names (malloced must be freed)
|
||||
* @param[out] count Number of entries in streams list
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
*/
|
||||
int
|
||||
get_all_streams_from_backend(clixon_handle h, char ***streams, int *count)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xret = NULL;
|
||||
cxobj *xnchild = NULL;
|
||||
cxobj *xe;
|
||||
cxobj *xname;
|
||||
cbuf *cb = NULL;
|
||||
int cnt = 0;
|
||||
char **st = NULL;
|
||||
|
||||
clixon_debug(CLIXON_DBG_SNMP, "");
|
||||
*count = 0;
|
||||
*streams = NULL;
|
||||
if ((cb = cbuf_new()) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "cbuf_new");
|
||||
goto done;
|
||||
}
|
||||
/* get alle streams from backend */
|
||||
cprintf(cb, "<rpc xmlns=\"%s\" %s><get><filter type=\"subtree\"><netconf xmlns=\"%s\"><streams/></netconf></filter></get></rpc>]]>]]>",
|
||||
NETCONF_BASE_NAMESPACE, NETCONF_MESSAGE_ID_ATTR, EVENT_RFC5277_NAMESPACE);
|
||||
if (clicon_rpc_netconf(h, cbuf_get(cb), &xret, NULL) < 0)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xret, NULL, "rpc-reply/rpc-error")) != NULL)
|
||||
goto done;
|
||||
if ((xe = xpath_first(xret, NULL, "rpc-reply/data/netconf/streams")) == NULL) {
|
||||
clixon_debug(CLIXON_DBG_SNMP, "No streams provided by backend");
|
||||
goto ok;
|
||||
}
|
||||
while ((xnchild = xml_child_each(xe, xnchild, CX_ELMNT)) != NULL){
|
||||
if ((xname = xpath_first(xnchild, NULL, "name")) != NULL){
|
||||
char *stream_name = xml_body(xname);
|
||||
if (cnt == 0) {
|
||||
if((st = malloc(sizeof(char*))) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
else{
|
||||
if((st = realloc(st, (cnt+1) * sizeof(char*))) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "realloc");
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
st[cnt] = calloc(strlen(stream_name)+1, sizeof(char));
|
||||
strcpy(st[cnt], stream_name);
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
ok:
|
||||
*count = cnt;
|
||||
*streams = st;
|
||||
retval = 0;
|
||||
|
||||
done:
|
||||
clixon_debug(CLIXON_DBG_SNMP, "retval: %d", retval);
|
||||
if (xret)
|
||||
xml_free(xret);
|
||||
if (cb)
|
||||
cbuf_free(cb);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Init snmp stream (traps) by subscribing to all backend notifications
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @retval 0 OK
|
||||
* @retval -1 Error
|
||||
* @see snmp_stream_subscribe
|
||||
*/
|
||||
int
|
||||
clixon_snmp_stream_init(clixon_handle h)
|
||||
{
|
||||
int retval = -1;
|
||||
struct stream_socket *stream_socket;
|
||||
char **streams = NULL;
|
||||
int streams_num = 0;
|
||||
int socket = -1;
|
||||
|
||||
clixon_debug(CLIXON_DBG_SNMP, "");
|
||||
if (get_all_streams_from_backend(h, &streams, &streams_num) < 0)
|
||||
goto done;
|
||||
for (int i=0; i<streams_num; i++){
|
||||
socket = -1;
|
||||
if(snmp_stream_subscribe(h, streams[i], &socket) < 0)
|
||||
goto done;
|
||||
if (socket != -1){
|
||||
/* Listen to backend socket */
|
||||
if (clixon_event_reg_fd(socket,
|
||||
snmp_stream_cb,
|
||||
h,
|
||||
"stream socket") < 0)
|
||||
goto done;
|
||||
if ((stream_socket = calloc(1, sizeof(struct stream_socket))) == NULL){
|
||||
clixon_err(OE_SNMP, errno, "malloc");
|
||||
goto done;
|
||||
}
|
||||
stream_socket->socket = socket;
|
||||
ADDQ(stream_socket, STREAM_SOCKETS);
|
||||
}
|
||||
}
|
||||
|
||||
retval = 0;
|
||||
|
||||
done:
|
||||
if (streams){
|
||||
for (int i = 0; i < streams_num; i++)
|
||||
free(streams[i]);
|
||||
free(streams);
|
||||
}
|
||||
|
||||
clixon_debug(CLIXON_DBG_SNMP, "retval: %d", retval);
|
||||
return retval;
|
||||
}
|
||||
46
apps/snmp/snmp_stream.h
Normal file
46
apps/snmp/snmp_stream.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
*
|
||||
***** BEGIN LICENSE BLOCK *****
|
||||
|
||||
Copyright (C) 2024 Olof Hagsand
|
||||
2024 Mico Micic and Moser-Baer AG
|
||||
|
||||
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 *****
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _SNMP_STREAM_H_
|
||||
#define _SNMP_STREAM_H_
|
||||
|
||||
/*
|
||||
* Prototypes
|
||||
*/
|
||||
int clixon_snmp_stream_init(clixon_handle h);
|
||||
int clixon_snmp_stream_shutdown(clixon_handle h);
|
||||
|
||||
#endif /* _SNMP_STREAM_H_ */
|
||||
Loading…
Add table
Add a link
Reference in a new issue