4 * SNMP client interface
8 * Copyright (C) 2009 Adam Kropelin
10 * This program is free software; you can redistribute it and/or
11 * modify it under the terms of version 2 of the GNU General
12 * Public License as published by the Free Software Foundation.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
30 #define close(x) closesocket(x)
35 SnmpEngine::SnmpEngine() :
42 SnmpEngine::~SnmpEngine()
48 bool SnmpEngine::Open(const char *host
, unsigned short port
, const char *comm
, bool trap
)
50 // In case we are already open
54 // Remember new community name
57 // Generate starting request id
59 gettimeofday(&now
, NULL
);
62 // Look up destination address
63 memset(&_destaddr
, 0, sizeof(_destaddr
));
64 _destaddr
.sin_family
= AF_INET
;
65 _destaddr
.sin_port
= htons(port
);
66 _destaddr
.sin_addr
.s_addr
= inet_addr(host
);
67 if (_destaddr
.sin_addr
.s_addr
== INADDR_NONE
)
70 char *tmphstbuf
= NULL
;
72 struct hostent
*hp
= gethostname_re(host
, &he
, &tmphstbuf
, &hstbuflen
);
73 if (!hp
|| hp
->h_length
!= sizeof(_destaddr
.sin_addr
.s_addr
) ||
74 hp
->h_addrtype
!= AF_INET
)
80 memcpy(&_destaddr
.sin_addr
.s_addr
, hp
->h_addr
,
81 sizeof(_destaddr
.sin_addr
.s_addr
));
86 _socket
= socket(PF_INET
, SOCK_DGRAM
, 0);
93 // Bind to rx on any interface
94 struct sockaddr_in addr
;
95 memset(&addr
, 0, sizeof(addr
));
96 addr
.sin_family
= AF_INET
;
98 addr
.sin_addr
.s_addr
= INADDR_ANY
;
99 int rc
= bind(_socket
, (struct sockaddr
*)&addr
, sizeof(addr
));
106 // Open socket for receiving traps, if clients wants one
109 _trapsock
= socket(PF_INET
, SOCK_DGRAM
, 0);
116 memset(&addr
, 0, sizeof(addr
));
117 addr
.sin_family
= AF_INET
;
118 addr
.sin_port
= htons(SNMP_TRAP_PORT
);
119 addr
.sin_addr
.s_addr
= INADDR_ANY
;
120 rc
= bind(_trapsock
, (struct sockaddr
*)&addr
, sizeof(addr
));
131 void SnmpEngine::Close()
139 bool SnmpEngine::Set(const int oid
[], Variable
*data
)
142 VbListMessage
req(Asn::SET_REQ_PDU
, _community
, _reqid
++);
143 req
.Append(oid
, data
);
147 // Wait for a response
148 VbListMessage
*rsp
= (VbListMessage
*)rspwait(1000);
152 // Check error status
153 if (rsp
->ErrorStatus())
163 bool SnmpEngine::Get(const int oid
[], Variable
*data
)
172 bool ret
= Get(oids
);
174 *data
= (*oids
.begin()).data
;
179 bool SnmpEngine::Get(alist
<OidVar
> &oids
)
181 // First, fetch all scalar (i.e. non-sequence) OIDs using a single
182 // SNMP GETNEXT-REQUEST. Note we use GETNEXT instead of GET since all
183 // OIDs omit the trailing 0.
185 // Start with a request with no varbinds
186 VbListMessage
getreq(Asn::GETNEXT_REQ_PDU
, _community
, _reqid
++);
188 // Append one varbind for each oidvar from the caller
189 alist
<OidVar
>::iterator iter
;
190 for (iter
= oids
.begin(); iter
!= oids
.end(); ++iter
)
191 if (iter
->data
.type
!= Asn::SEQUENCE
)
192 getreq
.Append(iter
->oid
);
194 // Perform request if we put at least one OID in it
195 if (getreq
.Size() > 0)
197 // Send request & await response
198 VbListMessage
*rsp
= perform(&getreq
);
202 // Verify response varbind size is same as request
203 // (i.e. agent provided a response for each varbind we requested)
204 if (rsp
->Size() != getreq
.Size())
210 // Copy response data into caller's oidvars. Although I believe the SNMP
211 // spec requires the GET-RESPONSE to give the var-bind-list in the same
212 // order as the GET-REQUEST, we're not going to count on that. A little
213 // CPU time spent searching is ok to ensure widest compatiblity in case
214 // we encounter a weak SNMP agent implementation.
215 VbListMessage
&response
= *rsp
;
216 for (unsigned int i
= 0; i
< response
.Size(); i
++)
218 for (iter
= oids
.begin(); iter
!= oids
.end(); ++iter
)
220 if (response
[i
].Oid().IsChildOf(iter
->oid
))
222 response
[i
].Extract(&iter
->data
);
228 // Done with response
232 // Now process sequences. For each sequence we issue a series of
233 // SNMP GETNEXT-REQUESTs until we detect the end of the sequence.
234 for (iter
= oids
.begin(); iter
!= oids
.end(); ++iter
)
236 if (iter
->data
.type
== Asn::SEQUENCE
)
238 Asn::ObjectId nextoid
= iter
->oid
;
241 // Create a GET-NEXT request
242 VbListMessage
nextreq(Asn::GETNEXT_REQ_PDU
, _community
, _reqid
++);
244 // Request a single OID
245 nextreq
.Append(nextoid
);
247 // Perform the request
248 VbListMessage
*rspmsg
= perform(&nextreq
);
250 // If request failed, we're done (possibly at end of MIB so we
251 // don't consider this an error)
255 // If result OID is not a child of the top-level OID we're fetching
256 // it means we've run off the end of the sequence so we're done
257 VarBind
&result
= (*rspmsg
)[0];
258 if (!result
.Oid().IsChildOf(iter
->oid
))
264 // Extract data and append to sequence
266 result
.Extract(&tmp
);
267 iter
->data
.seq
.append(tmp
);
269 // Save returned OID for next iteration
270 nextoid
= result
.Oid();
279 VbListMessage
*SnmpEngine::perform(VbListMessage
*req
)
285 // Wait for a response
286 VbListMessage
*rsp
= (VbListMessage
*)rspwait(2000);
287 if (rsp
&& rsp
->ErrorStatus())
296 TrapMessage
*SnmpEngine::TrapWait(unsigned int msec
)
301 return (TrapMessage
*)rspwait(msec
, true);
304 bool SnmpEngine::issue(Message
*msg
)
307 unsigned char data
[8192];
308 unsigned int buflen
= sizeof(data
);
309 unsigned char *buffer
= data
;
310 if (!msg
->Marshal(buffer
, buflen
))
313 // Send data to destination
314 int datalen
= buffer
- data
;
315 int rc
= sendto(_socket
, (char*)data
, datalen
, 0,
316 (struct sockaddr
*)&_destaddr
, sizeof(_destaddr
));
326 Message
*SnmpEngine::rspwait(unsigned int msec
, bool trap
)
328 static unsigned char data
[8192];
329 struct sockaddr_in fromaddr
;
331 int sock
= trap
? _trapsock
: _socket
;
333 // Calculate exit time
334 struct timeval exittime
;
335 gettimeofday(&exittime
, NULL
);
336 exittime
.tv_usec
+= msec
* 1000;
337 while (exittime
.tv_usec
>= 1000000)
339 exittime
.tv_usec
-= 1000000;
345 // See if we've run out of time
347 gettimeofday(&now
, NULL
);
348 if (now
.tv_sec
> exittime
.tv_sec
||
349 (now
.tv_sec
== exittime
.tv_sec
&&
350 now
.tv_usec
>= exittime
.tv_usec
))
353 // Calculate new timeout
354 struct timeval timeout
;
355 timeout
.tv_sec
= exittime
.tv_sec
- now
.tv_sec
;
356 timeout
.tv_usec
= exittime
.tv_usec
- now
.tv_usec
;
357 while (timeout
.tv_usec
< 0)
359 timeout
.tv_usec
+= 1000000;
363 // Wait for a datagram to arrive
367 int rc
= select(sock
+1, &fds
, NULL
, NULL
, &timeout
);
370 if (errno
== EAGAIN
|| errno
== EINTR
)
380 socklen_t fromlen
= sizeof(fromaddr
);
381 rc
= recvfrom(sock
, (char*)data
, sizeof(data
), 0,
382 (struct sockaddr
*)&fromaddr
, &fromlen
);
385 if (errno
== EAGAIN
|| errno
== EINTR
)
390 // Ignore packet if it's not from our agent
391 if (fromaddr
.sin_addr
.s_addr
!= _destaddr
.sin_addr
.s_addr
)
394 // Got a packet from our agent: decode it
395 unsigned char *buffer
= data
;
396 unsigned int buflen
= rc
;
397 Message
*msg
= Message::Demarshal(buffer
, buflen
);
401 // Check message type
402 if (trap
&& msg
->Type() == Asn::TRAP_PDU
)
406 else if (!trap
&& msg
->Type() == Asn::GET_RSP_PDU
&&
407 ((VbListMessage
*)msg
)->RequestId() == _reqid
-1)
413 printf("Unhandled SNMP message type: %02x\n", msg
->Type());
416 // Throw it out and try again
421 // *****************************************************************************
423 // *****************************************************************************
425 VarBind::VarBind(const Asn::ObjectId
&oid
, Variable
*data
)
427 _oid
= new Asn::ObjectId(oid
);
434 _data
= new Asn::Integer(data
->type
);
435 *(_data
->AsInteger()) = data
->i32
;
440 _data
= new Asn::Integer(data
->type
);
441 *(_data
->AsInteger()) = data
->u32
;
443 case Asn::OCTETSTRING
:
444 _data
= new Asn::OctetString(data
->str
);
448 _data
= new Asn::Null();
454 _data
= new Asn::Null();
458 VarBind::VarBind(Asn::Sequence
&seq
)
460 if (seq
.Size() == 2 &&
461 seq
[0]->IsObjectId())
463 _oid
= seq
[0]->copy()->AsObjectId();
464 _data
= seq
[1]->copy();
468 _oid
= new Asn::ObjectId();
469 _data
= new Asn::Null();
479 bool VarBind::Extract(Variable
*out
)
481 out
->type
= _data
->Type();
482 if (_data
->IsInteger())
484 out
->i32
= _data
->AsInteger()->IntValue();
485 out
->u32
= _data
->AsInteger()->UintValue();
487 else if (_data
->IsOctetString())
489 out
->str
= *_data
->AsOctetString();
493 printf("Unsupported Asn::Object::AsnType: %d\n", _data
->Type());
499 Asn::Sequence
*VarBind::GetAsn()
501 Asn::Sequence
*seq
= new Asn::Sequence();
502 seq
->Append(_oid
->copy());
503 seq
->Append(_data
->copy());
507 // *****************************************************************************
509 // *****************************************************************************
511 VarBindList::VarBindList(Asn::Sequence
&seq
)
513 for (unsigned int i
= 0; i
< seq
.Size(); i
++)
515 if (seq
[i
]->IsSequence())
516 _vblist
.append(new VarBind(*seq
[i
]->AsSequence()));
520 VarBindList::~VarBindList()
522 for (unsigned int i
= 0; i
< _vblist
.size(); i
++)
526 void VarBindList::Append(const Asn::ObjectId
&oid
, Variable
*data
)
528 _vblist
.append(new VarBind(oid
, data
));
531 Asn::Sequence
*VarBindList::GetAsn()
533 Asn::Sequence
*seq
= new Asn::Sequence();
534 for (unsigned int i
= 0; i
< _vblist
.size(); i
++)
535 seq
->Append(_vblist
[i
]->GetAsn());
539 // *****************************************************************************
541 // *****************************************************************************
542 VbListMessage
*VbListMessage::CreateFromSequence(
543 Asn::Identifier type
, const char *community
, Asn::Sequence
&seq
)
545 // Verify format: We should have 4 parts.
546 if (seq
.Size() != 4 ||
547 !seq
[0]->IsInteger() || // request-id
548 !seq
[1]->IsInteger() || // error-status
549 !seq
[2]->IsInteger() || // error-index
550 !seq
[3]->IsSequence()) // variable-bindings
554 return new VbListMessage(type
, community
, seq
);
557 VbListMessage::VbListMessage(
558 Asn::Identifier type
,
559 const char *community
,
560 Asn::Sequence
&seq
) :
561 Message(type
, community
)
563 // Format was already verified in CreateFromSequence()
564 _reqid
= seq
[0]->AsInteger()->IntValue();
565 _errstatus
= seq
[1]->AsInteger()->IntValue();
566 _errindex
= seq
[2]->AsInteger()->IntValue();
567 _vblist
= new VarBindList(*seq
[3]->AsSequence());
570 VbListMessage::VbListMessage(
571 Asn::Identifier type
,
572 const char *community
,
574 Message(type
, community
),
578 _vblist(new VarBindList())
582 void VbListMessage::Append(const Asn::ObjectId
&oid
, Variable
*data
)
584 _vblist
->Append(oid
, data
);
587 Asn::Sequence
*VbListMessage::GetAsn()
589 Asn::Sequence
*seq
= new Asn::Sequence(_type
);
590 seq
->Append(new Asn::Integer(_reqid
));
591 seq
->Append(new Asn::Integer(_errstatus
));
592 seq
->Append(new Asn::Integer(_errindex
));
593 seq
->Append(_vblist
->GetAsn());
597 // *****************************************************************************
599 // *****************************************************************************
600 Message
*Message::Demarshal(unsigned char *&buffer
, unsigned int &buflen
)
604 Asn::Identifier type
;
606 Asn::Object
*obj
= Asn::Object::Demarshal(buffer
, buflen
);
610 // Data demarshalled okay. Now walk the object tree to parse the message.
612 // Top-level object should be a sequence of length 3
613 Asn::Sequence
&seq
= *(Asn::Sequence
*)obj
;
614 if (!obj
->IsSequence() || seq
.Size() != 3)
617 // First item in sequence is an integer specifying SNMP version
618 if (!seq
[0]->IsInteger() ||
619 seq
[0]->AsInteger()->IntValue() != SNMP_VERSION_1
)
622 // Second item is the community string
623 if (!seq
[1]->IsOctetString())
625 community
= *seq
[1]->AsOctetString();
627 // Third is another sequence containing the PDU
628 type
= seq
[2]->Type();
631 case Asn::GET_REQ_PDU
:
632 case Asn::GETNEXT_REQ_PDU
:
633 case Asn::GET_RSP_PDU
:
634 ret
= VbListMessage::CreateFromSequence(
635 type
, community
, *seq
[2]->AsSequence());
638 ret
= TrapMessage::CreateFromSequence(
639 type
, community
, *seq
[2]->AsSequence());
650 bool Message::Marshal(unsigned char *&buffer
, unsigned int &buflen
)
652 Asn::Sequence
*seq
= new Asn::Sequence();
653 seq
->Append(new Asn::Integer(SNMP_VERSION_1
));
654 seq
->Append(new Asn::OctetString(_community
));
655 seq
->Append(GetAsn());
657 bool ret
= seq
->Marshal(buffer
, buflen
);
662 // *****************************************************************************
664 // *****************************************************************************
665 TrapMessage
*TrapMessage::CreateFromSequence(
666 Asn::Identifier type
, const char *community
, Asn::Sequence
&seq
)
668 // Verify format: We should have 6 parts.
669 if (seq
.Size() != 6 ||
670 !seq
[0]->IsObjectId() || // enterprise
671 !seq
[1]->IsOctetString() || // agent-addr
672 !seq
[2]->IsInteger() || // generic-trap
673 !seq
[3]->IsInteger() || // specific-trap
674 !seq
[4]->IsInteger() || // time-stamp
675 !seq
[5]->IsSequence()) // variable-bindings
679 return new TrapMessage(type
, community
, seq
);
682 TrapMessage::TrapMessage(
683 Asn::Identifier type
,
684 const char *community
,
685 Asn::Sequence
&seq
) :
686 Message(type
, community
)
688 // Format was already verified in CreateFromSequence()
689 _enterprise
= seq
[0]->copy()->AsObjectId();
690 _generic
= seq
[2]->AsInteger()->IntValue();
691 _specific
= seq
[3]->AsInteger()->IntValue();
692 _timestamp
= seq
[4]->AsInteger()->UintValue();
693 _vblist
= new VarBindList(*seq
[5]->AsSequence());