C NET-SNMP Get and Set specifically via MIB Name, Not OID - c

I have written and am testing software for a generic SNMP client module in C as well as an implementation using this generic module. I am having trouble getting a get request to work by passing in a MIB name(e.g. sysDescr) instead of an OID(e.g. 1.3.6.1.2.1.1.1).
I am successful when I pass in a character array containing the OID to _snmp_parse_oid()_ but not the name.
I have checked the MIB file to make sure I am using the correct name. When I run the command line SNMP translate on the name it gives me the OID listed above:
$ snmptranslate -m +<MIB File> -IR -On <MIB Name>
.#.#.#.#.#.#.#####.#.#.#.#.#.#
(In the above command I replaced my actual mib file with <MIB File>, mib name with <MIB Name>, and OID numbers returned from the command with # characters)
The following is my code for my generic SNMP get function, assume returned values are #define numbers and I have removed some error handling for brevity:
/// #Synopsis Function to send out get request since the
/// SNMPOidData object has been setup
///
/// #Param oid_name String containing the OID to set
/// #Param value Value to set
///
/// #Returns Error
int snmpGet(SNMPAgent *this, char const * const oid_name, SNMPOidData * value)
{
netsnmp_pdu *pdu;
netsnmp_pdu *response;
netsnmp_variable_list *vars;
oid *retrieved_oid;
oidStruct oid_to_get;
int status = 0;
int result = ERROR_SUCCESS;
// Create the PDU for the data for our request
pdu = snmp_pdu_create(SNMP_MSG_GET);
oid_to_get.OidLen = MAX_OID_LEN; // Set max length
// Send out the request(s)
retrieved_oid = snmp_parse_oid(oid_name, oid_to_get.Oid, &oid_to_get.OidLen);
// Set the data
snmp_add_null_var(pdu, oid_to_get.Oid, oid_to_get.OidLen))
// Send the request out
status = snmp_synch_response(this->port.snmp_session_handle, pdu, &response);
if (STAT_SUCCESS == status)
{
if (SNMP_ERR_NOERROR == response->errstat)
{
vars = response->variables;
value->type = vars->type;
if (vars->next_variable != NULL)
{
// There are more values, set return type to null
value->type = ASN_NULL;
}
else if (!(CHECK_END(vars->type))) // Exception
{
result = ERROR_NOT_PRESENT;
fprintf(stderr, "Warning: OID=%s gets snmp exception %d \n",
oid_name, vars->type);
}
else if ((vars->type == ASN_INTEGER)
|| (vars->type == ASN_COUNTER)
|| (vars->type == ASN_UNSIGNED))
{
value->integer = *(vars->val.integer);
value->str_len = sizeof(value->integer);
}
else
{
value->str_len = vars->val_len;
if (value->str_len >= MAX_ASN_STR_LEN)
value->str_len = MAX_ASN_STR_LEN;
if (value->str_len > 0)
memcpy(value->string, vars->val.string, value->str_len);
// guarantee NULL terminated string
value->string[value->str_len] = '\0';
}
}
}
this->freePDU(response); // Clean up: free the response
return result;
}
The error I am getting:
oid_name: Unknown Object Identifier (Sub-id not found: (top) -> <MIB Name>)
Which comes from the following call:
retrieved_oid = snmp_parse_oid(oid_name, oid_to_get.Oid, &oid_to_get.OidLen);
I have made sure that the MIB files are on the machine in the configured place (snmptranslate wouldn't work if this weren't the case).
I have spent a good amount of time on Google results as well as directly searching here on Stack Overflow.
The following is a good tutorial but does not address my issue (they directly reference the OID they want to get the value of):
http://www.net-snmp.org/wiki/index.php/TUT:Simple_Application
Any help or insight would be much appreciated.
Some other info I can think of is that this is being compiled to run on an armv5tejl target running Linux communicating with an external device via ethernet.
Thanks,

When I call MIB variables by their string name I use the following net-snmp functions.
read_objid(OID, anOID, &anOID_len);
snmp_add_null_var(pdu, anOID, anOID_len);
Where:
oid anOID[MAX_OID_LEN];
size_t anOID_len = MAX_OID_LEN;
In my program I pack this all into a single function call.
void packSingleGetOID(const char *OID, struct snmp_pdu *pdu){
// OID in / PDU out
oid anOID[MAX_OID_LEN];
size_t anOID_len = MAX_OID_LEN;
read_objid(OID, anOID, &anOID_len);
snmp_add_null_var(pdu, anOID, anOID_len);
}
I pass in the MIB OID string and the pointer to session pdu. Remember the OID string is MIB_Name::variable.

