UPS: apcupsd clean sources
[tomato.git] / release / src / router / apcupsd / src / drivers / snmplite / snmp.cpp
blob443b0caccb48e8f29963bd99114b602efc0e5477
1 /*
2 * snmp.cpp
4 * SNMP client interface
5 */
7 /*
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,
22 * MA 02111-1307, USA.
25 #include "apc.h"
26 #include "snmp.h"
27 #include "asn.h"
29 #ifdef __WIN32__
30 #define close(x) closesocket(x)
31 #endif
33 using namespace Snmp;
35 SnmpEngine::SnmpEngine() :
36 _socket(-1),
37 _trapsock(-1),
38 _reqid(0)
42 SnmpEngine::~SnmpEngine()
44 close(_socket);
45 close(_trapsock);
48 bool SnmpEngine::Open(const char *host, unsigned short port, const char *comm, bool trap)
50 // In case we are already open
51 close(_socket);
52 close(_trapsock);
54 // Remember new community name
55 _community = comm;
57 // Generate starting request id
58 struct timeval now;
59 gettimeofday(&now, NULL);
60 _reqid = now.tv_usec;
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)
69 struct hostent he;
70 char *tmphstbuf = NULL;
71 size_t hstbuflen = 0;
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)
76 free(tmphstbuf);
77 return false;
80 memcpy(&_destaddr.sin_addr.s_addr, hp->h_addr,
81 sizeof(_destaddr.sin_addr.s_addr));
82 free(tmphstbuf);
85 // Get a UDP socket
86 _socket = socket(PF_INET, SOCK_DGRAM, 0);
87 if (_socket == -1)
89 perror("socket");
90 return false;
93 // Bind to rx on any interface
94 struct sockaddr_in addr;
95 memset(&addr, 0, sizeof(addr));
96 addr.sin_family = AF_INET;
97 addr.sin_port = 0;
98 addr.sin_addr.s_addr = INADDR_ANY;
99 int rc = bind(_socket, (struct sockaddr*)&addr, sizeof(addr));
100 if (rc == -1)
102 perror("bind");
103 return false;
106 // Open socket for receiving traps, if clients wants one
107 if (trap)
109 _trapsock = socket(PF_INET, SOCK_DGRAM, 0);
110 if (_trapsock == -1)
112 perror("socket");
113 return false;
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));
121 if (rc == -1)
123 perror("bind");
124 return false;
128 return true;
131 void SnmpEngine::Close()
133 close(_socket);
134 _socket = -1;
135 close(_trapsock);
136 _trapsock = -1;
139 bool SnmpEngine::Set(const int oid[], Variable *data)
141 // Send the request
142 VbListMessage req(Asn::SET_REQ_PDU, _community, _reqid++);
143 req.Append(oid, data);
144 if (!issue(&req))
145 return false;
147 // Wait for a response
148 VbListMessage *rsp = (VbListMessage*)rspwait(1000);
149 if (!rsp)
150 return false;
152 // Check error status
153 if (rsp->ErrorStatus())
155 delete rsp;
156 return false;
159 delete rsp;
160 return true;
163 bool SnmpEngine::Get(const int oid[], Variable *data)
165 OidVar oidvar;
166 oidvar.oid = oid;
167 oidvar.data = *data;
169 alist<OidVar> oids;
170 oids.append(oidvar);
172 bool ret = Get(oids);
173 if (ret)
174 *data = (*oids.begin()).data;
176 return ret;
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);
199 if (!rsp)
200 return false;
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())
206 delete rsp;
207 return false;
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);
223 break;
228 // Done with response
229 delete rsp;
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;
239 while (1)
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)
252 if (!rspmsg)
253 break;
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))
260 delete rspmsg;
261 break;
264 // Extract data and append to sequence
265 Variable tmp;
266 result.Extract(&tmp);
267 iter->data.seq.append(tmp);
269 // Save returned OID for next iteration
270 nextoid = result.Oid();
271 delete rspmsg;
276 return true;
279 VbListMessage *SnmpEngine::perform(VbListMessage *req)
281 // Send the request
282 if (!issue(req))
283 return NULL;
285 // Wait for a response
286 VbListMessage *rsp = (VbListMessage*)rspwait(2000);
287 if (rsp && rsp->ErrorStatus())
289 delete rsp;
290 return NULL;
293 return rsp;
296 TrapMessage *SnmpEngine::TrapWait(unsigned int msec)
298 if (_trapsock == -1)
299 return NULL;
301 return (TrapMessage*)rspwait(msec, true);
304 bool SnmpEngine::issue(Message *msg)
306 // Marshal the data
307 unsigned char data[8192];
308 unsigned int buflen = sizeof(data);
309 unsigned char *buffer = data;
310 if (!msg->Marshal(buffer, buflen))
311 return false;
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));
317 if (rc != datalen)
319 perror("sendto");
320 return false;
323 return true;
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;
340 exittime.tv_sec++;
343 while(1)
345 // See if we've run out of time
346 struct timeval now;
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))
351 return NULL;
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;
360 timeout.tv_sec--;
363 // Wait for a datagram to arrive
364 fd_set fds;
365 FD_ZERO(&fds);
366 FD_SET(sock, &fds);
367 int rc = select(sock+1, &fds, NULL, NULL, &timeout);
368 if (rc == -1)
370 if (errno == EAGAIN || errno == EINTR)
371 continue;
372 return NULL;
375 // Timeout
376 if (rc == 0)
377 return NULL;
379 // Read datagram
380 socklen_t fromlen = sizeof(fromaddr);
381 rc = recvfrom(sock, (char*)data, sizeof(data), 0,
382 (struct sockaddr*)&fromaddr, &fromlen);
383 if (rc == -1)
385 if (errno == EAGAIN || errno == EINTR)
386 continue;
387 return NULL;
390 // Ignore packet if it's not from our agent
391 if (fromaddr.sin_addr.s_addr != _destaddr.sin_addr.s_addr)
392 continue;
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);
398 if (!msg)
399 continue;
401 // Check message type
402 if (trap && msg->Type() == Asn::TRAP_PDU)
404 return msg;
406 else if (!trap && msg->Type() == Asn::GET_RSP_PDU &&
407 ((VbListMessage *)msg)->RequestId() == _reqid-1)
409 return msg;
411 else
413 printf("Unhandled SNMP message type: %02x\n", msg->Type());
416 // Throw it out and try again
417 delete msg;
421 // *****************************************************************************
422 // VarBind
423 // *****************************************************************************
425 VarBind::VarBind(const Asn::ObjectId &oid, Variable *data)
427 _oid = new Asn::ObjectId(oid);
429 if (data)
431 switch (data->type)
433 case Asn::INTEGER:
434 _data = new Asn::Integer(data->type);
435 *(_data->AsInteger()) = data->i32;
436 break;
437 case Asn::TIMETICKS:
438 case Asn::COUNTER:
439 case Asn::GAUGE:
440 _data = new Asn::Integer(data->type);
441 *(_data->AsInteger()) = data->u32;
442 break;
443 case Asn::OCTETSTRING:
444 _data = new Asn::OctetString(data->str);
445 break;
446 case Asn::NULLL:
447 default:
448 _data = new Asn::Null();
449 break;
452 else
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();
466 else
468 _oid = new Asn::ObjectId();
469 _data = new Asn::Null();
473 VarBind::~VarBind()
475 delete _data;
476 delete _oid;
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();
491 else
493 printf("Unsupported Asn::Object::AsnType: %d\n", _data->Type());
494 return false;
496 return true;
499 Asn::Sequence *VarBind::GetAsn()
501 Asn::Sequence *seq = new Asn::Sequence();
502 seq->Append(_oid->copy());
503 seq->Append(_data->copy());
504 return seq;
507 // *****************************************************************************
508 // VarBindList
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++)
523 delete _vblist[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());
536 return seq;
539 // *****************************************************************************
540 // VbListMessage
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
551 return NULL;
553 // Extract data
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,
573 int reqid) :
574 Message(type, community),
575 _reqid(reqid),
576 _errstatus(0),
577 _errindex(0),
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());
594 return seq;
597 // *****************************************************************************
598 // Message
599 // *****************************************************************************
600 Message *Message::Demarshal(unsigned char *&buffer, unsigned int &buflen)
602 Message *ret = NULL;
603 astring community;
604 Asn::Identifier type;
606 Asn::Object *obj = Asn::Object::Demarshal(buffer, buflen);
607 if (!obj)
608 return NULL;
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)
615 goto error;
617 // First item in sequence is an integer specifying SNMP version
618 if (!seq[0]->IsInteger() ||
619 seq[0]->AsInteger()->IntValue() != SNMP_VERSION_1)
620 goto error;
622 // Second item is the community string
623 if (!seq[1]->IsOctetString())
624 goto error;
625 community = *seq[1]->AsOctetString();
627 // Third is another sequence containing the PDU
628 type = seq[2]->Type();
629 switch (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());
636 break;
637 case Asn::TRAP_PDU:
638 ret = TrapMessage::CreateFromSequence(
639 type, community, *seq[2]->AsSequence());
640 break;
641 default:
642 break;
645 error:
646 delete obj;
647 return ret;
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);
658 delete seq;
659 return ret;
662 // *****************************************************************************
663 // TrapMessage
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
676 return NULL;
678 // Extract data
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());