Related

SNMP Agent: Could mib2c generate code for InetAddress or String type (ie something not an integer type)

I was able to transform 95% of a dedicated MIB to C code and make it run in a sub-agent like described in the last part of this Net-SNMP tutorial
For this I naturally use the mib2c.mfd.conf (I just read that mfd stands for Mib For Dummies ... all is said ...)
mib2c -I -c mib2c.mfd.conf my_mib_node
It generated a long .c file with almost all the oids like the one below.
Almost no lines were generated for the VideoInetAddr OID
//ABSTRACT OF SOURCE FILE GENERATED BY MIB2C
//...
long VideoFormat = 0; /* XXX: set default value */
// <<<=== NOTHING GENERATED HERE FOR VideoInetAddr OF TYPE INETADDRESS
// WHEREAS OTHER INTEGERS ARE NORMALLY PRESENT
long VideoInetPort = 0; /* XXX: set default value */
//...
void init_my_mib_node(void)
{
//...
const oid VideoFormat_oid[] = { 1,3,6,1,4,1,a,b,c,d,e };
static netsnmp_watcher_info VideoFormat_winfo;
// <<<=== NO OID GENERATED for VideoInetAddr OF TYPE INETADDRESS
// WHEREAS OTHER OIDs ARE NORMALLY GENERATED
static netsnmp_watcher_info VideoInetAddr_winfo; //We have the winfo after all
const oid VideoInetPort_oid[] = { 1,3,6,1,4,1,a,b,c,d,g };
static netsnmp_watcher_info VideoInetPort_winfo;
DEBUGMSGTL(("my_mib_node",
"Initializing VideoFormat scalar integer. Default value = %d\n",
VideoFormat));
reg = netsnmp_create_handler_registration(
"VideoFormat", NULL,
VideoFormat_oid, OID_LENGTH(VideoFormat_oid),
HANDLER_CAN_RWRITE);
netsnmp_init_watcher_info(&VideoFormat_winfo, &VideoFormat,
sizeof(long),ASN_INTEGER, WATCHER_FIXED_SIZE);
if (netsnmp_register_watched_scalar( reg, &VideoFormat_winfo ) < 0 ) {
snmp_log( LOG_ERR, "Failed to register watched VideoFormat" );
//...
}
This worked fine and needed 5 minutes (no code to write, just call the init() function), I was able to GET and SET all ... integers ...
Some oids are of Type InetAddress were not generated, neither were strings
Question
Is there a mib conf file able to generate code for every type
I tried the mib2c.old-api.conf which generates code also for the non-integer oids but I find it not as convenient. There is more boilerplate code to write.
Yes, mib2c could generate code for IP addresses. I cannot say that mfd does this, but, definitely, some mib2c.iterate.conf (for tables) does this.
The type of IP in SNMP is ASN_IPADDRESS represented by unint32_t in C.
Also,You need to make sure that in MIB-file for object, which represents IP, you have "SYNTAX IpAddress".
Have a look:
at the MIB file with IP object and implementation in C
Piece of answer but I am very far from comprehension and so side problems persist
Very pragmatically I managed to add by hand
//I put here ONLY what I added, see question above to complete code
#define STR_LENGTH_IPV4 sizeof("xxx.yyy.zzz.www")
char VideoInetAddr[STR_LENGTH_IPV4] = "192.168.2.3";
//...
const oid VideoInetAddr_oid[] = { 1,3,6,1,4,1,a,b,c,d,f };
reg = netsnmp_create_handler_registration(
"VideoInetAddr", NULL,
VideoInetAddr_oid, OID_LENGTH(VideoInetAddr_oid),
HANDLER_CAN_RWRITE);
netsnmp_init_watcher_info(&VideoInetAddr_winfo, &VideoInetAddr, sizeof(VideoInetAddr),
ASN_OCTET_STR, WATCHER_MAX_SIZE );
if (netsnmp_register_watched_scalar( reg, &VideoInetAddr_winfo ) < 0 ) {
snmp_log( LOG_ERR, "Failed to register watched VideoInetAddr" );
}
It still need to understand exactly the option like WATCHER_MAX_SIZE (is-it the good one ?)

Nanopb without callbacks

I'm using Nanopb to try and send protobuf messages from a VxWorks based National Instruments Compact RIO (9025). My cross compilation works great, and I can even send a complete message with data types that don't require extra encoding. What's getting me is the callbacks. My code is cross compiled and called from LabVIEW and the callback based structure of Nanopb seems to break (error out, crash, target reboots, whatever) on the target machine. If I run it without any callbacks it works great.
Here is the code in question:
bool encode_string(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
char *str = "Woo hoo!";
if (!pb_encode_tag_for_field(stream, field))
return false;
return pb_encode_string(stream, (uint8_t*)str, strlen(str));
}
extern "C" uint16_t getPacket(uint8_t* packet)
{
uint8_t buffer[256];
uint16_t packetSize;
ExampleMsg msg = {};
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
msg.name.funcs.encode = &encode_string;
msg.value = 17;
msg.number = 18;
pb_encode(&stream, ExampleMsg_fields, &msg);
packetSize = stream.bytes_written;
memcpy(packet, buffer, 256);
return packetSize;
}
And here's the proto file:
syntax = "proto2"
message ExampleMsg {
required int32 value = 1;
required int32 number = 2;
required string name = 3;
}
I have tried making the callback an extern "C" as well and it didn't change anything. I've also tried adding a nanopb options file with a max length and either didn't understand it correctly or it didn't work either.
If I remove the string from the proto message and remove the callback, it works great. It seems like the callback structure is not going to work in this LabVIEW -> C library environment. Is there another way I can encode the message without the callback structure? Or somehow embed the callback into the getPacket() function?
Updated code:
extern "C" uint16_t getPacket(uint8_t* packet)
{
uint8_t buffer[256];
for (unsigned int i = 0; i < 256; ++i)
buffer[i] = 0;
uint16_t packetSize;
ExampleMsg msg = {};
pb_ostream_t stream = pb_ostream_from_buffer(buffer, sizeof(buffer));
msg.name.funcs.encode = &encode_string;
msg.value = 17;
msg.number = 18;
char name[] = "Woo hoo!";
strncpy(msg.name, name, strlen(name));
pb_encode(&stream, ExampleMsg_fields, &msg);
packetSize = stream.bytes_written;
memcpy(packet, buffer, sizeof(buffer));
return packetSize;
}
Updated proto file:
syntax = "proto2"
import "nanopb.proto";
message ExampleMsg {
required int32 value = 1;
required int32 number = 2;
required string name = 3 [(nanopb).max_size = 40];
}
You can avoid callbacks by giving a maximum size for the string field using the option (nanopb).max_size = 123 in the .proto file. Then nanopb can generate a simple char array in the structure (relevant part of documentation).
Regarding why callbacks don't work: just a guess, but try adding extern "C" also to the callback function. I assume you are using C++ there, so perhaps on that platform the C and C++ calling conventions differ and that causes the crash.
Does the VxWorks serial console give any more information about the crash? I don't remember if it does that for functions called from LabView, so running some test code directly from the VxWorks shell may be worth a try also.
Perhaps the first hurdle is how the code handles strings.
LabVIEW's native string representation is not null-terminated like C, but you can configure LabVIEW to use a different representation or update your code to handle LabVIEW's native format.
LabVIEW stores a string in a special format in which the first four bytes of the array of characters form a 32-bit signed integer that stores how many characters appear in the string. Thus, a string with n characters requires n + 4 bytes to store in memory.
LabVIEW Help: Using Arrays and Strings in the Call Library Function Node
http://zone.ni.com/reference/en-XX/help/371361L-01/lvexcodeconcepts/array_and_string_options/

C - Parse string from SNMP SET (weird)

I am working on my own SNMP agent and am having trouble processing strings. I am pretty new to SNMP as well.
I've referred to the following links for some of implementing my own agent :
http://www.net-snmp.org/dev/agent/ucdDemoPublic_8c_source.html
http://www.net-snmp.org/dev/agent/example_8h_source.html
The second link shows exactly how to handle when a user attempts to set an integer type MIB object :
line 657 shows :
intval = *((long *) var_val);
My Question : How would I go about it with a string? I've tried casting it, strncpy, snprintf, etc.
My work :
I know, or at least think, that the following is legal :
int
setString(int action,
u_char * var_val,
u_char var_val_type,
size_t var_val_len,
u_char * statP, oid * name, size_t name_len)
{
unsigned char publicString[10];
static long intval;
char *cmd_string = NULL;
/*
* Define an arbitrary maximum permissible value
*/
switch (action) {
case RESERVE1:
//intval = *((long *) var_val);
/*
* Check that the value being set is acceptable
*/
if (var_val_type != ASN_OCTET_STR) {
DEBUGMSGTL(("setString", "%x not string type", var_val_type));
return SNMP_ERR_WRONGTYPE;
}
if (var_val_len > 1 ) {
DEBUGMSGTL(("setString", "wrong length %" NETSNMP_PRIz "u",
var_val_len));
return SNMP_ERR_WRONGLENGTH;
}
if ( !(var_val[0] == '1' || var_val[0] == '0') )
{
DEBUGMSGTL(("setString", "wrong value %s", var_val));
return SNMP_ERR_WRONGVALUE;
}
I know it is somewhat working because when I invoke
# snmpset -v 2c -c xxx 10.20.30.40 1.3.6.1.4.1.54321.3.0 s 3
Error in packet.
Reason: wrongValue (The set value is illegal or unsupported in some way)
Failed object: MY-TEST-MIB::testSnmp.3.0
AND
snmpset -v 2c -c xxx 10.20.30.40 1.3.6.1.4.1.54321.3.0 s 1
MY-TEST-MIB::testSnmp.3.0 = STRING: "1"
This proves to me at least the last collection of code is working.
Here is the action part :
case ACTION:
/*
* Set the variable as requested.
* Note that this may need to be reversed,
* so save any information needed to do this.
*/
if ( var_val[0] == '1' )
{
//do stuff - there realy is a script call here that does something
}
if ( var_val[0] == '0' )
{
//do stuff - there realy is a script call here that does something
}
break;
The above section I cannot get to work.
I've been able to get what I want by making the object an INTEGER (ASN.1) type but I can't do that because when reading this object it returns a STRING (ASN.1).

How to get mounted drive's volume name in linux using C?

I'm currently working on program, which must display information about mounted flash drive. I want to display full space, free space, file system type and volume name. But problem is that, i can't find any API through which i can get volume name(volume label). Is there any api to do this?
p.s. full space, free space and file system type i'm getting via statfs function
Assuming that you work on a recent desktop-like distribution (Fedora, Ubuntu, etc.), you have HAL daemon running and a D-Bus session.
Within org.freedesktop.UDisks namespace you can find the object that represents this drive (say org/freedekstop/UDisks/devices/sdb/. It implements org.freedesktop.UDisks.interface. This interface has all the properties that you can dream of, including UUID (IdUuid), FAT label (IdLabel), all the details about filesystem, SMART status (if the drive supports that) etc. etc.
How to use D-Bus API in C is a topic for another question. I assume that's been already discussed in detail -- just search [dbus] and [c] tags.
Flash drives are generally FAT32, which means the "name" that you're looking for is probably the FAT drive label. The most common linux command to retrieve that information is mlabel from the mtools package.
The command looks like this:
[root#localhost]$ mlabel -i /dev/sde1 -s ::
Volume label is USB-DISK
This program works by reading the raw FAT header of the filesystem and retrieving the label from that data. You can look at the source code for the applciation to see how you can replicate the parsing of FAT data in your own application... or you can simply execute run the mlabel binary and read the result into your program. The latter sounds simpler to me.
To call the methods:
kernResult = self->FindEjectableCDMedia(&mediaIterator);
if (KERN_SUCCESS != kernResult) {
printf("FindEjectableCDMedia returned 0x%08x\n", kernResult);
}
kernResult = self->GetPath(mediaIterator, bsdPath, sizeof(bsdPath));
if (KERN_SUCCESS != kernResult) {
printf("GetPath returned 0x%08x\n", kernResult);
}
and the methods:
// Returns an iterator across all DVD media (class IODVDMedia). Caller is responsible for releasing
// the iterator when iteration is complete.
kern_return_t ScanPstEs::FindEjectableCDMedia(io_iterator_t *mediaIterator)
{
kern_return_t kernResult;
CFMutableDictionaryRef classesToMatch;
// CD media are instances of class kIODVDMediaTypeROM
classesToMatch = IOServiceMatching(kIODVDMediaClass);
if (classesToMatch == NULL) {
printf("IOServiceMatching returned a NULL dictionary.\n");
} else {
CFDictionarySetValue(classesToMatch, CFSTR(kIODVDMediaClass), kCFBooleanTrue);
}
kernResult = IOServiceGetMatchingServices(kIOMasterPortDefault, classesToMatch, mediaIterator);
return kernResult;
}
// Given an iterator across a set of CD media, return the BSD path to the
// next one. If no CD media was found the path name is set to an empty string.
kern_return_t GetPath(io_iterator_t mediaIterator, char *Path, CFIndex maxPathSize)
{
io_object_t nextMedia;
kern_return_t kernResult = KERN_FAILURE;
DADiskRef disk = NULL;
DASessionRef session = NULL;
CFDictionaryRef props = NULL;
char * bsdPath = '\0';
*Path = '\0';
nextMedia = IOIteratorNext(mediaIterator);
if (nextMedia) {
CFTypeRef bsdPathAsCFString;
bsdPathAsCFString = IORegistryEntryCreateCFProperty(nextMedia,CFSTR(kIOBSDNameKey),kCFAllocatorDefault,0);
if (bsdPathAsCFString) {
//strlcpy(bsdPath, _PATH_DEV, maxPathSize);
// Add "r" before the BSD node name from the I/O Registry to specify the raw disk
// node. The raw disk nodes receive I/O requests directly and do not go through
// the buffer cache.
//strlcat(bsdPath, "r", maxPathSize);
size_t devPathLength = strlen(bsdPath);
if (CFStringGetCString( (CFStringRef)bsdPathAsCFString , bsdPath + devPathLength,maxPathSize - devPathLength, kCFStringEncodingUTF8)) {
qDebug("BSD path: %s\n", bsdPath);
kernResult = KERN_SUCCESS;
}
session = DASessionCreate(kCFAllocatorDefault);
if(session == NULL) {
qDebug("Can't connect to DiskArb\n");
return -1;
}
disk = DADiskCreateFromBSDName(kCFAllocatorDefault, session, bsdPath);
if(disk == NULL) {
CFRelease(session);
qDebug( "Can't create DADisk for %s\n", bsdPath);
return -1;
}
props = DADiskCopyDescription(disk);
if(props == NULL) {
CFRelease(session);
CFRelease(disk);
qDebug("Can't get properties for %s\n",bsdPath);
return -1;
}
CFStringRef daName = (CFStringRef )CFDictionaryGetValue(props, kDADiskDescriptionVolumeNameKey);
CFStringGetCString(daName,Path,sizeof(Path),kCFStringEncodingUTF8);
if(daName) {
qDebug("%s",Path);
CFRetain(daName);
}
CFRelease(daName);
CFRelease(props);
CFRelease(disk);
CFRelease(session);
CFRelease(bsdPathAsCFString);
}
IOObjectRelease(nextMedia);
}
return kernResult;
}

How to set request-specific data to SNMP agent using net-snmp?

I want the SNMP agent to response differently depending on the source requester, but cannot find a way to magic convey some data to make it distinguishable by the SNMP agent.
What I have tried setting is the netsnmp_session structure and netsnmp_pdu structure. because they're two parameters of snmp_send. The data field I tried to facilitate is myvoid and callback_magic.
But unfortunately on the SNMP agent, the data received are all 0, which is not what I have set on the SNMP client.
Sorry to answer myselv's question.
Finally I found the following trick to circumvent the issue:
insert a well known SNMP object(such as ifNumber) immediately after the target SNMP object to identify the specific SNMP query.
The handler function in agent should check the variable next to current variable to see whether
it's exactly the well known SNMP object ifNumber. If yes then the query comes from you, which using
NET-SNMP API to form the variable list of this query.
client code:
oid dest_OID[ MAX_OID_LEN ] = {0};
size_t dest_OID_len = COUNT_OF(dest_OID);
get_node(g_snmp_name_ifNumber, dest_OID, &dest_OID_len );
snmp_add_null_var(pdu, dest_OID, dest_OID_len);
On agent side:
int get_status(netsnmp_mib_handler *handler,
netsnmp_handler_registration *reginfo,
netsnmp_agent_request_info *reqinfo,
netsnmp_request_info *requests)
{
switch (reqinfo->mode) {
case MODE_GET:
{
bool is_sent_by_manager = false;
if( requests->requestvb->next_variable )
{
struct variable_list * v = requests->requestvb->next_variable;
oid dest_OID[ MAX_OID_LEN ] = {0};
size_t dest_OID_len = COUNT_OF(dest_OID);
get_node(g_snmp_name_ifNumber, dest_OID, &dest_OID_len );
const int nbytes = v->name_length * sizeof(v->name[0]);
if( dest_OID_len >= v->name_length
&& memcmp(dest_OID, v->name, nbytes) == 0 ) {
is_sent_by_manager = true;
}
}
if( is_sent_by_manager ) {
...
}
else {
...
}

Resources