Repository: calmh/node-snmp-native Branch: master Commit: 46e7780c4ad7 Files: 18 Total size: 404.5 KB Directory structure: gitextract_v45mz64j/ ├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── docs/ │ ├── asn1ber.html │ ├── coverage.html │ ├── docco.css │ ├── example.html │ ├── public/ │ │ └── stylesheets/ │ │ └── normalize.css │ └── snmp.html ├── example.js ├── lib/ │ ├── asn1ber.js │ └── snmp.js ├── package.json └── test/ ├── asn1ber.js ├── integration.js └── snmp.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .*.swp gmon.out node_modules lib-cov ================================================ FILE: .jshintrc ================================================ { "curly": true, "eqeqeq": true, "forin": true, "immed": true, "latedef": true, "newcap": true, "noarg": true, "noempty": true, "undef": true, "trailing": true, "node": true, "nomen": false, "plusplus": false } ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - node - lts/* - '12' - '10' - '8' cache: yarn ================================================ FILE: LICENSE ================================================ Copyright (C) 2012 Jakob Borg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ __ /\ \__ __ ____ ___ ___ ___ _____ ___ __ \ \ ,_\/\_\ __ __ __ /',__\ /' _ `\ /' __` __`\/\ '__`\ _______ /' _ `\ /'__`\ \ \ \/\/\ \ /\ \/\ \ /'__`\ /\__, `\/\ \/\ \/\ \/\ \/\ \ \ \L\ \/\______\/\ \/\ \/\ \L\.\_\ \ \_\ \ \\ \ \_/ |/\ __/ \/\____/\ \_\ \_\ \_\ \_\ \_\ \ ,__/\/______/\ \_\ \_\ \__/.\_\\ \__\\ \_\\ \___/ \ \____\ \/___/ \/_/\/_/\/_/\/_/\/_/\ \ \/ \/_/\/_/\/__/\/_/ \/__/ \/_/ \/__/ \/____/ \ \_\ \/_/ snmp-native [![Build Status](https://secure.travis-ci.org/calmh/node-snmp-native.png)](http://travis-ci.org/calmh/node-snmp-native) =========== This is a native SNMP library for Node.js. The purpose is to provide enough functionality to perform large scale monitoring of network equipment. Current features towards this end are: - Full implementation of SNMPv2c, including 64 bit data types. - Support for Get, GetNext and Set requests, with optimizations such as GetAll and GetSubtree. - No unusual external dependencies, no non-JavaScript code. - Very high performance, unlimited parallellism. (There are always limits. However, there are no arbitrary such imposed by this code and you at least won't run out of file descriptors.) - De facto standards compliance. Generated packets are compared against Net-SNMP and should be identical in all relevant aspects. - Well tested. Test coverage should be at or close to 100% for all important code paths. It specifically does *not* include: - Compatibility with SNMPv1, SNMPv2u or SNMPv3. These are (in order) deprecated, weird, and too complicated. Yes, it's an opinionated library. - MIB parsing. Do this in your client app if it's necessary. It's optimized for polling tens of thousands of counters on hundreds or thousands of hosts in a parallell manner. This is known to work (although performance might be limited by less than optimal SNMP agent implementations in random network gear). Documentation ============= Installation ------------ $ npm install snmp-native Usage ----- ### Import ```javascript var snmp = require('snmp-native'); ``` ### new Session(options) Create a `Session`. The `Session` constructor, like most of the other functions, take an `options` object. The options passed to the `Session` will be the defaults for any subsequent function calls on that session, but can be overridden as needed. Useful parameters here are `host`, `port` and `family`. ```javascript // Create a Session with default settings. var session = new snmp.Session(); // Create a Session with explicit default host, port, and community. var session = new snmp.Session({ host: 'device.example.com', port: 161, community: 'special' }); // Create an IPv6 Session. var session = new snmp.Session({ host: '2001:db8::42', family: 'udp6', community: 'private' }); ``` The following options are recognized as properties in the options object. All can be specified in the `Session` constructor and optionally overridden at a later time by setting them in the option object to a method call. For optimum performance when polling many hosts, create a session without specifying the `host`. Reuse this session for all hosts and specify the `host` on each `get`, `getAll`, etc. - `host`: The host to send the request to. An resolvable name is allowed in addition to IP addresses. Default: `'localhost'`. - `port`: The UDP port number to send the request to. Default: `161`. - `community`: The SNMP community name. Default: `'public'`. - `family`: Address family to bind to. This is only used by the `Session` constructor since that is when the bind is done. It cannot be changed or overridden after construction. Default: `'udp4'`. Valid values: `'udp4'` or `'udp6'`. - `timeouts`: An array of timeout values. Values are times in milliseconds, the length of the array is the total number of transmissions that will occur. Default: `[5000, 5000, 5000, 5000]` (four attempts, with five seconds between each). A backoff can be implemented by timeouts along the lines of `[ 1000, 2000, 4000, 8000 ]`. Retransmissions can be disabled by using only a single timeout value: `[ 5000 ]`. - `bindPort`: UDP port used to bind the socket locally. Default: `0` (random port) - `msgReceived`: A `(message, rinfo) => {}` function responsible to handle incoming messages and sending UDP responses back. If nothing is given here, the default implementation is used. This is useful if you want to implement custom logic in your application ### VarBind objects All of the `get*` functions return arrays of `VarBind` as the result to the callback. The `VarBind` objects have the following properties: - `oid`: The OID they represent (in array form). - `type`: The integer type code for the returned value. - `value`: The value, in decoded form. This will be an integer for integer, gauge, counter and timetick types, a string for an octet string value, an array for array or IP number types. - `valueRaw`: For octet string values, this is a raw `Buffer` representing the string. - `valueHex`: For octet string values, this is a hex string representation of the value. - `sendStamp`: The timestamp (in milliseconds) when the request was transmitted. - `receiveStamp`: The timestamp (in milliseconds) when the response was received. ### get(options, callback) Perform a simple GetRequest. Options (in addition to the ones defined above for `Session`): - `oid`: The OID to get. Example: `[1, 3, 6, 1, 4, 1, 1, 2, 3, 4]` or `'.1.3.6.1.4.1.1.2.3.4'`. Both forms are accepted, but the string form will need to be parsed to an array, slightly increasing CPU usage. Will call the specified `callback` with an `error` object (`null` on success) and the varbind that was received. ```javascript session.get({ oid: [1, 3, 6, 1, 4, 1, 42, 1, 0] }, function (error, varbinds) { if (error) { console.log('Fail :('); } else { console.log(varbinds[0].oid + ' = ' + varbinds[0].value + ' (' + varbinds[0].type + ')'); } }); ``` You can also specify host, community, etc explicitly. ```javascript session.get({ oid: [1, 3, 6, 1, 4, 1, 42, 1, 0], host: 'localhost', community: 'test' }, ...); ``` ### getNext(options, callback) Perform a simple GetNextRequest. Options: - `oid`: The OID to get. Example: `[1, 3, 6, 1, 4, 1, 1, 2, 3, 4]` or `'.1.3.6.1.4.1.1.2.3.4'`. Will call the specified `callback` with an `error` object (`null` on success) and the varbind that was received. ```javascript session.getNext({ oid: [1, 3, 6, 1, 4, 1, 42, 1, 0] }, function (error, varbinds) { if (error) { console.log('Fail :('); } else { console.log(varbinds[0].oid + ' = ' + varbinds[0].value + ' (' + varbinds[0].type + ')'); } }); ``` ### getAll(options, callback) Perform repeated GetRequests to fetch all the required values. Multiple OIDs will get packed into as few GetRequest packets as possible to minimize roundtrip delays. Gets will be issued serially (not in parallell) to avoid flooding hosts. Options: - `oids`: An array of OIDs to get. Example: `[[1, 3, 6, 1, 4, 1, 1, 2, 3], [1, 3, 6, 1, 4, 1, 1, 2, 4]]` or `['.1.3.6.1.4.1.1.2.3.4', '.1.3.6.1.4.1.2.3.4.5']`. - `abortOnError`: Whether to stop or continue when an error is encountered. Default: `false`. - `combinedTimeout`: Timeout in milliseconds that the getAll() may take. Default: no timeout. The callback will be called with an error object or a list of varbinds. If the options property `abortOnError` is false (default) any variables that couldn't be fetched will simply be omitted from the results. If it is true, the callback will be called with an error object on any failure. If the `combinedTimeout` is triggered, the callback is called with an error and the partial results. ```javascript var oids = [ [1, 3, 6, 1, 4, 1, 42, 1, 0], [1, 3, 6, 1, 4, 1, 42, 2, 0], ... ]; session.getAll({ oids: oids }, function (error, varbinds) { varbinds.forEach(function (vb) { console.log(vb.oid + ' = ' + vb.value + ' (' + vb.type + ')'); }); }); ``` ### getSubtree(options, callback) Perform repeated GetNextRequests to fetch all values in the specified tree. Options: - `oid`: The OID to get. Example: `[1, 3, 6, 1, 4, 1, 1, 2, 3, 4]` or `'.1.3.6.1.4.1.1.2.3.4'`. - `combinedTimeout`: Timeout in milliseconds that the getSubtree() may take. Default: no timeout. Will call the specified `callback` with an `error` object (`null` on success) and the list of varbinds that was fetched. If the `combinedTimeout` is triggered, the callback is called with an error and the partial results. ```javascript session.getSubtree({ oid: [1, 3, 6, 1, 4, 1, 42] }, function (error, varbinds) { if (error) { console.log('Fail :('); } else { varbinds.forEach(function (vb) { console.log(vb.oid + ' = ' + vb.value + ' (' + vb.type + ')'); }); } }); ``` ### set(options, callback) Perform a simple SetRequest. Options: - `oid`: The OID to perform the set on. Example: `[1, 3, 6, 1, 4, 1, 1, 2, 3, 4]` or `'.1.3.6.1.4.1.1.2.3.4'`. - `value`: The value to set. Example: `42`. - `type`: The type of the value. Currently supports `asn1ber.T.Integer` (2), `asn1ber.T.Gauge` (66), `asn1ber.T.IpAddress` (64), `asn1ber.T.OctetString` (4) and `asn1ber.T.Null` (5). Example: `2`. Example: ```javascript session.set({ oid: [1, 3, 6, 1, 4, 1, 42, 1, 0], value: 42, type: 2 }, function (error, varbind) { if (error) { console.log('Fail :('); } else { console.log('The set is done.'); } }); ``` If you're not really interested in the outcome of the set (and if you are, why aren't you using scripted telnet or ssh instead to begin with?), you can call it without a callback: ```javascript session.set({ oid: [1, 3, 6, 1, 4, 1, 42, 1, 0], value: 42, type: 2 }); ``` ### close() Cancels all outstanding requests and frees used OS resources. Outstanding requests will call their callback with the "Cancelled" error set. Example: ```javascript session.close(); ``` License ======= MIT ================================================ FILE: docs/asn1ber.html ================================================ asn1ber.js
================================================ FILE: docs/coverage.html ================================================ Coverage

Coverage

96%
473
457
16

lib/asn1ber.js

98%
156
153
3
LineHitsSource
1// This file implements a minimal subset of Abstract Syntax Notation One (**ASN.1**)
2// Basic Encoding Rules (**BER**), namely the parts that are necessary for sending
3// and receiving SNMPv2c messages.
4//
5// (c) 2012 Jakob Borg, Nym Networks
6
7"use strict";
8
9// We define constants for the commonly used ASN.1 types in SNMP.
10
111var T = {
12 Integer: 0x02,
13 OctetString: 0x04,
14 Null: 0x05,
15 ObjectIdentifier: 0x06,
16 Sequence: 0x30,
17 IpAddress: 0x40,
18 Counter: 0x41,
19 Gauge: 0x42,
20 TimeTicks: 0x43,
21 Opaque: 0x44,
22 NsapAddress: 0x45,
23 Counter64: 0x46,
24 NoSuchObject: 0x80,
25 NoSuchInstance: 0x81,
26 EndOfMibView: 0x82,
27 PDUBase: 0xA0
28};
29
301var P = {
31 GetRequestPDU: 0x00,
32 GetNextRequestPDU: 0x01,
33 GetResponsePDU: 0x02,
34 SetRequestPDU: 0x03
35};
36
37// The types are also available for consumers of the library.
38
391exports.types = T;
401exports.pduTypes = P;
411exports.unittest = {};
42
43// Private helper functions
44// -----
45
46// Encode a length as it should be encoded.
47
48function lengthArray(len) {
49345 var arr = [];
50
51345 if (len <= 127) {
52 // Return a single byte if the value is 127 or less.
53339 return [ len ];
54 } else {
55 // Otherwise encode it as a MSB base-256 integer.
566 while (len > 0) {
578 arr.push(len % 256);
588 len = parseInt(len / 256, 10);
59 }
60 // Add a length byte in front and set the high bit to indicate
61 // that this is a longer value than one byte.
626 arr.push(128 + arr.length);
636 arr.reverse();
646 return arr;
65 }
66}
67
681exports.unittest.lengthArray = lengthArray;
69
70// Return a wrapped copy of the passed `contents`, with the specified wrapper type.
71// This is used for Sequence and other constructed types.
72
73function wrapper(type, contents) {
74266 var buf, len, i;
75
76 // Get the encoded length of the contents
77266 len = lengthArray(contents.length);
78
79 // Set up a buffer with the type and length bytes plus a straight copy of the content.
80266 buf = new Buffer(1 + contents.length + len.length);
81266 buf[0] = type;
82266 for (i = 1; i < len.length + 1; i++) {
83271 buf[i] = len[i - 1];
84 }
85266 contents.copy(buf, len.length + 1, 0);
86266 return buf;
87}
88
89// Get the encoded representation of a number in an OID.
90// If the number is less than 128, pass it as it is.
91// Otherwise return an array of base-128 digits, most significant first,
92// with the high bit set on all octets except the last one.
93// This encoding should be used for all number in an OID except the first
94// two (.1.3) which are handled specially.
95
96function oidInt(val) {
973 var bytes = [];
98
993 bytes.push(val % 128);
1003 val = parseInt(val / 128, 10);
1013 while (val > 127) {
1021 bytes.push(128 + val % 128);
1031 val = parseInt(val / 128, 10);
104 }
1053 bytes.push(val + 128);
1063 return bytes.reverse();
107}
108
109// Encode an OID. The first two number are encoded specially
110// in the first octet, then the rest are encoded as one octet per number
111// unless the number exceeds 127. If so, it's encoded as several base-127
112// octets with the high bit set to indicate continuation.
113
114function oidArray(oid) {
11576 var bytes, i, val;
116
117 // Enforce some minimum requirements on the OID.
11876 if (oid.length < 2) {
1191 throw new Error("Minimum OID length is two.");
12075 } else if (oid[0] !== 1 || oid[1] !== 3) {
1211 throw new Error("SNMP OIDs always start with .1.3.");
122 }
123
124 // Calculate the first byte of the encoded OID according to the 'special' rule.
12574 bytes = [ 40 * oid[0] + oid[1] ];
126
127 // For the rest of the OID, encode each number individually and add the
128 // resulting bytes to the buffer.
12974 for (i = 2; i < oid.length; i++) {
130511 val = oid[i];
131511 if (val > 127) {
1323 bytes = bytes.concat(oidInt(val));
133 } else {
134508 bytes.push(val);
135 }
136 }
137
13874 return bytes;
139}
140
141// Divide an integer into base-256 bytes.
142// Most significant byte first.
143function intArray(val) {
144198 var array = [];
145
146198 if (val === 0) {
14794 array.push(0);
148 } else {
149104 while (val > 0) {
150246 array.push(val % 256);
151246 val = parseInt(val / 256, 10);
152 }
153 }
154
155 // Do not produce integers that look negative (high bit
156 // of first byte set).
157198 if (array[array.length - 1] >= 0x80) {
1582 array.push(0);
159 }
160
161198 return array.reverse();
162}
163
164// Functions to encode ASN.1 from native objects
165// -----
166
167// Encode a simple integer.
168// Integers are encoded as a simple base-256 byte sequence, most significant byte first,
169// prefixed with a length byte. In principle we support arbitrary integer sizes, in practice
170// Javascript doesn't even **have** integers so some precision might get lost.
171
1721exports.encodeInteger = function (val) {
173198 var i, arr, buf;
174
175 // Get the bytes that we're going to encode.
176198 arr = intArray(val);
177
178 // Now that we know the length, we allocate a buffer of the required size.
179 // We set the type and length bytes appropriately.
180198 buf = new Buffer(2 + arr.length);
181198 buf[0] = T.Integer;
182198 buf[1] = arr.length;
183
184 // Copy the bytes into the array.
185198 for (i = 0; i < arr.length; i++) {
186342 buf[i + 2] = arr[i];
187 }
188
189198 return buf;
190};
191
192// Create the representation of a Null, `05 00`.
193
1941exports.encodeNull = function () {
19567 var buf = new Buffer(2);
19667 buf[0] = T.Null;
19767 buf[1] = 0;
19867 return buf;
199};
200
201// Encode a Sequence, which is a wrapper of type `30`.
202
2031exports.encodeSequence = function (contents) {
204168 return wrapper(T.Sequence, contents);
205};
206
207// Encode an OctetString, which is a wrapper of type `04`.
208
2091exports.encodeOctetString = function (string) {
21052 var buf, contents;
211
21252 if (typeof string === 'string') {
21349 contents = new Buffer(string);
2143 } else if (Buffer.isBuffer(string)) {
2151 contents = string;
216 } else {
2172 throw new Error('Only Buffer and string types are acceptable as OctetString.');
218 }
219
22050 return wrapper(T.OctetString, contents);
221};
222
223// Encode an IpAddress, which is a wrapper of type `40`.
224
2251exports.encodeIpAddress = function (address) {
2261 var contents, octets, value = [];
227
2281 if (typeof address !== 'string' && !Buffer.isBuffer(address)) {
2290 throw new Error('Only Buffer and string types are acceptable as OctetString.');
230 }
231
232 // assume that the string is in dotted decimal format ipv4
233 // also, use toString in case a buffer was passed in.
234
2351 octets = address.toString().split('.');
2361 if (octets.length !== 4) {
2370 throw new Error('IP Addresses must be specified in dotted decimal format.');
238 }
2391 octets.forEach(function (octet) {
2404 var octetValue = parseInt(octet, 10);
2414 if (octet < 0 || octet > 255) {
2420 throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets));
243 }
2444 value.push(octetValue);
245 });
246
2471 contents = new Buffer(value);
248
2491 return wrapper(T.IpAddress, contents);
250};
251
252// Encode an ObjectId.
253
2541exports.encodeOid = function (oid) {
25576 var buf, bytes, i, len;
256
257 // Get the encoded format of the OID.
25876 bytes = oidArray(oid);
259
260 // Get the encoded format of the length
26174 len = lengthArray(bytes.length);
262
263 // Fill in the buffer with type, length and OID data.
26474 buf = new Buffer(1 + bytes.length + len.length);
26574 buf[0] = T.ObjectIdentifier;
26674 for (i = 1; i < len.length + 1; i++) {
26774 buf[i] = len[i - 1];
268 }
26974 for (i = len.length + 1; i < bytes.length + len.length + 1; i++) {
270589 buf[i] = bytes[i - len.length - 1];
271 }
272
27374 return buf;
274};
275
276// Encode an SNMP request with specified `contents`.
277// The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`.
278
2791exports.encodeRequest = function (type, contents) {
28047 return wrapper(T.PDUBase + type, contents);
281};
282
283// Functions to parse ASN.1 to native objects
284// -----
285
286// Parse and return type, data length and header length.
287function typeAndLength(buf) {
288268 var res, len, i;
289
290268 res = { type: buf[0], len: 0, header: 1 };
291268 if (buf[1] < 128) {
292 // If bit 8 is zero, this byte indicates the content length (up to 127 bytes).
293261 res.len = buf[1];
294261 res.header += 1;
295 } else {
296 // If bit 8 is 1, bits 0 to 7 indicate the number of following legth bytes.
297 // These bytes are a simple msb base-256 integer indicating the content length.
2987 for (i = 0; i < buf[1] - 128; i++) {
29910 res.len += buf[i + 1];
30010 res.len *= 256;
301 }
3027 res.header += buf[1] - 128 + 1;
303 }
304268 return res;
305}
306
3071exports.typeAndLength = typeAndLength;
308
309// Parse a buffer containing a representation of an integer.
310// Verifies the type, then multiplies in each byte as it comes.
311
3121exports.parseInteger = function (buf) {
313281 var i, val;
314
315281 if (buf[0] !== T.Integer && buf[0] !== T.Counter &&
316 buf[0] !== T.Counter64 && buf[0] !== T.Gauge &&
317 buf[0] !== T.TimeTicks) {
3181 throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer');
319 }
320
321280 val = 0;
322280 for (i = 0; i < buf[1]; i++) {
323547 val *= 256;
324547 val += buf[i + 2];
325 }
326
327280 return val;
328};
329
330// Parse a buffer containing a representation of an OctetString.
331// Verify the type, then just grab the string out of the buffer.
332
3331exports.parseOctetString = function (buf) {
33478 if (buf[0] !== T.OctetString) {
3351 throw new Error('Buffer does not appear to be an OctetString');
336 }
337
338 // SNMP doesn't specify an encoding so I pick UTF-8 as the 'most standard'
339 // encoding. We'll see if that assumption survives contact with actual reality.
34077 return buf.toString('utf-8', 2, 2 + buf[1]);
341};
342
343// Parse a buffer containing a representation of an ObjectIdentifier.
344// Verify the type, then apply the relevent encoding rules.
345
3461exports.parseOid = function (buf) {
34790 var oid, val, i;
348
34990 if (buf[0] !== T.ObjectIdentifier) {
3501 throw new Error('Buffer does not appear to be an ObjectIdentifier');
351 }
352
353 // The first byte contains the first two numbers in the OID
35489 oid = [ parseInt(buf[2] / 40, 10), buf[2] % 40 ];
355
356 // The rest of the data is a base-128-encoded OID
35789 for (i = 0; i < buf[1] - 1; i++) {
358681 val = 0;
359681 while (buf[i + 3] >= 128) {
36017 val += buf[i + 3] - 128;
36117 val *= 128;
36217 i++;
363 }
364681 val += buf[i + 3];
365681 oid.push(val);
366 }
367
36889 return oid;
369};
370
371// Parse a buffer containing a representation of an array type.
372// This is for example an IpAddress.
373
3741exports.parseArray = function (buf) {
3753 var i, nelem, array;
376
3773 if (buf[0] !== T.IpAddress) {
3781 throw new Error('Buffer does not appear to be an array type.');
379 }
380
3812 nelem = buf[1];
3822 array = [];
383
3842 for (i = 0; i < buf[1]; i++) {
3858 array.push(buf[i + 2]);
386 }
387
3882 return array;
389};
390
391// Parse a buffer containing a representation of an opaque type.
392// This is for example an IpAddress.
393
3941exports.parseOpaque = function (buf) {
3954 var hdr;
396
3974 hdr = typeAndLength(buf);
398
3994 if (hdr.type !== T.Opaque) {
4001 throw new Error('Buffer does not appear to be an opaque type.');
401 }
402
4033 return '0x' + buf.slice(hdr.header).toString('hex');
404};
405
406/*globals exports: false*/
407

lib/snmp.js

95%
317
304
13
LineHitsSource
1// Introduction
2// -----
3// This is `node-snmp-native`, a native (Javascript) implementation of an SNMP
4// client library targeted at Node.js. It's MIT licensed and available at
5// https://github.com/calmh/node-snmp-native
6//
7// (c) 2012 Jakob Borg, Nym Networks
8
9"use strict";
10
11// Code
12// -----
13// This file implements a structure representing an SNMP message
14// and routines for converting to and from the network representation.
15
16// Define our external dependencies.
171var assert = require('assert');
181var dgram = require('dgram');
191var events = require('events');
20
21// We also need our ASN.1 BER en-/decoding routines.
221var asn1ber = require('./asn1ber');
23
24// Basic structures
25// ----
26
27// A `VarBind` is the innermost structure, containing an OID-Value pair.
28function VarBind() {
29223 this.type = 5;
30223 this.value = null;
31}
32
33// The `PDU` contains the SNMP request or response fields and a list of `VarBinds`.
34function PDU() {
35108 this.type = asn1ber.pduTypes.GetRequestPDU;
36108 this.reqid = 1;
37108 this.error = 0;
38108 this.errorIndex = 0;
39108 this.varbinds = [ new VarBind() ];
40}
41
42// The `Packet` contains the SNMP version and community and the `PDU`.
43function Packet() {
44108 this.version = 1;
45108 this.community = 'public';
46108 this.pdu = new PDU();
47}
48
49// Allow consumers to create packet structures from scratch.
501exports.Packet = Packet;
51
52// Private helper functions
53// ----
54
55// Concatenate several buffers to one.
56function concatBuffers(buffers) {
57210 var total, cur = 0, buf;
58
59 // First we calculate the total length,
60210 total = buffers.reduce(function (tot, b) {
61538 return tot + b.length;
62 }, 0);
63
64 // then we allocate a new Buffer large enough to contain all data,
65210 buf = new Buffer(total);
66210 buffers.forEach(function (buffer) {
67 // finally we copy the data into the new larger buffer.
68538 buffer.copy(buf, cur, 0);
69538 cur += buffer.length;
70 });
71
72210 return buf;
73}
74
75// Clear a pending packet when it times out or is successfully received.
76
77function clearRequest(reqs, reqid) {
7843 var self = this;
79
8043 var entry = reqs[reqid];
8143 if (entry) {
8243 if (entry.timeout) {
8342 clearTimeout(entry.timeout);
84 }
8543 delete reqs[reqid];
86 }
87}
88
89// Convert a string formatted OID to an array, leaving anything non-string alone.
90
91function parseSingleOid(oid) {
9279 if (typeof oid !== 'string') {
9359 return oid;
94 }
95
9620 if (oid[0] !== '.') {
974 throw new Error('Invalid OID format');
98 }
99
10016 oid = oid.split('.')
101 .filter(function (s) {
102154 return s.length > 0;
103 })
104 .map(function (s) {
105138 return parseInt(s, 10);
106 });
107
10816 return oid;
109}
110
111// Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings.
112
113function parseOids(options) {
11458 if (options.oid) {
11548 options.oid = parseSingleOid(options.oid);
116 }
11755 if (options.oids) {
1185 options.oids = options.oids.map(parseSingleOid);
119 }
120}
121
122// Update targ with attributes from _defs.
123// Any existing attributes on targ are untouched.
124
125function defaults(targ, _defs) {
126159 [].slice.call(arguments, 1).forEach(function (def) {
127164 Object.keys(def).forEach(function (key) {
128818 if (!targ.hasOwnProperty(key)) {
129468 targ[key] = def[key];
130 }
131 });
132 });
133}
134
135// Encode structure to ASN.1 BER
136// ----
137
138// Return an ASN.1 BER encoding of a Packet structure.
139// This is suitable for transmission on a UDP socket.
140function encode(pkt) {
14147 var version, community, reqid, err, erridx, vbs, pdu, message;
142
143 // We only support SNMPv2c, so enforce that version stamp.
14447 if (pkt.version !== 1) {
1450 throw new Error('Only SNMPv2c is supported.');
146 }
147
148 // Encode the message header fields.
14947 version = asn1ber.encodeInteger(pkt.version);
15047 community = asn1ber.encodeOctetString(pkt.community);
151
152 // Encode the PDU header fields.
15347 reqid = asn1ber.encodeInteger(pkt.pdu.reqid);
15447 err = asn1ber.encodeInteger(pkt.pdu.error);
15547 erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex);
156
157 // Encode the PDU varbinds.
15847 vbs = [];
15947 pkt.pdu.varbinds.forEach(function (vb) {
16073 var oid = asn1ber.encodeOid(vb.oid), val;
161
16273 if (vb.type === asn1ber.types.Null) {
16366 val = asn1ber.encodeNull();
1647 } else if (vb.type === asn1ber.types.Integer) {
1655 val = asn1ber.encodeInteger(vb.value);
1662 } else if (vb.type === asn1ber.types.IpAddress) {
1671 val = asn1ber.encodeIpAddress(vb.value);
1681 } else if (vb.type === asn1ber.types.OctetString) {
1691 val = asn1ber.encodeOctetString(vb.value);
170 } else {
1710 throw new Error('Unknown varbind type "' + vb.type + '" in encoding.');
172 }
17372 vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val])));
174 });
175
176 // Concatenate all the varbinds together.
17746 vbs = asn1ber.encodeSequence(concatBuffers(vbs));
178
179 // Create the PDU by concatenating the inner fields and adding a request structure around it.
18046 pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs]));
181
182 // Create the message by concatenating the header fields and the PDU.
18346 message = asn1ber.encodeSequence(concatBuffers([version, community, pdu]));
184
18546 return message;
186}
187
1881exports.encode = encode;
189
190// Parse ASN.1 BER into a structure
191// -----
192
193// Parse an SNMP packet into its component fields.
194// We don't do a lot of validation so a malformed packet will probably just
195// make us blow up.
196
197function parse(buf) {
19861 var pkt, oid, bvb, vb, hdr;
199
20061 pkt = new Packet();
201
202 // First we have a sequence marker (two bytes).
203 // We don't care about those, so cut them off.
20461 hdr = asn1ber.typeAndLength(buf);
20561 assert.equal(asn1ber.types.Sequence, hdr.type);
20659 buf = buf.slice(hdr.header);
207
208 // Then comes the version field (integer). Parse it and slice it.
20959 pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
21059 buf = buf.slice(2 + buf[1]);
211
212 // We then get the community. Parse and slice.
21359 pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2));
21459 buf = buf.slice(2 + buf[1]);
215
216 // Here's the PDU structure. We're interested in the type. Slice the rest.
21759 hdr = asn1ber.typeAndLength(buf);
21859 assert.ok(hdr.type >= 0xA0);
21959 pkt.pdu.type = hdr.type - 0xA0;
22059 buf = buf.slice(hdr.header);
221
222 // The request id field.
22359 pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
22459 buf = buf.slice(2 + buf[1]);
225
226 // The error field.
22759 pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
22859 buf = buf.slice(2 + buf[1]);
229
230 // The error index field.
23159 pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2));
23259 buf = buf.slice(2 + buf[1]);
233
234 // Here's the varbind list. Not interested.
23559 hdr = asn1ber.typeAndLength(buf);
23659 assert.equal(asn1ber.types.Sequence, hdr.type);
23759 buf = buf.slice(hdr.header);
238
239 // Now comes the varbinds. There might be many, so we loop for as long as we have data.
24059 pkt.pdu.varbinds = [];
24159 while (buf[0] === asn1ber.types.Sequence) {
24285 vb = new VarBind();
243
244 // Slice off the sequence header.
24585 hdr = asn1ber.typeAndLength(buf);
24685 assert.equal(asn1ber.types.Sequence, hdr.type);
24785 bvb = buf.slice(hdr.header);
248
249 // Parse and save the ObjectIdentifier.
25085 vb.oid = asn1ber.parseOid(bvb);
251
252 // Parse the value. We use the type marker to figure out
253 // what kind of value it is and call the appropriate parser
254 // routine. For the SNMPv2c error types, we simply set the
255 // value to a text representation of the error and leave handling
256 // up to the user.
25785 bvb = bvb.slice(2 + bvb[1]);
25885 vb.type = bvb[0];
25985 if (vb.type === asn1ber.types.Null) {
260 // Null type.
26116 vb.value = null;
26269 } else if (vb.type === asn1ber.types.OctetString) {
263 // Octet string type.
26416 vb.value = asn1ber.parseOctetString(bvb);
26553 } else if (vb.type === asn1ber.types.Integer ||
266 vb.type === asn1ber.types.Counter ||
267 vb.type === asn1ber.types.Counter64 ||
268 vb.type === asn1ber.types.TimeTicks ||
269 vb.type === asn1ber.types.Gauge) {
270 // Integer type and it's derivatives that behave in the same manner.
27141 vb.value = asn1ber.parseInteger(bvb);
27212 } else if (vb.type === asn1ber.types.ObjectIdentifier) {
273 // Object identifier type.
2741 vb.value = asn1ber.parseOid(bvb);
27511 } else if (vb.type === asn1ber.types.IpAddress) {
276 // IP Address type.
2771 vb.value = asn1ber.parseArray(bvb);
27810 } else if (vb.type === asn1ber.types.Opaque) {
279 // Opaque type. The 'parsing' here is very light; basically we return a
280 // string representation of the raw bytes in hex.
2812 vb.value = asn1ber.parseOpaque(bvb);
2828 } else if (vb.type === asn1ber.types.EndOfMibView) {
283 // End of MIB view error, returned when attempting to GetNext beyond the end
284 // of the current view.
2851 vb.value = 'endOfMibView';
2867 } else if (vb.type === asn1ber.types.NoSuchObject) {
287 // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist.
2881 vb.value = 'noSuchObject';
2896 } else if (vb.type === asn1ber.types.NoSuchInstance) {
290 // No such instance error, returned when attempting to Get/GetNext an instance
291 // that doesn't exist in a given table.
2926 vb.value = 'noSuchInstance';
293 } else {
294 // Something else that we can't handle, so throw an error.
295 // The error will be caught and presented in a useful manner on stderr,
296 // with a dump of the message causing it.
2970 throw new Error('Unrecognized value type ' + vb.type);
298 }
299
300 // Take the raw octet string value and preseve it as a buffer and hex string.
30185 vb.valueRaw = bvb.slice(2);
30285 vb.valueHex = vb.valueRaw.toString('hex');
303
304 // Add the request id to the varbind (even though it doesn't really belong)
305 // so that it will be availble to the end user.
30685 vb.requestId = pkt.pdu.reqid;
307
308 // Push whatever we parsed to the varbind list.
30985 pkt.pdu.varbinds.push(vb);
310
311 // Go fetch the next varbind, if there seems to be any.
31285 if (buf.length > hdr.header + hdr.len) {
31326 buf = buf.slice(hdr.header + hdr.len);
314 } else {
31559 break;
316 }
317 }
318
31959 return pkt;
320}
321
3221exports.parse = parse;
323
324// Utility functions
325// -----
326
327// Compare two OIDs, returning -1, 0 or +1 depending on the relation between
328// oidA and oidB.
329
3301exports.compareOids = function (oidA, oidB) {
3319 var mlen, i;
332
333 // The undefined OID, if there is any, is deemed lesser.
3349 if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') {
3351 return 1;
3368 } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') {
3371 return -1;
338 }
339
340 // Check each number part of the OIDs individually, and if there is any
341 // position where one OID is larger than the other, return accordingly.
342 // This will only check up to the minimum length of both OIDs.
3437 mlen = Math.min(oidA.length, oidB.length);
3447 for (i = 0; i < mlen; i++) {
34526 if (oidA[i] > oidB[i]) {
3461 return -1;
34725 } else if (oidB[i] > oidA[i]) {
3481 return 1;
349 }
350 }
351
352 // If there is one OID that is longer than the other after the above comparison,
353 // consider the shorter OID to be lesser.
3545 if (oidA.length > oidB.length) {
3552 return -1;
3563 } else if (oidB.length > oidA.length) {
3572 return 1;
358 } else {
359 // The OIDs are obviously equal.
3601 return 0;
361 }
362};
363
364
365// Communication functions
366// -----
367
368// This is called for when we receive a message.
369
370function msgReceived(msg, rinfo) {
37144 var self = this, now = Date.now(), pkt, entry;
372
37344 if (msg.length === 0) {
374 // Not sure why we sometimes receive an empty message.
375 // As far as I'm concerned it shouldn't happen, but we'll ignore it
376 // and if it's necessary a retransmission of the request will be
377 // made later.
3780 return;
379 }
380
381 // Parse the packet, or call the informative
382 // parse error display if we fail.
38344 try {
38444 pkt = parse(msg);
385 } catch (error) {
3861 return self.parseError(error, msg);
387 }
388
389 // If this message's request id matches one we've sent,
390 // cancel any outstanding timeout and call the registered
391 // callback.
39243 entry = self.reqs[pkt.pdu.reqid];
39343 if (entry) {
39440 clearRequest(self.reqs, pkt.pdu.reqid);
395
39640 if (typeof entry.callback === 'function') {
39739 pkt.pdu.varbinds.forEach(function (vb) {
39865 vb.receiveStamp = now;
39965 vb.sendStamp = entry.sendStamp;
400 });
401
40239 entry.callback(null, pkt.pdu.varbinds);
403 }
404 } else {
405 // This happens if we receive the response to a message we've already timed out
406 // and removed the request entry for. Maybe we shouldn't even log the warning.
407
408 // Calculate the approximate send time and how old the packet is.
4093 var age = (Date.now() & 0x1fffff) - (pkt.pdu.reqid >>> 10);
4103 if (age < 0) {
4110 age += 0x200000;
412 }
4133 console.warn('Response with unknown request ID from ' + rinfo.address + '. Consider increasing timeouts (' + age + ' ms old?).');
414 }
415}
416
417// Default options for new sessions and operations.
4181exports.defaultOptions = {
419 host: 'localhost',
420 port: 161,
421 community: 'public',
422 family: 'udp4',
423 timeouts: [ 5000, 5000, 5000, 5000 ]
424};
425
426// This creates a new SNMP session.
427
428function Session(options) {
42947 var self = this;
430
43147 self.options = options || {};
43247 defaults(self.options, exports.defaultOptions);
433
43447 self.reqs = {};
43547 self.socket = dgram.createSocket(self.options.family);
43647 self.socket.on('message', msgReceived.bind(self));
43747 self.socket.on('close', function () {
438 // Remove the socket so we don't try to send a message on
439 // it when it's closed.
4400 self.socket = undefined;
441 });
44247 self.socket.on('error', function () {
443 // Errors will be emitted here as well as on the callback to the send function.
444 // We handle them there, so doing anything here is unnecessary.
445 // But having no error handler trips up the test suite.
446 });
447}
448
449// We inherit from EventEmitter so that we can emit error events
450// on fatal errors.
4511Session.prototype = Object.create(events.EventEmitter.prototype);
4521exports.Session = Session;
453
454// Generate a request ID. It's best kept within a signed 32 bit integer.
455// Uses the current time in ms, shifted left ten bits, plus a counter.
456// This gives us space for 1 transmit every microsecond and wraps every
457// ~1000 seconds. This is OK since we only need to keep unique ID:s for in
458// flight packets and they should be safely timed out by then.
459
4601Session.prototype.requestId = function () {
46145 var self = this, now = Date.now();
462
46345 if (!self.prevTs) {
46435 self.prevTs = now;
46535 self.counter = 0;
466 }
467
46845 if (now === self.prevTs) {
46935 self.counter += 1;
47035 if (self.counter > 1023) {
4710 throw new Error('Request ID counter overflow. Adjust algorithm.');
472 }
473 } else {
47410 self.prevTs = now;
47510 self.counter = 0;
476 }
477
47845 return ((now & 0x1fffff) << 10) + self.counter;
479};
480
481// Display useful debugging information when a parse error occurs.
482
4831Session.prototype.parseError = function (error, buffer) {
4841 var self = this, hex;
485
486 // Display a friendly introductory text.
4871 console.error('Woops! An error occurred while parsing an SNMP message. :(');
4881 console.error('To have this problem corrected, please report the information below verbatim');
4891 console.error('via email to snmp@nym.se or by creating a GitHub issue at');
4901 console.error('https://github.com/calmh/node-snmp-native/issues');
4911 console.error('');
4921 console.error('Thanks!');
493
494 // Display the stack backtrace so we know where the exception happened.
4951 console.error('');
4961 console.error(error.stack);
497
498 // Display the buffer data, nicely formatted so we can replicate the problem.
4991 console.error('\nMessage data:');
5001 hex = buffer.toString('hex');
5011 while (hex.length > 0) {
5027 console.error(' ' + hex.slice(0, 32).replace(/([0-9a-f]{2})/g, '$1 '));
5037 hex = hex.slice(32);
504 }
505
506 // Let the exception bubble upwards.
5071 self.emit('error', error);
508};
509
510// Send a message. Can be used after manually constructing a correct Packet structure.
511
5121Session.prototype.sendMsg = function (pkt, options, callback) {
51345 var self = this, buf, reqid, retrans = 0;
514
51545 defaults(options, self.options);
516
51745 reqid = self.requestId();
51845 pkt.pdu.reqid = reqid;
519
52045 buf = encode(pkt);
521
522 function transmit() {
52347 if (!self.socket || !self.reqs[reqid]) {
524 // The socket has already been closed, perhaps due to an error that ocurred while a timeout
525 // was scheduled. We can't do anything about it now.
5260 clearRequest(self.reqs, reqid);
5270 return;
528 }
529
530 // Send the message.
53147 self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) {
53247 var entry = self.reqs[reqid];
533
53447 if (err) {
5351 clearRequest(self.reqs, reqid);
5361 return callback(err);
53746 } else if (entry) {
53846 entry.sendStamp = Date.now();
539
54046 if (options.timeouts[retrans]) {
541 // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply.
54244 entry.timeout = setTimeout(transmit, options.timeouts[retrans]);
54344 retrans += 1;
544 } else {
5452 clearRequest(self.reqs, reqid);
5462 return callback(new Error('Timeout'));
547 }
548 }
549 });
550 }
551
552 // Register the callback to call when we receive a reply.
55344 self.reqs[reqid] = { callback: callback };
554 // Transmit the message.
55544 transmit();
556};
557
558// Shortcut to create a GetRequest and send it, while registering a callback.
559// Needs `options.oid` to be an OID in array form.
560
5611Session.prototype.get = function (options, callback) {
56224 var self = this, pkt;
563
56424 defaults(options, self.options);
56524 parseOids(options);
566
56723 if (!options.oid) {
5681 return callback(null, []);
569 }
570
57122 pkt = new Packet();
57222 pkt.community = options.community;
57322 pkt.pdu.varbinds[0].oid = options.oid;
57422 self.sendMsg(pkt, options, callback);
575};
576
577// Shortcut to create a SetRequest and send it, while registering a callback.
578// Needs `options.oid` to be an OID in array form, `options.value` to be an
579// integer and `options.type` to be asn1ber.T.Integer (2).
580
5811Session.prototype.set = function (options, callback) {
58210 var self = this, pkt;
583
58410 defaults(options, self.options);
58510 parseOids(options);
586
5879 if (!options.oid) {
5881 throw new Error('Missing required option `oid`.');
5898 } else if (options.value === undefined) {
5901 throw new Error('Missing required option `value`.');
5917 } else if (!options.type) {
5921 throw new Error('Missing required option `type`.');
593 }
594
5956 pkt = new Packet();
5966 pkt.community = options.community;
5976 pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU;
5986 pkt.pdu.varbinds[0].oid = options.oid;
5996 pkt.pdu.varbinds[0].type = options.type;
6006 pkt.pdu.varbinds[0].value = options.value;
6016 self.sendMsg(pkt, options, callback);
602};
603
604// Shortcut to get all OIDs in the `options.oids` array sequentially. The
605// callback is called when the entire operation is completed. If
606// options.abortOnError is truish, an error while getting any of the values
607// will cause the callback to be called with error status. When
608// `options.abortOnError` is falsish (the default), any errors will be ignored
609// and any successfully retrieved values sent to the callback.
610
6111Session.prototype.getAll = function (options, callback) {
6125 var self = this, results = [];
613
6145 defaults(options, self.options, { abortOnError: false });
6155 parseOids(options);
616
6175 if (!options.oids || options.oids.length === 0) {
6182 return callback(null, []);
619 }
620
621 function getOne(c) {
6224 var oid, pkt, m, vb;
623
6244 pkt = new Packet();
6254 pkt.community = options.community;
6264 pkt.pdu.varbinds = [];
627
628 // Push up to 16 varbinds in the same message.
629 // The number 16 isn't really that magical, it's just a nice round
630 // number that usually seems to fit withing a single packet and gets
631 // accepted by the switches I've tested it on.
6324 for (m = 0; m < 16 && c < options.oids.length; m++) {
63330 vb = new VarBind();
63430 vb.oid = options.oids[c];
63530 pkt.pdu.varbinds.push(vb);
63630 c++;
637 }
638
6394 self.sendMsg(pkt, options, function (err, varbinds) {
6404 if (options.abortOnError && err) {
6410 callback(err);
642 } else {
6434 if (varbinds) {
6444 results = results.concat(varbinds);
645 }
6464 if (c < options.oids.length) {
6471 getOne(c);
648 } else {
6493 callback(null, results);
650 }
651 }
652 });
653 }
654
6553 getOne(0);
656};
657
658// Shortcut to create a GetNextRequest and send it, while registering a callback.
659// Needs `options.oid` to be an OID in array form.
660
6611Session.prototype.getNext = function (options, callback) {
66216 var self = this, pkt;
663
66416 defaults(options, self.options);
66516 parseOids(options);
666
66714 if (!options.oid) {
6681 return callback(null, []);
669 }
670
67113 pkt = new Packet();
67213 pkt.community = options.community;
67313 pkt.pdu.type = 1;
67413 pkt.pdu.varbinds[0].oid = options.oid;
67513 self.sendMsg(pkt, options, callback);
676};
677
678// Shortcut to get all entries below the specified OID.
679// The callback will be called once with the list of
680// varbinds that was collected, or with an error object.
681// Needs `options.oid` to be an OID in array form.
682
6831Session.prototype.getSubtree = function (options, callback) {
6843 var self = this, vbs = [];
685
6863 defaults(options, self.options);
6873 parseOids(options);
688
6893 if (!options.oid) {
6901 return callback(null, []);
691 }
692
6932 options.startOid = options.oid;
694
695 // Helper to check whether `oid` in inside the tree rooted at
696 // `root` or not.
697 function inTree(root, oid) {
69811 var i;
69911 if (oid.length <= root.length) {
7001 return false;
701 }
70210 for (i = 0; i < root.length; i++) {
70379 if (oid[i] !== root[i]) {
7041 return false;
705 }
706 }
7079 return true;
708 }
709
710 // Helper to handle the result of getNext and call the user's callback
711 // as appropriate. The callback will see one of the following patterns:
712 // - callback([an Error object], undefined) -- an error ocurred.
713 // - callback(null, [a Packet object]) -- data from under the tree.
714 // - callback(null, null) -- end of tree.
715 function result(error, varbinds) {
71611 if (error) {
7170 callback(error);
718 } else {
71911 if (inTree(options.startOid, varbinds[0].oid)) {
7209 if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') {
7210 callback(null, vbs);
722 } else {
7239 vbs.push(varbinds[0]);
7249 var next = { oid: varbinds[0].oid };
7259 defaults(next, options);
7269 self.getNext(next, result);
727 }
728 } else {
7292 callback(null, vbs);
730 }
731 }
732 }
733
7342 self.getNext(options, result);
735};
736
737// Close the socket. Necessary to finish the event loop and exit the program.
738
7391Session.prototype.close = function () {
7400 this.socket.close();
741};
742
================================================ FILE: docs/docco.css ================================================ /*--------------------- Typography ----------------------------*/ @font-face { font-family: 'aller-light'; src: url('public/fonts/aller-light.eot'); src: url('public/fonts/aller-light.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-light.woff') format('woff'), url('public/fonts/aller-light.ttf') format('truetype'); font-weight: normal; font-style: normal; } @font-face { font-family: 'aller-bold'; src: url('public/fonts/aller-bold.eot'); src: url('public/fonts/aller-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/aller-bold.woff') format('woff'), url('public/fonts/aller-bold.ttf') format('truetype'); font-weight: normal; font-style: normal; } @font-face { font-family: 'novecento-bold'; src: url('public/fonts/novecento-bold.eot'); src: url('public/fonts/novecento-bold.eot?#iefix') format('embedded-opentype'), url('public/fonts/novecento-bold.woff') format('woff'), url('public/fonts/novecento-bold.ttf') format('truetype'); font-weight: normal; font-style: normal; } /*--------------------- Layout ----------------------------*/ html { height: 100%; } body { font-family: "aller-light"; font-size: 14px; line-height: 18px; color: #30404f; margin: 0; padding: 0; height:100%; } #container { min-height: 100%; } a { color: #000; } b, strong { font-weight: normal; font-family: "aller-bold"; } p, ul, ol { margin: 15px 0 0px; } h1, h2, h3, h4, h5, h6 { color: #112233; line-height: 1em; font-weight: normal; font-family: "novecento-bold"; text-transform: uppercase; margin: 30px 0 15px 0; } h1 { margin-top: 40px; } hr { border: 0; background: 1px solid #ddd; height: 1px; margin: 20px 0; } pre, tt, code { font-size: 12px; line-height: 16px; font-family: Menlo, Monaco, Consolas, "Lucida Console", monospace; margin: 0; padding: 0; } .annotation pre { display: block; margin: 0; padding: 7px 10px; background: #fcfcfc; -moz-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); -webkit-box-shadow: inset 0 0 10px rgba(0,0,0,0.1); box-shadow: inset 0 0 10px rgba(0,0,0,0.1); overflow-x: auto; } .annotation pre code { border: 0; padding: 0; background: transparent; } blockquote { border-left: 5px solid #ccc; margin: 0; padding: 1px 0 1px 1em; } .sections blockquote p { font-family: Menlo, Consolas, Monaco, monospace; font-size: 12px; line-height: 16px; color: #999; margin: 10px 0 0; white-space: pre-wrap; } ul.sections { list-style: none; padding:0 0 5px 0;; margin:0; } /* Force border-box so that % widths fit the parent container without overlap because of margin/padding. More Info : http://www.quirksmode.org/css/box.html */ ul.sections > li > div { -moz-box-sizing: border-box; /* firefox */ -ms-box-sizing: border-box; /* ie */ -webkit-box-sizing: border-box; /* webkit */ -khtml-box-sizing: border-box; /* konqueror */ box-sizing: border-box; /* css3 */ } /*---------------------- Jump Page -----------------------------*/ #jump_to, #jump_page { margin: 0; background: white; -webkit-box-shadow: 0 0 25px #777; -moz-box-shadow: 0 0 25px #777; -webkit-border-bottom-left-radius: 5px; -moz-border-radius-bottomleft: 5px; font: 16px Arial; cursor: pointer; text-align: right; list-style: none; } #jump_to a { text-decoration: none; } #jump_to a.large { display: none; } #jump_to a.small { font-size: 22px; font-weight: bold; color: #676767; } #jump_to, #jump_wrapper { position: fixed; right: 0; top: 0; padding: 10px 15px; margin:0; } #jump_wrapper { display: none; padding:0; } #jump_to:hover #jump_wrapper { display: block; } #jump_page { padding: 5px 0 3px; margin: 0 0 25px 25px; } #jump_page .source { display: block; padding: 15px; text-decoration: none; border-top: 1px solid #eee; } #jump_page .source:hover { background: #f5f5ff; } #jump_page .source:first-child { } /*---------------------- Low resolutions (> 320px) ---------------------*/ @media only screen and (min-width: 320px) { .pilwrap { display: none; } ul.sections > li > div { display: block; padding:5px 10px 0 10px; } ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { padding-left: 30px; } ul.sections > li > div.content { background: #f5f5ff; overflow-x:auto; -webkit-box-shadow: inset 0 0 5px #e5e5ee; box-shadow: inset 0 0 5px #e5e5ee; border: 1px solid #dedede; margin:5px 10px 5px 10px; padding-bottom: 5px; } ul.sections > li > div.annotation pre { margin: 7px 0 7px; padding-left: 15px; } ul.sections > li > div.annotation p tt, .annotation code { background: #f8f8ff; border: 1px solid #dedede; font-size: 12px; padding: 0 0.2em; } } /*---------------------- (> 481px) ---------------------*/ @media only screen and (min-width: 481px) { #container { position: relative; } body { background-color: #F5F5FF; font-size: 15px; line-height: 21px; } pre, tt, code { line-height: 18px; } p, ul, ol { margin: 0 0 15px; } #jump_to { padding: 5px 10px; } #jump_wrapper { padding: 0; } #jump_to, #jump_page { font: 10px Arial; text-transform: uppercase; } #jump_page .source { padding: 5px 10px; } #jump_to a.large { display: inline-block; } #jump_to a.small { display: none; } #background { position: absolute; top: 0; bottom: 0; width: 350px; background: #fff; border-right: 1px solid #e5e5ee; z-index: -1; } ul.sections > li > div.annotation ul, ul.sections > li > div.annotation ol { padding-left: 40px; } ul.sections > li { white-space: nowrap; } ul.sections > li > div { display: inline-block; } ul.sections > li > div.annotation { max-width: 350px; min-width: 350px; min-height: 5px; padding: 13px; overflow-x: hidden; white-space: normal; vertical-align: top; text-align: left; } ul.sections > li > div.annotation pre { margin: 15px 0 15px; padding-left: 15px; } ul.sections > li > div.content { padding: 13px; vertical-align: top; background: #f5f5ff; border: none; -webkit-box-shadow: none; box-shadow: none; } .pilwrap { position: relative; display: inline; } .pilcrow { font: 12px Arial; text-decoration: none; color: #454545; position: absolute; top: 3px; left: -20px; padding: 1px 2px; opacity: 0; -webkit-transition: opacity 0.2s linear; } .for-h1 .pilcrow { top: 47px; } .for-h2 .pilcrow, .for-h3 .pilcrow, .for-h4 .pilcrow { top: 35px; } ul.sections > li > div.annotation:hover .pilcrow { opacity: 1; } } /*---------------------- (> 1025px) ---------------------*/ @media only screen and (min-width: 1025px) { body { font-size: 16px; line-height: 24px; } #background { width: 525px; } ul.sections > li > div.annotation { max-width: 525px; min-width: 525px; padding: 10px 25px 1px 50px; } ul.sections > li > div.content { padding: 9px 15px 16px 25px; } } /*---------------------- Syntax Highlighting -----------------------------*/ td.linenos { background-color: #f0f0f0; padding-right: 10px; } span.lineno { background-color: #f0f0f0; padding: 0 5px 0 5px; } /* github.com style (c) Vasily Polovnyov */ pre code { display: block; padding: 0.5em; color: #000; background: #f8f8ff } pre .comment, pre .template_comment, pre .diff .header, pre .javadoc { color: #408080; font-style: italic } pre .keyword, pre .assignment, pre .literal, pre .css .rule .keyword, pre .winutils, pre .javascript .title, pre .lisp .title, pre .subst { color: #954121; /*font-weight: bold*/ } pre .number, pre .hexcolor { color: #40a070 } pre .string, pre .tag .value, pre .phpdoc, pre .tex .formula { color: #219161; } pre .title, pre .id { color: #19469D; } pre .params { color: #00F; } pre .javascript .title, pre .lisp .title, pre .subst { font-weight: normal } pre .class .title, pre .haskell .label, pre .tex .command { color: #458; font-weight: bold } pre .tag, pre .tag .title, pre .rules .property, pre .django .tag .keyword { color: #000080; font-weight: normal } pre .attribute, pre .variable, pre .instancevar, pre .lisp .body { color: #008080 } pre .regexp { color: #B68 } pre .class { color: #458; font-weight: bold } pre .symbol, pre .ruby .symbol .string, pre .ruby .symbol .keyword, pre .ruby .symbol .keymethods, pre .lisp .keyword, pre .tex .special, pre .input_number { color: #990073 } pre .builtin, pre .constructor, pre .built_in, pre .lisp .title { color: #0086b3 } pre .preprocessor, pre .pi, pre .doctype, pre .shebang, pre .cdata { color: #999; font-weight: bold } pre .deletion { background: #fdd } pre .addition { background: #dfd } pre .diff .change { background: #0086b3 } pre .chunk { color: #aaa } pre .tex .formula { opacity: 0.5; } ================================================ FILE: docs/example.html ================================================ example.js
================================================ FILE: docs/public/stylesheets/normalize.css ================================================ /*! normalize.css v2.0.1 | MIT License | git.io/normalize */ /* ========================================================================== HTML5 display definitions ========================================================================== */ /* * Corrects `block` display not defined in IE 8/9. */ article, aside, details, figcaption, figure, footer, header, hgroup, nav, section, summary { display: block; } /* * Corrects `inline-block` display not defined in IE 8/9. */ audio, canvas, video { display: inline-block; } /* * Prevents modern browsers from displaying `audio` without controls. * Remove excess height in iOS 5 devices. */ audio:not([controls]) { display: none; height: 0; } /* * Addresses styling for `hidden` attribute not present in IE 8/9. */ [hidden] { display: none; } /* ========================================================================== Base ========================================================================== */ /* * 1. Sets default font family to sans-serif. * 2. Prevents iOS text size adjust after orientation change, without disabling * user zoom. */ html { font-family: sans-serif; /* 1 */ -webkit-text-size-adjust: 100%; /* 2 */ -ms-text-size-adjust: 100%; /* 2 */ } /* * Removes default margin. */ body { margin: 0; } /* ========================================================================== Links ========================================================================== */ /* * Addresses `outline` inconsistency between Chrome and other browsers. */ a:focus { outline: thin dotted; } /* * Improves readability when focused and also mouse hovered in all browsers. */ a:active, a:hover { outline: 0; } /* ========================================================================== Typography ========================================================================== */ /* * Addresses `h1` font sizes within `section` and `article` in Firefox 4+, * Safari 5, and Chrome. */ h1 { font-size: 2em; } /* * Addresses styling not present in IE 8/9, Safari 5, and Chrome. */ abbr[title] { border-bottom: 1px dotted; } /* * Addresses style set to `bolder` in Firefox 4+, Safari 5, and Chrome. */ b, strong { font-weight: bold; } /* * Addresses styling not present in Safari 5 and Chrome. */ dfn { font-style: italic; } /* * Addresses styling not present in IE 8/9. */ mark { background: #ff0; color: #000; } /* * Corrects font family set oddly in Safari 5 and Chrome. */ code, kbd, pre, samp { font-family: monospace, serif; font-size: 1em; } /* * Improves readability of pre-formatted text in all browsers. */ pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } /* * Sets consistent quote types. */ q { quotes: "\201C" "\201D" "\2018" "\2019"; } /* * Addresses inconsistent and variable font size in all browsers. */ small { font-size: 80%; } /* * Prevents `sub` and `sup` affecting `line-height` in all browsers. */ sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } sup { top: -0.5em; } sub { bottom: -0.25em; } /* ========================================================================== Embedded content ========================================================================== */ /* * Removes border when inside `a` element in IE 8/9. */ img { border: 0; } /* * Corrects overflow displayed oddly in IE 9. */ svg:not(:root) { overflow: hidden; } /* ========================================================================== Figures ========================================================================== */ /* * Addresses margin not present in IE 8/9 and Safari 5. */ figure { margin: 0; } /* ========================================================================== Forms ========================================================================== */ /* * Define consistent border, margin, and padding. */ fieldset { border: 1px solid #c0c0c0; margin: 0 2px; padding: 0.35em 0.625em 0.75em; } /* * 1. Corrects color not being inherited in IE 8/9. * 2. Remove padding so people aren't caught out if they zero out fieldsets. */ legend { border: 0; /* 1 */ padding: 0; /* 2 */ } /* * 1. Corrects font family not being inherited in all browsers. * 2. Corrects font size not being inherited in all browsers. * 3. Addresses margins set differently in Firefox 4+, Safari 5, and Chrome */ button, input, select, textarea { font-family: inherit; /* 1 */ font-size: 100%; /* 2 */ margin: 0; /* 3 */ } /* * Addresses Firefox 4+ setting `line-height` on `input` using `!important` in * the UA stylesheet. */ button, input { line-height: normal; } /* * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` * and `video` controls. * 2. Corrects inability to style clickable `input` types in iOS. * 3. Improves usability and consistency of cursor style between image-type * `input` and others. */ button, html input[type="button"], /* 1 */ input[type="reset"], input[type="submit"] { -webkit-appearance: button; /* 2 */ cursor: pointer; /* 3 */ } /* * Re-set default cursor for disabled elements. */ button[disabled], input[disabled] { cursor: default; } /* * 1. Addresses box sizing set to `content-box` in IE 8/9. * 2. Removes excess padding in IE 8/9. */ input[type="checkbox"], input[type="radio"] { box-sizing: border-box; /* 1 */ padding: 0; /* 2 */ } /* * 1. Addresses `appearance` set to `searchfield` in Safari 5 and Chrome. * 2. Addresses `box-sizing` set to `border-box` in Safari 5 and Chrome * (include `-moz` to future-proof). */ input[type="search"] { -webkit-appearance: textfield; /* 1 */ -moz-box-sizing: content-box; -webkit-box-sizing: content-box; /* 2 */ box-sizing: content-box; } /* * Removes inner padding and search cancel button in Safari 5 and Chrome * on OS X. */ input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; } /* * Removes inner padding and border in Firefox 4+. */ button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } /* * 1. Removes default vertical scrollbar in IE 8/9. * 2. Improves readability and alignment in all browsers. */ textarea { overflow: auto; /* 1 */ vertical-align: top; /* 2 */ } /* ========================================================================== Tables ========================================================================== */ /* * Remove most spacing between table cells. */ table { border-collapse: collapse; border-spacing: 0; } ================================================ FILE: docs/snmp.html ================================================ snmp.js
================================================ FILE: example.js ================================================ // Example code for node-snmp-native. // ---- // This file contains examples of how to use the library. // Basic setup // ----- // The snmp object is the main entry point to the library. var snmp = require('snmp-native'); var util = require('util'); var host = 'localhost'; var community = 'public'; // A session is required to communicate with an agent. var session = new snmp.Session({ host: host, community: community }); // All OIDs are represented as integer arrays. // There is no interpretation of string or MIB names. // This here is the OID for sysDescr.0. var oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]; // Getting a single value // ----- // This is how you issue a simple get request. session.get({ oid: oid }, function (err, varbinds) { var vb; if (err) { // If there is an error, such as an SNMP timeout, we'll end up here. console.log(err); } else { vb = varbinds[0]; console.log('The system description is "' + vb.value + '"'); } // The session must be closed when you're done with it. session.close(); }); // Parsing an OID string and getting an entire tree // ----- // Here we convert an OID from string representation to array. // This is the base OID for the ifName tree. var oidStr = '.1.3.6.1.2.1.31.1.1.1.1'; oid = oidStr .split('.') .filter(function (s) { return s.length > 0; }) .map(function (s) { return parseInt(s, 10); }); // You can also get an entire subtree (an SNMP walk). var session2 = new snmp.Session({ host: host, community: community }); session2.getSubtree({ oid: oid }, function (err, varbinds) { if (err) { // If there is an error, such as an SNMP timeout, we'll end up here. console.log(err); } else { // This is the list of varbinds. varbinds.forEach(function (vb) { console.log('Name of interface ' + vb.oid[vb.oid.length - 1] + ' is "' + vb.value + '"'); }); } session2.close(); }); // You can get all of a collection of OIDs in one go. // The semantics is similar to getSubtree. var session3 = new snmp.Session({ host: host, community: community }); var oids = [[1, 3, 6, 1, 2, 1, 1, 1, 0], [1, 3, 6, 1, 2, 1, 1, 2, 0]]; session3.getAll({ oids: oids }, function (err, varbinds) { varbinds.forEach(function (vb) { console.log(vb.oid + ' = ' + vb.value); }); session3.close(); }); // You can also create a destination-less "session" to use on multiple // hosts. This is useful for conserving file descriptors when talking // to a large number of hosts. This example scans the 192.168.1.0/24 // network for SNMP responders. var session4 = new snmp.Session({ community: community }); // New session without host parameter. We set community to avoid repeating it later. var oid = [1, 3, 6, 1, 2, 1, 1, 1, 0]; // sysDescr.0 var cnt = 254; // Expected number of callbacks. for (var i = 1; i < 255; i++) { /*jshint loopfunc:true */ // We need a function to get a closure over i. (function (host) { session4.get({ oid: oid, host: host }, function (err, vbs) { if (err) { // Probably a Timeout. console.log('Error for ' + host + ': ' + err); } else { // Print the returned value (sysDescr). var vb = vbs[0]; console.log(host + ': ' + vb.oid + ' = ' + vb.value); } if (--cnt === 0) { // All requests have returned, time to close the session and exit. session4.close(); } }); }('192.168.1.' + i)); } // Example output // ----- // The expected output is something along these lines. // Note that the asynchronicity results in the responses // being printed in a different order that what you might // guess from the above code. /* 1,3,6,1,2,1,1,1,0 = Solaris anto.nym.se 11.0 physical 1,3,6,1,2,1,1,2,0 = 1,3,6,1,4,1,8072,3,2,3 The system description is "Solaris anto.nym.se 11.0 physical" Name of interface 1 is "lo0" Name of interface 2 is "e1000g0" Name of interface 3 is "vboxnet0" Name of interface 4 is "e1000g1" Name of interface 5 is "he0" Name of interface 6 is "nym0" <... lots of timeouts for the scan stuff ...> */ ================================================ FILE: lib/asn1ber.js ================================================ // This file implements a minimal subset of Abstract Syntax Notation One (**ASN.1**) // Basic Encoding Rules (**BER**), namely the parts that are necessary for sending // and receiving SNMPv2c messages. // // (c) 2012 Jakob Borg, Nym Networks "use strict"; // We define constants for the commonly used ASN.1 types in SNMP. var T = { Integer: 0x02, OctetString: 0x04, Null: 0x05, ObjectIdentifier: 0x06, Sequence: 0x30, IpAddress: 0x40, Counter: 0x41, Gauge: 0x42, TimeTicks: 0x43, Opaque: 0x44, NsapAddress: 0x45, Counter64: 0x46, NoSuchObject: 0x80, NoSuchInstance: 0x81, EndOfMibView: 0x82, PDUBase: 0xA0 }; var P = { GetRequestPDU: 0x00, GetNextRequestPDU: 0x01, GetResponsePDU: 0x02, SetRequestPDU: 0x03 }; var E = { NoError: 0, TooBig: 1, NoSuchName: 2, BadValue: 3, ReadOnly: 4, GenErr: 5, NoAccess: 6, WrongType: 7, WrongLength: 8, WrongEncoding: 9, WrongValue: 10, NoCreation: 11, InconsistentValue: 12, ResourceUnavailable: 13, CommitFailed: 14, UndoFailed: 15, AuthorizationError: 16, NotWritable: 17, InconsistentName: 18 }; var LOG256 = Math.log(256); // The types are also available for consumers of the library. exports.types = T; exports.pduTypes = P; exports.errors = E; exports.unittest = {}; // Private helper functions // ----- // Encode a length as it should be encoded. function lengthArray(len) { var arr = []; if (len <= 127) { // Return a single byte if the value is 127 or less. return [ len ]; } else { // Otherwise encode it as a MSB base-256 integer. while (len > 0) { arr.push(len % 256); len = parseInt(len / 256, 10); } // Add a length byte in front and set the high bit to indicate // that this is a longer value than one byte. arr.push(128 + arr.length); arr.reverse(); return arr; } } exports.unittest.lengthArray = lengthArray; // Return a wrapped copy of the passed `contents`, with the specified wrapper type. // This is used for Sequence and other constructed types. function wrapper(type, contents) { var buf, len, i; // Get the encoded length of the contents len = lengthArray(contents.length); // Set up a buffer with the type and length bytes plus a straight copy of the content. buf = new Buffer(1 + contents.length + len.length); buf[0] = type; for (i = 1; i < len.length + 1; i++) { buf[i] = len[i - 1]; } contents.copy(buf, len.length + 1, 0); return buf; } // Get the encoded representation of a number in an OID. // If the number is less than 128, pass it as it is. // Otherwise return an array of base-128 digits, most significant first, // with the high bit set on all octets except the last one. // This encoding should be used for all number in an OID except the first // two (.1.3) which are handled specially. function oidInt(val) { var bytes = []; bytes.push(val % 128); val = parseInt(val / 128, 10); while (val > 127) { bytes.push(128 + val % 128); val = parseInt(val / 128, 10); } bytes.push(val + 128); return bytes.reverse(); } // Encode an OID. The first two number are encoded specially // in the first octet, then the rest are encoded as one octet per number // unless the number exceeds 127. If so, it's encoded as several base-127 // octets with the high bit set to indicate continuation. function oidArray(oid) { var bytes, i, val; // Enforce some minimum requirements on the OID. if (oid.length < 2) { throw new Error("Minimum OID length is two."); } else if (oid[0] > 2) { throw new Error("Invalid OID"); } else if (oid[0] == 0 && oid[1] > 39) { throw new Error("Invalid OID"); } else if (oid[0] == 1 && oid[1] > 39) { throw new Error("Invalid OID"); } else if (oid[0] == 2 && oid[1] > 79) { throw new Error("Invalid OID"); } // Calculate the first byte of the encoded OID according to the 'special' rule. bytes = [ 40 * oid[0] + oid[1] ]; // For the rest of the OID, encode each number individually and add the // resulting bytes to the buffer. for (i = 2; i < oid.length; i++) { val = oid[i]; if (val > 127) { bytes = bytes.concat(oidInt(val)); } else { bytes.push(val); } } return bytes; } // Divide an integer into base-256 bytes. // Most significant byte first. function intArray(val) { var array = [], encVal = val, bytes; if (val === 0) { array.push(0); } else { if (val < 0) { bytes = Math.floor(1 + Math.log(-val) / LOG256); // Encode negatives as 32-bit two's complement. Let's hope that fits. encVal += Math.pow(2, 8 * bytes); } while (encVal > 0) { array.push(encVal % 256); encVal = parseInt(encVal / 256, 10); } } // Do not produce integers that look negative (high bit // of first byte set). if (val > 0 && array[array.length - 1] >= 0x80) { array.push(0); } return array.reverse(); } // Functions to encode ASN.1 from native objects // ----- // Encode a simple integer. // Integers are encoded as a simple base-256 byte sequence, most significant byte first, // prefixed with a length byte. In principle we support arbitrary integer sizes, in practice // Javascript doesn't even **have** integers so some precision might get lost. function encodeIntegerish(val, type) { var i, arr, buf; // Get the bytes that we're going to encode. arr = intArray(val); // Now that we know the length, we allocate a buffer of the required size. // We set the type and length bytes appropriately. buf = new Buffer(2 + arr.length); buf[0] = type; buf[1] = arr.length; // Copy the bytes into the array. for (i = 0; i < arr.length; i++) { buf[i + 2] = arr[i]; } return buf; } // Integer type, 0x02 exports.encodeInteger = function (val) { return(encodeIntegerish(val, T.Integer)); }; // Gauge type, 0x42 exports.encodeGauge = function (val) { return(encodeIntegerish(val, T.Gauge)); }; // Counter type, 0x41 exports.encodeCounter = function (val) { return(encodeIntegerish(val, T.Counter)); }; // TimeTicks type, 0x43 exports.encodeTimeTicks = function (val) { return(encodeIntegerish(val, T.TimeTicks)); }; // Create the representation of a Null, `05 00`. exports.encodeNull = function () { var buf = new Buffer(2); buf[0] = T.Null; buf[1] = 0; return buf; }; // NoSuchObject type, 0x80 exports.encodeNoSuchObject = function() { var buf = new Buffer(2); buf[0] = T.NoSuchObject; buf[1] = 0; return buf; }; // NoSuchInstance type, 0x81 exports.encodeNoSuchInstance = function() { var buf = new Buffer(2); buf[0] = T.NoSuchInstance; buf[1] = 0; return buf; }; // EndOfMibView type, 0x82 exports.encodeEndOfMibView = function() { var buf = new Buffer(2); buf[0] = T.EndOfMibView; buf[1] = 0; return buf; }; // Encode a Sequence, which is a wrapper of type `30`. exports.encodeSequence = function (contents) { return wrapper(T.Sequence, contents); }; // Encode an OctetString, which is a wrapper of type `04`. exports.encodeOctetString = function (string) { var buf, contents; if (typeof string === 'string') { contents = new Buffer(string); } else if (Buffer.isBuffer(string)) { contents = string; } else { throw new Error('Only Buffer and string types are acceptable as OctetString.'); } return wrapper(T.OctetString, contents); }; // Encode an IpAddress, which is a wrapper of type `40`. exports.encodeIpAddress = function (address) { var contents, octets, value = []; if (typeof address !== 'string' && !Buffer.isBuffer(address)) { throw new Error('Only Buffer and string types are acceptable as OctetString.'); } // assume that the string is in dotted decimal format ipv4 // also, use toString in case a buffer was passed in. octets = address.toString().split('.'); if (octets.length !== 4) { throw new Error('IP Addresses must be specified in dotted decimal format.'); } octets.forEach(function (octet) { var octetValue = parseInt(octet, 10); if (octet < 0 || octet > 255) { throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets)); } value.push(octetValue); }); contents = new Buffer(value); return wrapper(T.IpAddress, contents); }; // Encode an ObjectId. exports.encodeOid = function (oid) { var buf, bytes, i, len; // Get the encoded format of the OID. bytes = oidArray(oid); // Get the encoded format of the length len = lengthArray(bytes.length); // Fill in the buffer with type, length and OID data. buf = new Buffer(1 + bytes.length + len.length); buf[0] = T.ObjectIdentifier; for (i = 1; i < len.length + 1; i++) { buf[i] = len[i - 1]; } for (i = len.length + 1; i < bytes.length + len.length + 1; i++) { buf[i] = bytes[i - len.length - 1]; } return buf; }; // Encode an SNMP request with specified `contents`. // The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`. exports.encodeRequest = function (type, contents) { return wrapper(T.PDUBase + type, contents); }; // Functions to parse ASN.1 to native objects // ----- // Parse and return type, data length and header length. function typeAndLength(buf) { var res, len, i; res = { type: buf[0], len: 0, header: 1 }; if (buf[1] < 128) { // If bit 8 is zero, this byte indicates the content length (up to 127 bytes). res.len = buf[1]; res.header += 1; } else { // If bit 8 is 1, bits 0 to 7 indicate the number of following legth bytes. // These bytes are a simple msb base-256 integer indicating the content length. for (i = 0; i < buf[1] - 128; i++) { res.len *= 256; res.len += buf[i + 2]; } res.header += buf[1] - 128 + 1; } return res; } exports.typeAndLength = typeAndLength; // Parse a buffer containing a representation of an integer. // Verifies the type, then multiplies in each byte as it comes. exports.parseInteger = function (buf) { var i, val, type, len; type = buf[0]; len = buf[1]; if (type !== T.Integer && type !== T.Counter && type !== T.Counter64 && type !== T.Gauge && type !== T.TimeTicks) { throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer'); } val = 0; for (i = 0; i < len; i++) { val *= 256; val += buf[i + 2]; } if (buf[2] > 127 && type === T.Integer) { return val - Math.pow(2, 8 * buf[1]); } else { return val; } }; // Parse a buffer containing a representation of an OctetString. // Verify the type, then just grab the string out of the buffer. exports.parseOctetString = function (buf) { var i, len, lenBytes = 0; if (buf[0] !== T.OctetString) { throw new Error('Buffer does not appear to be an OctetString'); } // SNMP doesn't specify an encoding so I pick UTF-8 as the 'most standard' // encoding. We'll see if that assumption survives contact with actual reality. len = buf[1]; if (len > 128) { // Multi byte length encoding lenBytes = len - 128; len = 0; for (i = 0; i < lenBytes; i++) { len *= 256; len += buf[2+i]; } } return buf.toString('utf-8', 2 + lenBytes, 2 + lenBytes + len); }; // Parse a buffer containing a representation of an ObjectIdentifier. // Verify the type, then apply the relevent encoding rules. exports.parseOid = function (buf) { var oid, val, i, o1, o2; if (buf[0] !== T.ObjectIdentifier) { throw new Error('Buffer does not appear to be an ObjectIdentifier'); } // The first byte contains the first two numbers in the OID. They're // magical! They're compactly encoded in a special way! KILL ME NOW! o1 = parseInt(buf[2] / 40, 10); if (o1 > 2) { o1 = 2; } o2 = buf[2] - 40 * o1; oid = [o1, o2]; // The rest of the data is a base-128-encoded OID for (i = 0; i < buf[1] - 1; i++) { val = 0; while (buf[i + 3] >= 128) { val += buf[i + 3] - 128; val *= 128; i++; } val += buf[i + 3]; oid.push(val); } return oid; }; // Parse a buffer containing a representation of an array type. // This is for example an IpAddress. exports.parseArray = function (buf) { var i, nelem, array; if (buf[0] !== T.IpAddress) { throw new Error('Buffer does not appear to be an array type.'); } nelem = buf[1]; array = []; for (i = 0; i < buf[1]; i++) { array.push(buf[i + 2]); } return array; }; // Parse a buffer containing a representation of an opaque type. // This is for example an IpAddress. exports.parseOpaque = function (buf) { var hdr; hdr = typeAndLength(buf); if (hdr.type !== T.Opaque) { throw new Error('Buffer does not appear to be an opaque type.'); } return '0x' + buf.slice(hdr.header).toString('hex'); }; /*globals exports: false*/ ================================================ FILE: lib/snmp.js ================================================ // Introduction // ----- // This is `node-snmp-native`, a native (Javascript) implementation of an SNMP // client library targeted at Node.js. It's MIT licensed and available at // https://github.com/calmh/node-snmp-native // // (c) 2012 Jakob Borg, Nym Networks "use strict"; // Code // ----- // This file implements a structure representing an SNMP message // and routines for converting to and from the network representation. // Define our external dependencies. var assert = require('assert'); var dgram = require('dgram'); var events = require('events'); // We also need our ASN.1 BER en-/decoding routines. var asn1ber = require('./asn1ber'); exports.PduTypes = asn1ber.pduTypes; exports.DataTypes = asn1ber.types; exports.Errors = asn1ber.errors; var versions = { SNMPv1: 0, SNMPv2c: 1 }; exports.Versions = versions; // Basic structures // ---- // A `VarBind` is the innermost structure, containing an OID-Value pair. function VarBind() { this.type = 5; this.value = null; } // The `PDU` contains the SNMP request or response fields and a list of `VarBinds`. function PDU() { this.type = asn1ber.pduTypes.GetRequestPDU; this.reqid = 1; this.error = 0; this.errorIndex = 0; this.varbinds = [ new VarBind() ]; } // The `Packet` contains the SNMP version and community and the `PDU`. function Packet() { this.version = versions.SNMPv2c; this.community = 'public'; this.pdu = new PDU(); } // Allow consumers to create packet structures from scratch. exports.Packet = Packet; // Private helper functions // ---- // Concatenate several buffers to one. function concatBuffers(buffers) { var total, cur = 0, buf; // First we calculate the total length, total = buffers.reduce(function (tot, b) { return tot + b.length; }, 0); // then we allocate a new Buffer large enough to contain all data, buf = new Buffer(total); buffers.forEach(function (buffer) { // finally we copy the data into the new larger buffer. buffer.copy(buf, cur, 0); cur += buffer.length; }); return buf; } // Clear a pending packet when it times out or is successfully received. function clearRequest(reqs, reqid) { var self = this; var entry = reqs[reqid]; if (entry) { if (entry.timeout) { clearTimeout(entry.timeout); } delete reqs[reqid]; } } // Convert a string formatted OID to an array, leaving anything non-string alone. function parseSingleOid(oid) { if (typeof oid !== 'string') { return oid; } if (oid[0] !== '.') { throw new Error('Invalid OID format'); } oid = oid.split('.') .filter(function (s) { return s.length > 0; }) .map(function (s) { return parseInt(s, 10); }); return oid; } // Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings. function parseOids(options) { if (options.oid) { options.oid = parseSingleOid(options.oid); } if (options.oids) { options.oids = options.oids.map(parseSingleOid); } } // Update targ with attributes from _defs. // Any existing attributes on targ are untouched. function defaults(targ, _defs) { [].slice.call(arguments, 1).forEach(function (def) { Object.keys(def).forEach(function (key) { if (!targ.hasOwnProperty(key)) { targ[key] = def[key]; } }); }); } // Encode structure to ASN.1 BER // ---- // Return an ASN.1 BER encoding of a Packet structure. // This is suitable for transmission on a UDP socket. function encode(pkt) { var version, community, reqid, err, erridx, vbs, pdu, message; // We only support SNMPv1 and SNMPv2c, so enforce those version stamps. if (pkt.version !== versions.SNMPv1 && pkt.version !== versions.SNMPv2c) { throw new Error('Only SNMPv1 and SNMPv2c are supported.'); } // Encode the message header fields. version = asn1ber.encodeInteger(pkt.version); community = asn1ber.encodeOctetString(pkt.community); // Encode the PDU header fields. reqid = asn1ber.encodeInteger(pkt.pdu.reqid); err = asn1ber.encodeInteger(pkt.pdu.error); erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex); // Encode the PDU varbinds. vbs = []; pkt.pdu.varbinds.forEach(function (vb) { var oid = asn1ber.encodeOid(vb.oid), val; if (vb.type === asn1ber.types.Null || vb.value === null) { val = asn1ber.encodeNull(); } else if (vb.type === asn1ber.types.Integer) { val = asn1ber.encodeInteger(vb.value); } else if (vb.type === asn1ber.types.Gauge) { val = asn1ber.encodeGauge(vb.value); } else if (vb.type === asn1ber.types.IpAddress) { val = asn1ber.encodeIpAddress(vb.value); } else if (vb.type === asn1ber.types.OctetString) { val = asn1ber.encodeOctetString(vb.value); } else if (vb.type === asn1ber.types.ObjectIdentifier) { val = asn1ber.encodeOid(vb.value, true); } else if (vb.type === asn1ber.types.Counter) { val = asn1ber.encodeCounter(vb.value); } else if (vb.type === asn1ber.types.TimeTicks) { val = asn1ber.encodeTimeTicks(vb.value); } else if (vb.type === asn1ber.types.NoSuchObject) { val = asn1ber.encodeNoSuchObject(); } else if (vb.type === asn1ber.types.NoSuchInstance) { val = asn1ber.encodeNoSuchInstance(); } else if (vb.type === asn1ber.types.EndOfMibView) { val = asn1ber.encodeEndOfMibView(); } else { throw new Error('Unknown varbind type "' + vb.type + '" in encoding.'); } vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val]))); }); // Concatenate all the varbinds together. vbs = asn1ber.encodeSequence(concatBuffers(vbs)); // Create the PDU by concatenating the inner fields and adding a request structure around it. pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs])); // Create the message by concatenating the header fields and the PDU. message = asn1ber.encodeSequence(concatBuffers([version, community, pdu])); return message; } exports.encode = encode; // Parse ASN.1 BER into a structure // ----- // Parse an SNMP packet into its component fields. // We don't do a lot of validation so a malformed packet will probably just // make us blow up. function parse(buf) { var pkt, oid, bvb, vb, hdr, vbhdr; pkt = new Packet(); // First we have a sequence marker (two bytes). // We don't care about those, so cut them off. hdr = asn1ber.typeAndLength(buf); assert.equal(asn1ber.types.Sequence, hdr.type); buf = buf.slice(hdr.header); // Then comes the version field (integer). Parse it and slice it. pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); buf = buf.slice(2 + buf[1]); // We then get the community. Parse and slice. pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2)); buf = buf.slice(2 + buf[1]); // Here's the PDU structure. We're interested in the type. Slice the rest. hdr = asn1ber.typeAndLength(buf); assert.ok(hdr.type >= 0xA0); pkt.pdu.type = hdr.type - 0xA0; buf = buf.slice(hdr.header); // The request id field. pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); buf = buf.slice(2 + buf[1]); // The error field. pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); buf = buf.slice(2 + buf[1]); // The error index field. pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); buf = buf.slice(2 + buf[1]); // Here's the varbind list. Not interested. hdr = asn1ber.typeAndLength(buf); assert.equal(asn1ber.types.Sequence, hdr.type); buf = buf.slice(hdr.header); // Now comes the varbinds. There might be many, so we loop for as long as we have data. pkt.pdu.varbinds = []; while (buf[0] === asn1ber.types.Sequence) { vb = new VarBind(); // Slice off the sequence header. hdr = asn1ber.typeAndLength(buf); assert.equal(asn1ber.types.Sequence, hdr.type); bvb = buf.slice(hdr.header, hdr.len + hdr.header); // Parse and save the ObjectIdentifier. vb.oid = asn1ber.parseOid(bvb); // Parse the value. We use the type marker to figure out // what kind of value it is and call the appropriate parser // routine. For the SNMPv2c error types, we simply set the // value to a text representation of the error and leave handling // up to the user. var vb_name_hdr = asn1ber.typeAndLength(bvb); bvb = bvb.slice(vb_name_hdr.header + vb_name_hdr.len); var vb_value_hdr = asn1ber.typeAndLength(bvb); vb.type = vb_value_hdr.type; if (vb.type === asn1ber.types.Null) { // Null type. vb.value = null; } else if (vb.type === asn1ber.types.OctetString) { // Octet string type. vb.value = asn1ber.parseOctetString(bvb); } else if (vb.type === asn1ber.types.Integer || vb.type === asn1ber.types.Counter || vb.type === asn1ber.types.Counter64 || vb.type === asn1ber.types.TimeTicks || vb.type === asn1ber.types.Gauge) { // Integer type and it's derivatives that behave in the same manner. vb.value = asn1ber.parseInteger(bvb); } else if (vb.type === asn1ber.types.ObjectIdentifier) { // Object identifier type. vb.value = asn1ber.parseOid(bvb); } else if (vb.type === asn1ber.types.IpAddress) { // IP Address type. vb.value = asn1ber.parseArray(bvb); } else if (vb.type === asn1ber.types.Opaque) { // Opaque type. The 'parsing' here is very light; basically we return a // string representation of the raw bytes in hex. vb.value = asn1ber.parseOpaque(bvb); } else if (vb.type === asn1ber.types.EndOfMibView) { // End of MIB view error, returned when attempting to GetNext beyond the end // of the current view. vb.value = 'endOfMibView'; } else if (vb.type === asn1ber.types.NoSuchObject) { // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist. vb.value = 'noSuchObject'; } else if (vb.type === asn1ber.types.NoSuchInstance) { // No such instance error, returned when attempting to Get/GetNext an instance // that doesn't exist in a given table. vb.value = 'noSuchInstance'; } else { // Something else that we can't handle, so throw an error. // The error will be caught and presented in a useful manner on stderr, // with a dump of the message causing it. throw new Error('Unrecognized value type ' + vb.type); } // Take the raw octet string value and preseve it as a buffer and hex string. vb.valueRaw = bvb.slice(vb_value_hdr.header, vb_value_hdr.header + vb_value_hdr.len); vb.valueHex = vb.valueRaw.toString('hex'); // Add the request id to the varbind (even though it doesn't really belong) // so that it will be availble to the end user. vb.requestId = pkt.pdu.reqid; // Push whatever we parsed to the varbind list. pkt.pdu.varbinds.push(vb); // Go fetch the next varbind, if there seems to be any. if (buf.length > hdr.header + hdr.len) { buf = buf.slice(hdr.header + hdr.len); } else { break; } } return pkt; } exports.parse = parse; // Utility functions // ----- // Compare two OIDs, returning -1, 0 or +1 depending on the relation between // oidA and oidB. function compareOids (oidA, oidB) { var mlen, i; // The undefined OID, if there is any, is deemed lesser. if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') { return 1; } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') { return -1; } // Check each number part of the OIDs individually, and if there is any // position where one OID is larger than the other, return accordingly. // This will only check up to the minimum length of both OIDs. mlen = Math.min(oidA.length, oidB.length); for (i = 0; i < mlen; i++) { if (oidA[i] > oidB[i]) { return -1; } else if (oidB[i] > oidA[i]) { return 1; } } // If there is one OID that is longer than the other after the above comparison, // consider the shorter OID to be lesser. if (oidA.length > oidB.length) { return -1; } else if (oidB.length > oidA.length) { return 1; } else { // The OIDs are obviously equal. return 0; } } exports.compareOids = compareOids; // Communication functions // ----- // This is called for when we receive a message. function msgReceived(msg, rinfo) { var self = this, now = Date.now(), pkt, entry; if (msg.length === 0) { // Not sure why we sometimes receive an empty message. // As far as I'm concerned it shouldn't happen, but we'll ignore it // and if it's necessary a retransmission of the request will be // made later. return; } // Parse the packet, or call the informative // parse error display if we fail. try { pkt = parse(msg); } catch (error) { return self.emit('error', error); } // If this message's request id matches one we've sent, // cancel any outstanding timeout and call the registered // callback. entry = self.reqs[pkt.pdu.reqid]; if (entry) { clearRequest(self.reqs, pkt.pdu.reqid); if (typeof entry.callback === 'function') { if (pkt.pdu.error !== 0) { // An error response should be reported as an error to the callback. // We try to find the error description, or in worst case call it // just "Unknown Error ". var errorDescr = Object.keys(asn1ber.errors).filter(function (key) { return asn1ber.errors[key] === pkt.pdu.error; })[0] || 'Unknown Error ' + pkt.pdu.error; return entry.callback(new Error(errorDescr)); } pkt.pdu.varbinds.forEach(function (vb) { vb.receiveStamp = now; vb.sendStamp = entry.sendStamp; }); entry.callback(null, pkt.pdu.varbinds); } } } // Default options for new sessions and operations. exports.defaultOptions = { host: 'localhost', port: 161, bindPort: 0, community: 'public', family: 'udp4', timeouts: [ 5000, 5000, 5000, 5000 ], version: versions.SNMPv2c }; // This creates a new SNMP session. function Session(options) { var self = this; self.options = options || {}; defaults(self.options, exports.defaultOptions); self.reqs = {}; self.socket = dgram.createSocket(self.options.family); self.socket.on('message', self.options.msgReceived || msgReceived.bind(self)); self.socket.on('close', function () { // Remove the socket so we don't try to send a message on // it when it's closed. self.socket = undefined; }); self.socket.on('error', function () { // Errors will be emitted here as well as on the callback to the send function. // We handle them there, so doing anything here is unnecessary. // But having no error handler trips up the test suite. }); // If exclusive is false (default), then cluster workers will use the same underlying handle, // allowing connection handling duties to be shared. // When exclusive is true, the handle is not shared, and attempted port sharing results in an error. self.socket.bind({ port: self.options.bindPort, // unless otherwise specified, get a random port automatically exclusive: true // you should not share the same port, otherwise yours packages will be screwed up between workers }); } // We inherit from EventEmitter so that we can emit error events // on fatal errors. Session.prototype = Object.create(events.EventEmitter.prototype); exports.Session = Session; // Generate a request ID. It's best kept within a signed 32 bit integer. // Uses the current time in ms, shifted left ten bits, plus a counter. // This gives us space for 1 transmit every microsecond and wraps every // ~1000 seconds. This is OK since we only need to keep unique ID:s for in // flight packets and they should be safely timed out by then. Session.prototype.requestId = function () { var self = this, now = Date.now(); if (!self.prevTs) { self.prevTs = now; self.counter = 0; } if (now === self.prevTs) { self.counter += 1; if (self.counter > 1023) { throw new Error('Request ID counter overflow. Adjust algorithm.'); } } else { self.prevTs = now; self.counter = 0; } return ((now & 0x1fffff) << 10) + self.counter; }; // Send a message. Can be used after manually constructing a correct Packet structure. Session.prototype.sendMsg = function (pkt, options, callback) { var self = this, buf, reqid, retrans = 0; defaults(options, self.options); reqid = self.requestId(); pkt.pdu.reqid = reqid; buf = encode(pkt); function transmit() { if (!self.socket || !self.reqs[reqid]) { // The socket has already been closed, perhaps due to an error that ocurred while a timeout // was scheduled. We can't do anything about it now. clearRequest(self.reqs, reqid); return; } else if (!options.timeouts[retrans]){ // If there is no other configured retransmission attempt, we raise a final timeout error clearRequest(self.reqs, reqid); return callback(new Error('Timeout')); } // Send the message. self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) { var entry = self.reqs[reqid]; if (err) { clearRequest(self.reqs, reqid); return callback(err); } else if (entry) { // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply. entry.sendStamp = Date.now(); entry.timeout = setTimeout(transmit, options.timeouts[retrans]); retrans += 1; } }); } // Register the callback to call when we receive a reply. self.reqs[reqid] = { callback: callback }; // Transmit the message. transmit(); }; // Shortcut to create a GetRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form. Session.prototype.get = function (options, callback) { var self = this, pkt; defaults(options, self.options); parseOids(options); if (!options.oid) { return callback(null, []); } pkt = new Packet(); pkt.community = options.community; pkt.version = options.version; pkt.pdu.varbinds[0].oid = options.oid; self.sendMsg(pkt, options, callback); }; // Shortcut to create a SetRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form, `options.value` to be an // integer and `options.type` to be asn1ber.T.Integer (2). Session.prototype.set = function (options, callback) { var self = this, pkt; defaults(options, self.options); parseOids(options); if (!options.oid) { throw new Error('Missing required option `oid`.'); } else if (options.value === undefined) { throw new Error('Missing required option `value`.'); } else if (!options.type) { throw new Error('Missing required option `type`.'); } pkt = new Packet(); pkt.community = options.community; pkt.version = options.version; pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU; pkt.pdu.varbinds[0].oid = options.oid; pkt.pdu.varbinds[0].type = options.type; pkt.pdu.varbinds[0].value = options.value; self.sendMsg(pkt, options, callback); }; // Shortcut to get all OIDs in the `options.oids` array sequentially. The // callback is called when the entire operation is completed. If // options.abortOnError is truish, an error while getting any of the values // will cause the callback to be called with error status. When // `options.abortOnError` is falsish (the default), any errors will be ignored // and any successfully retrieved values sent to the callback. Session.prototype.getAll = function (options, callback) { var self = this, results = [], combinedTimeoutTimer = null, combinedTimeoutExpired = false; defaults(options, self.options, { abortOnError: false }); parseOids(options); if (!options.oids || options.oids.length === 0) { return callback(null, []); } function getOne(c) { var oid, pkt, m, vb; pkt = new Packet(); pkt.community = options.community; pkt.version = options.version; pkt.pdu.varbinds = []; // Push up to 16 varbinds in the same message. // The number 16 isn't really that magical, it's just a nice round // number that usually seems to fit withing a single packet and gets // accepted by the switches I've tested it on. for (m = 0; m < 16 && c < options.oids.length; m++) { vb = new VarBind(); vb.oid = options.oids[c]; pkt.pdu.varbinds.push(vb); c++; } self.sendMsg(pkt, options, function (err, varbinds) { if (combinedTimeoutExpired) { return; } if (options.abortOnError && err) { clearTimeout(combinedTimeoutTimer); callback(err); } else { if (varbinds) { results = results.concat(varbinds); } if (c < options.oids.length) { getOne(c); } else { clearTimeout(combinedTimeoutTimer); callback(null, results); } } }); } if (options.combinedTimeout) { var combinedTimeoutEvent = function() { combinedTimeoutExpired = true; return callback(new Error('Timeout'), results); }; combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); } getOne(0); }; // Shortcut to create a GetNextRequest and send it, while registering a callback. // Needs `options.oid` to be an OID in array form. Session.prototype.getNext = function (options, callback) { var self = this, pkt; defaults(options, self.options); parseOids(options); if (!options.oid) { return callback(null, []); } pkt = new Packet(); pkt.community = options.community; pkt.version = options.version; pkt.pdu.type = 1; pkt.pdu.varbinds[0].oid = options.oid; self.sendMsg(pkt, options, callback); }; // Shortcut to get all entries below the specified OID. // The callback will be called once with the list of // varbinds that was collected, or with an error object. // Needs `options.oid` to be an OID in array form. Session.prototype.getSubtree = function (options, callback) { var self = this, vbs = [], combinedTimeoutTimer = null, combinedTimeoutExpired = false; defaults(options, self.options); parseOids(options); if (!options.oid) { return callback(null, []); } options.startOid = options.oid; // Helper to check whether `oid` in inside the tree rooted at // `root` or not. function inTree(root, oid) { var i; if (oid.length <= root.length) { return false; } for (i = 0; i < root.length; i++) { if (oid[i] !== root[i]) { return false; } } return true; } // Helper to handle the result of getNext and call the user's callback // as appropriate. The callback will see one of the following patterns: // - callback([an Error object], undefined) -- an error ocurred. // - callback(null, [a Packet object]) -- data from under the tree. // - callback(null, null) -- end of tree. function result(error, varbinds) { if (combinedTimeoutExpired) { return; } if (error) { clearTimeout(combinedTimeoutTimer); callback(error); } else { if (inTree(options.startOid, varbinds[0].oid)) { if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') { clearTimeout(combinedTimeoutTimer); callback(null, vbs); } else if (vbs.length && compareOids(vbs.slice(-1)[0].oid, varbinds[0].oid) !== 1) { return callback(new Error('OID not increasing')); } else { vbs.push(varbinds[0]); var next = { oid: varbinds[0].oid }; defaults(next, options); self.getNext(next, result); } } else { clearTimeout(combinedTimeoutTimer); callback(null, vbs); } } } if (options.combinedTimeout) { var combinedTimeoutEvent = function() { combinedTimeoutExpired = true; return callback(new Error('Timeout'), vbs); }; combinedTimeoutTimer = setTimeout(combinedTimeoutEvent, options.combinedTimeout); } self.getNext(options, result); }; // Close the socket. Necessary to finish the event loop and exit the program. Session.prototype.close = function () { var self = this; for (var reqid in self.reqs) { if (self.reqs[reqid].callback) { self.reqs[reqid].callback(new Error('Cancelled')); } clearRequest(self.reqs, reqid); } this.socket.close(); }; ================================================ FILE: package.json ================================================ { "name": "snmp-native", "description": "A native Javascript SNMP implementation for Node.js", "author": "Jakob Borg (http://nym.se/)", "keywords": [ "snmp", "asn.1", "asn1", "network", "monitoring" ], "homepage": "http://nym.se/node-snmp-native/docs/", "version": "1.2.0", "license": "MIT", "main": "lib/snmp.js", "scripts": { "test": "NODE_PATH=lib mocha -R spec", "hint": "jshint *.js lib/*.js", "doc": "docco lib/* example.js 2>/dev/null", "cov": "jscoverage lib lib-cov && EXPRESS_COV=1 NODE_PATH=lib-cov mocha -R html-cov > docs/coverage.html" }, "dependencies": {}, "devDependencies": { "docco": "~0.6.2", "jscoverage": "~0.3.6", "jshint": "~1.1.0", "mocha": "~1.9.0", "should": "~1.2.2", "snmpjs": "~0.1.3" }, "repository": { "type": "git", "url": "git://github.com/calmh/node-snmp-native.git" }, "bugs": { "url": "http://github.com/calmh/node-snmp-native/issues", "email": "snmp@nym.se" } } ================================================ FILE: test/asn1ber.js ================================================ var assert = require('assert'); var asn1ber = require('../lib/asn1ber'); describe('asn1ber', function () { describe('encodeInteger()', function () { it('returns one byte for zero', function () { var correct = '020100'; var buf = asn1ber.encodeInteger(0); assert.equal(correct, buf.toString('hex')); }); it('returns one byte for one', function () { var buf = asn1ber.encodeInteger(1); assert.equal(3, buf.length); assert.equal(2, buf[0]); // Integer assert.equal(1, buf[1]); // Length assert.equal(1, buf[2]); // Value }); it('does not return first byte and first bit of second byte all ones', function () { var correct = '020300ff94'; var buf = asn1ber.encodeInteger(0xff94); assert.equal(correct, buf.toString('hex')); }); it('does not return a negative-looking integer', function () { var correct = '02020088'; var buf = asn1ber.encodeInteger(0x88); assert.equal(correct, buf.toString('hex')); }); it('returns correctly for larger integer', function () { var buf = asn1ber.encodeInteger(1234567890); assert.equal(6, buf.length); assert.equal(2, buf[0]); // Integer assert.equal(4, buf[1]); // Length assert.equal(73, buf[2]); // Value assert.equal(150, buf[3]); // Value assert.equal(2, buf[4]); // Value assert.equal(210, buf[5]); // Value }); it('encodes a negative integer', function () { assert.equal('0201fe', asn1ber.encodeInteger(-2).toString('hex')); assert.equal('0202fdda', asn1ber.encodeInteger(-550).toString('hex')); assert.equal('0203ed2979', asn1ber.encodeInteger(-1234567).toString('hex')); assert.equal('0204f8a432eb', asn1ber.encodeInteger(-123456789).toString('hex')); }); }); describe('encodeGauge()', function () { it('returns one byte for zero', function () { var correct = '420100'; var buf = asn1ber.encodeGauge(0); assert.equal(correct, buf.toString('hex')); }); it('returns one byte for one', function () { var buf = asn1ber.encodeGauge(1); assert.equal(3, buf.length); assert.equal(0x42, buf[0]); // Gauge assert.equal(1, buf[1]); // Length assert.equal(1, buf[2]); // Value }); it('does not return first byte and first bit of second byte all ones', function () { var correct = '420300ff94'; var buf = asn1ber.encodeGauge(0xff94); assert.equal(correct, buf.toString('hex')); }); it('does not return a negative-looking integer', function () { var correct = '42020088'; var buf = asn1ber.encodeGauge(0x88); assert.equal(correct, buf.toString('hex')); }); it('returns correctly for larger integer', function () { var buf = asn1ber.encodeGauge(1234567890); assert.equal(6, buf.length); assert.equal(0x42, buf[0]); // Gauge assert.equal(4, buf[1]); // Length assert.equal(73, buf[2]); // Value assert.equal(150, buf[3]); // Value assert.equal(2, buf[4]); // Value assert.equal(210, buf[5]); // Value }); }); describe('encodeCounter()', function () { it('returns one byte for zero', function () { var correct = '410100'; var buf = asn1ber.encodeCounter(0); assert.equal(correct, buf.toString('hex')); }); it('returns one byte for one', function () { var buf = asn1ber.encodeCounter(1); assert.equal(3, buf.length); assert.equal(0x41, buf[0]); // Gauge assert.equal(1, buf[1]); // Length assert.equal(1, buf[2]); // Value }); it('does not return first byte and first bit of second byte all ones', function () { var correct = '410300ff94'; var buf = asn1ber.encodeCounter(0xff94); assert.equal(correct, buf.toString('hex')); }); it('does not return a negative-looking integer', function () { var correct = '41020088'; var buf = asn1ber.encodeCounter(0x88); assert.equal(correct, buf.toString('hex')); }); it('returns correctly for larger integer', function () { var buf = asn1ber.encodeCounter(1234567890); assert.equal(6, buf.length); assert.equal(0x41, buf[0]); // Gauge assert.equal(4, buf[1]); // Length assert.equal(73, buf[2]); // Value assert.equal(150, buf[3]); // Value assert.equal(2, buf[4]); // Value assert.equal(210, buf[5]); // Value }); }); describe('encodeTimeTicks()', function () { it('returns one byte for zero', function () { var correct = '430100'; var buf = asn1ber.encodeTimeTicks(0); assert.equal(correct, buf.toString('hex')); }); it('returns one byte for one', function () { var buf = asn1ber.encodeTimeTicks(1); assert.equal(3, buf.length); assert.equal(0x43, buf[0]); // Gauge assert.equal(1, buf[1]); // Length assert.equal(1, buf[2]); // Value }); it('does not return first byte and first bit of second byte all ones', function () { var correct = '430300ff94'; var buf = asn1ber.encodeTimeTicks(0xff94); assert.equal(correct, buf.toString('hex')); }); it('does not return a negative-looking integer', function () { var correct = '43020088'; var buf = asn1ber.encodeTimeTicks(0x88); assert.equal(correct, buf.toString('hex')); }); it('returns correctly for larger integer', function () { var buf = asn1ber.encodeTimeTicks(1234567890); assert.equal(6, buf.length); assert.equal(0x43, buf[0]); // Gauge assert.equal(4, buf[1]); // Length assert.equal(73, buf[2]); // Value assert.equal(150, buf[3]); // Value assert.equal(2, buf[4]); // Value assert.equal(210, buf[5]); // Value }); }); describe('encodeNull()', function () { it('returns the null representation', function () { var buf = asn1ber.encodeNull(); assert.equal(2, buf.length); assert.equal(5, buf[0]); // Null assert.equal(0, buf[1]); // Zero }); }); describe('encodeNoSuchObject()', function() { it('returns the noSuchObject representation', function () { var buf = asn1ber.encodeNoSuchObject(); assert.equal(2, buf.length); assert.equal(0x80, buf[0]); // Null assert.equal(0, buf[1]); // Zero }); }); describe('encodeNoSuchInstance()', function() { it('returns the noSuchInstance representation', function () { var buf = asn1ber.encodeNoSuchInstance(); assert.equal(2, buf.length); assert.equal(0x81, buf[0]); // Null assert.equal(0, buf[1]); // Zero }); }); describe('encodeEndOfMibView()', function() { it('returns the endOfMibView representation', function () { var buf = asn1ber.encodeEndOfMibView(); assert.equal(2, buf.length); assert.equal(0x82, buf[0]); // Null assert.equal(0, buf[1]); // Zero }); }); describe('encodeSequence()', function () { it('returns an empty sequence', function () { var buf = asn1ber.encodeSequence(new Buffer(0)); assert.equal(2, buf.length); assert.equal(0x30, buf[0]); // Sequence assert.equal(0, buf[1]); // Zero length }); it('returns wrapped sequence', function () { var buf = asn1ber.encodeSequence(new Buffer(10)); assert.equal(12, buf.length); assert.equal(0x30, buf[0]); // Sequence assert.equal(10, buf[1]); // Length }); it('returns correctly wrapped long sequence', function () { var buf = asn1ber.encodeSequence(new Buffer(1024)); assert.equal(1024 + 1 + 3, buf.length); assert.equal(0x30, buf[0]); // Sequence assert.equal(128 + 2, buf[1]); // Length assert.equal(0x04, buf[2]); // Length assert.equal(0x00, buf[3]); // Length }); it('does not modify the passed data', function () { var orig = new Buffer(10); for (i = 0; i < 10; i++) { orig[i] = i; } var buf = asn1ber.encodeSequence(orig); var i; assert.equal(12, buf.length); assert.equal(0x30, buf[0]); // Sequence assert.equal(10, buf[1]); // Length for (i = 0; i < 10; i++) { assert.equal(i, buf[i + 2]); } }); }); describe('encodeOctetString()', function () { it('returns an empty string', function () { var buf = asn1ber.encodeOctetString(''); assert.equal(2, buf.length); assert.equal(4, buf[0]); // OctetString assert.equal(0, buf[1]); // Zero length }); it('returns a simple string correctly', function () { var str = 'abc'; var buf = asn1ber.encodeOctetString(str); var i; assert.equal(5, buf.length); assert.equal(4, buf[0]); // OctetString assert.equal(str.length, buf[1]); // Length for (i = 0; i < str.length; i++) { assert.equal(str.charCodeAt(i), buf[i + 2]); } }); it('returns a simple buffer correctly', function () { var orig = new Buffer('0123456789', 'hex'); var buf = asn1ber.encodeOctetString(orig); var i; assert.equal(7, buf.length); assert.equal(4, buf[0]); // OctetString assert.equal(orig.length, buf[1]); // Length for (i = 0; i < orig.length; i++) { assert.equal(orig[i], buf[i + 2]); } }); it('throws an exception for unknown source types', function (done) { try { asn1ber.encodeOctetString(12); } catch (err) { done(); } }); }); describe('encodeOid()', function () { it('throws an exception on empty OID', function (done) { try { asn1ber.encodeOid([]); } catch (err) { assert.equal("Minimum OID length is two.", err.message); done(); } }); it('throws an exception for incorrect SNMP OIDs', function (done) { try { asn1ber.encodeOid([8, 8, 6, 7, 8]); } catch (err) { assert.equal("Invalid OID", err.message); done(); } }); it('returns an oid correctly', function () { var oid = [1, 3, 6, 1, 4, 1, 2680, 1234567, 2, 7, 3, 2, 0]; var correct = '06 0f 2b 06 01 04 01 94 78 cb ad 07 02 07 03 02 00'.replace(/ /g, ''); var buf = asn1ber.encodeOid(oid); assert.equal(correct, buf.toString('hex')); }); it('returns an oid correctly even though the oid doesn\'t start with .1.3', function () { var oid = [2, 77, 8802, 1, 1, 2, 1, 4, 1, 1, 9]; var correct = '06 0b 9d c4 62 01 01 02 01 04 01 01 09'.replace(/ /g, ''); var buf = asn1ber.encodeOid(oid); assert.equal(correct, buf.toString('hex')); }); }); describe('encodeRequest()', function () { it('returns a get request sequence', function () { var buf = asn1ber.encodeRequest(0, new Buffer(0)); assert.equal(2, buf.length); assert.equal(160, buf[0]); // GetRequest assert.equal(0, buf[1]); // Zero length }); }); describe('parseInteger()', function () { it('throws an exception when passed a non-integer buffer', function (done) { try { var buf = new Buffer('040100', 'hex'); asn1ber.parseInteger(buf); } catch (err) { done(); } }); it('returns zero for an encoded zero', function () { var buf = new Buffer('020100', 'hex'); var int = asn1ber.parseInteger(buf); assert.equal(0, int); }); it('returns one for an encoded one', function () { var buf = new Buffer('020101', 'hex'); var int = asn1ber.parseInteger(buf); assert.equal(1, int); }); it('correctly parses a random larger integer', function () { var buf = new Buffer('0204499602d2', 'hex'); var int = asn1ber.parseInteger(buf); assert.equal(1234567890, int); }); it('correctly parses a negative integer', function () { assert.equal(-2, asn1ber.parseInteger(new Buffer('0201fe', 'hex'))); assert.equal(-550, asn1ber.parseInteger(new Buffer('0202fdda', 'hex'))); assert.equal(-1234567, asn1ber.parseInteger(new Buffer('0203ed2979', 'hex'))); assert.equal(-123456789, asn1ber.parseInteger(new Buffer('0204f8a432eb', 'hex'))) }); }); describe('parseOctetString()', function () { it('throws an exception when passed a non-octetstring buffer', function (done) { try { var buf = new Buffer('020100', 'hex'); asn1ber.parseOctetString(buf); } catch (err) { done(); } }); it('returns an empty string', function () { var buf = new Buffer('0400', 'hex'); var str = asn1ber.parseOctetString(buf); assert.equal('', str); }); it('correctly parses a random string', function () { var buf = new Buffer('0407536f6c61726973', 'hex'); var str = asn1ber.parseOctetString(buf); assert.equal('Solaris', str); }); it('correctly parses a long string', function () { var buf = new Buffer('0481cf302d3031323334353637383920312d3031323334353637383920322d3031323334353637383920332d3031323334353637383920342d3031323334353637383920352d3031323334353637383920362d3031323334353637383920372d3031323334353637383920382d3031323334353637383920392d3031323334353637383920412d3031323334353637383920422d3031323334353637383920432d3031323334353637383920442d3031323334353637383920452d3031323334353637383920462d30313233343536373839', 'hex'); var str = asn1ber.parseOctetString(buf); assert.equal('0-0123456789 1-0123456789 2-0123456789 3-0123456789 4-0123456789 5-0123456789 6-0123456789 7-0123456789 8-0123456789 9-0123456789 A-0123456789 B-0123456789 C-0123456789 D-0123456789 E-0123456789 F-0123456789', str); var veryLongString = ""; for (var i = 0; i < 512; i++) { veryLongString = veryLongString + "foo"+i; } buf = asn1ber.encodeOctetString(veryLongString); str = asn1ber.parseOctetString(buf); assert.equal(veryLongString,str); }); }); describe('parseOid()', function () { it('throws an exception when passed a non-oid buffer', function (done) { try { var buf = new Buffer('020100', 'hex'); asn1ber.parseOid(buf); } catch (err) { done(); } }); it('returns the shortest possible oid', function () { var buf = new Buffer('06012b', 'hex'); var oid = asn1ber.parseOid(buf); assert.deepEqual([1, 3], oid); }); it('correctly parses a random oid', function () { var correct = [1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0]; var buf = new Buffer('06 0d 2b 06 01 04 01 94 78 01 02 07 03 02 00'.replace(/ /g, ''), 'hex'); var oid = asn1ber.parseOid(buf); assert.deepEqual(correct, oid); }); it('correctly parses a long oid with a large component', function () { var correct = [1, 3, 6, 1, 2, 1, 7, 7, 1, 8, 2, 16, 32, 1, 4, 112, 0, 39, 4, 214, 0, 0, 0, 0, 0, 0, 0, 2, 123, 0, 0, 0, 4179634304]; var buf = new Buffer('06 252b 0601 0201 0707 0108 0210 2001 0470 0027 0481 5600 0000 0000 0000 027b 0000 008f c980 d100 0500'.replace(/ /g, ''), 'hex'); var oid = asn1ber.parseOid(buf); assert.deepEqual(correct, oid); }); it('correctly parses an oid with a second octet > 39', function () { var correct = [2, 77, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0]; var buf = new Buffer('06 0d 9d 06 01 04 01 94 78 01 02 07 03 02 00'.replace(/ /g, ''), 'hex'); var oid = asn1ber.parseOid(buf); assert.deepEqual(correct, oid); }); }); describe('parseArray()', function () { it('throws an exception when passed a non-array buffer', function (done) { try { var buf = new Buffer('020100', 'hex'); asn1ber.parseArray(buf); } catch (err) { done(); } }); it('correctly parses a random array', function () { var correct = [0x30, 0x40, 0x16, 0x32]; var buf = new Buffer('40 04 30 40 16 32'.replace(/ /g, ''), 'hex'); var oid = asn1ber.parseArray(buf); assert.deepEqual(correct, oid); }); }); describe('parseOpaque()', function () { it('throws an exception when passed a non-opaque buffer', function (done) { try { var buf = new Buffer('020100', 'hex'); asn1ber.parseOpaque(buf); } catch (err) { done(); } }); it('return the hex representation of an opaque value', function () { var correct = '0x9f78043e920000'; var buf = new Buffer('44079f78043e920000', 'hex'); var str = asn1ber.parseOpaque(buf); assert.deepEqual(correct, str); }); }); describe('lengthArray()', function () { it('returns the length directly if it\'s 127 or less', function () { assert.deepEqual([ 0 ], asn1ber.unittest.lengthArray(0)); assert.deepEqual([ 47 ], asn1ber.unittest.lengthArray(47)); assert.deepEqual([ 127 ], asn1ber.unittest.lengthArray(127)); }); it('returns the length as an encoded integer if greater than 127', function () { assert.deepEqual([ 128 + 1, 128 ], asn1ber.unittest.lengthArray(128)); assert.deepEqual([ 128 + 2, 0x04, 0x01 ], asn1ber.unittest.lengthArray(1025)); }); }); }); ================================================ FILE: test/integration.js ================================================ /*globals it:false, describe:false before:false after:false beforeEach:false */ var assert = require('assert'); var dgram = require('dgram'); var should = require('should'); var snmp = require('../lib/snmp'); var snmpsrv = require('snmpjs'); var agent = snmpsrv.createAgent(); var data = { '1.3.6.42.1.2.3': // No leading dot! { '1': { '1': { type: 'OctetString', value: 'system description' }, '2': { type: 'Counter64', value: 1234567890 }, '3': { type: 'Integer', value: 1234567890 }, '4': { type: 'TimeTicks', value: 1234567890 }, '5': { type: 'Null', value: null }, '6': { type: 'OctetString', value: new Buffer('001122334455', 'hex') }, '7': { type: 'Counter32', value: 4294967295 }, '8': { type: 'Counter64', value: { lo: 0xffffffff, hi: 0xffffffff }}, // As close to 2^64-1 as Javascript can get... } }, '1.3.6.42.1.2.4': { '1': { '1': { type: 'Opaque', value: new Buffer('11223344', 'hex') } } } }; function setupResponder(agent, data) { Object.keys(data).forEach(function (oid) { var responses = data[oid]; var columns = Object.keys(responses).map(function (x) { return parseInt(x, 10); }); var handler = function (prq) { var lastPartOfOid, parts, col, inst, ival, val, vb, nextOid; lastPartOfOid = prq.oid.replace(oid, '').replace(/^\./, ''); parts = lastPartOfOid.split('.').filter(function (s) { return s.length > 0; }).map(function (x) { return parseInt(x, 10); }); if (prq.op === snmpsrv.pdu.GetRequest) { col = parts[0].toString(); inst = parts[1].toString(); ival = responses[col][inst].value; if (typeof ival === 'function') { ival = ival(); } val = snmpsrv.data.createData({ type: responses[col][inst].type, value: ival }); vb = snmpsrv.varbind.createVarbind({ oid: prq.oid, data: val }); prq.done(vb); } else if (prq.op === snmpsrv.pdu.GetNextRequest) { if (parts.length === 0 || parts.length > 2) { col = columns[0]; inst = Object.keys(responses[col])[0]; } else if (parts.length === 1) { col = parts[0].toString(); inst = Object.keys(responses[col])[0]; } else if (parts.length === 2) { col = parts[0].toString(); inst = (parts[1] + 1).toString(); if (!responses[col][inst]) { col = (parts[0] + 1).toString(); if (!responses[col]) { return prq.done(); } inst = Object.keys(responses[col])[0]; } } if (!col || !inst) { prq.done() } else if (responses[col][inst]) { nextOid = oid + '.' + col + '.' + inst; ival = responses[col][inst].value; if (typeof ival === 'function') { ival = ival(); } val = snmpsrv.data.createData({ type: responses[col][inst].type, value: ival }); vb = snmpsrv.varbind.createVarbind({ oid: nextOid, data: val }); prq.done(vb); } else { prq.done(); } } }; agent.request({ oid: oid, columns: columns, handler: handler }); }); } setupResponder(agent, data); agent.request({ oid: '.1.3.6.12.1.2.4', columns: [ 1 ], handler: function (prq) { var val, vb; if (!prq.instance) { return prq.done(); } val = snmpsrv.data.createData({ type: 'Integer', value: prq.instance[0] }); vb = snmpsrv.varbind.createVarbind({ oid: prq.oid, data: val }); setTimeout(function () { prq.done(vb); }, prq.instance[0]); } }); // Create a fake server that reponds with nonsense. var server = dgram.createSocket('udp4'); server.on('message', function (msg, rinfo) { server.send(new Buffer(100), 0, 100, rinfo.port, rinfo.address); }); server.bind(1162); describe('integration', function () { before(function () { agent.bind({ family: 'udp4', port: 1161 }); }); after(function () { try { server.close(); agent.close(); } catch (err) { } }); describe('get', function () { it('sets valid send and receive timestamps', function (done) { var session = new snmp.Session({ port: 1161 }); var now = Date.now(); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 1] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].sendStamp.should.be.within(now, now + 50); varbinds[0].receiveStamp.should.be.within(varbinds[0].sendStamp, now + 150); done(); } }); }); it('parses a single OctetString value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 1] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 1]); varbinds[0].value.should.equal('system description'); varbinds[0].valueHex.should.equal('73797374656d206465736372697074696f6e'); done(); } }); }); it('parses a binary OctetString value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 6] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 6]); varbinds[0].valueHex.should.equal('001122334455'); done(); } }); }); it('parses a single Counter64 value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 2] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 2]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('parses a single Integer value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 3] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 3]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('parses a single TimeTicks value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 4] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 4]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('parses a single null value', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 5] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 5]); should.not.exist(varbinds[0].value); done(); } }); }); it('handles OIDs in string form', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: '.1.3.6.42.1.2.3.1.3' }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 3]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('gracefully handles undefined oid', function (done) { var session = new snmp.Session(); session.get({ }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(0); done(); } }); }); it('correctly understands a 2^32-1 Counter32', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: '.1.3.6.42.1.2.3.1.7' }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].value.should.equal(4294967295); // 2^32 - 1 done(); } }); }); it('correctly understands a 2^64-1 Counter64', function (done) { var session = new snmp.Session({ port: 1161 }); session.get({ oid: '.1.3.6.42.1.2.3.1.8' }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].value.should.equal(18446744073709552000); // 2^64 - 1, or as close as Javascripts float type comes. done(); } }); }); }); describe('timouts', function () { it('should encode the send time in the request id', function (done) { var now = Date.now(), req; var session = new snmp.Session({ port: 1161, timeouts: [ 50 ] }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 1] }, function (err, varbinds) { should.not.exist(err); should.exist(varbinds); varbinds.length.should.equal(1); // Truncate to 22 bits now &= 0x1fffff; // The request id should be the truncated time in millis, shifted // ten bits to the left. req = varbinds[0].requestId >>> 10; (now - req).should.be.within(-5, 0); // The leftmost ten bits should be increasing for packets // sent the same millisecond. It should be at most two. req = varbinds[0].requestId & 0x3ff; req.should.be.within(1, 2); done(); }); }); it('times out when the response takes longer than specified', function (done) { var session = new snmp.Session({ port: 1161, timeouts: [ 50 ] }); session.get({ oid: [1, 3, 6, 12, 1, 2, 4, 1, 100] }, function (err, varbinds) { should.not.exist(varbinds); should.exist(err); done(); }); }); it('does not time out when the timeout value is sufficient', function (done) { var session = new snmp.Session({ port: 1161, timeouts: [ 150 ] }); session.get({ oid: [1, 3, 6, 12, 1, 2, 4, 1, 100] }, function (err, varbinds) { should.not.exist(err); should.exist(varbinds); done(); }); }); it('does not time out when retransmits work', function (done) { var session = new snmp.Session({ port: 1161, timeouts: [ 50, 125 ] }); session.get({ oid: '.1.3.6.12.1.2.4.1.100' }, function (err, varbinds) { should.not.exist(err); should.exist(varbinds); done(); }); }); }); describe('options', function () { beforeEach(function () { snmp.defaultOptions.host = 'localhost'; snmp.defaultOptions.port = 161; }); it('gets a response given global default values', function (done) { snmp.defaultOptions.host = 'localhost'; snmp.defaultOptions.port = 1161; var session = new snmp.Session(); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 3] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 3]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('gets a response given session default values', function (done) { snmp.defaultOptions.host = 'example.com'; snmp.defaultOptions.port = 999; var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 3] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 3]); varbinds[0].value.should.equal(1234567890); done(); } }); }); it('gets a response given explicit values', function (done) { snmp.defaultOptions.host = 'example.com'; snmp.defaultOptions.port = 999; var session = new snmp.Session({ host: 'example.com', port: 999 }); session.get({ host: 'localhost', port: 1161, oid: [1, 3, 6, 42, 1, 2, 3, 1, 3] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 3]); varbinds[0].value.should.equal(1234567890); done(); } }); }); }); describe('getAll', function () { it('should get an array of oids', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var oids = [ [1, 3, 6, 42, 1, 2, 3, 1, 1], [1, 3, 6, 42, 1, 2, 3, 1, 2], [1, 3, 6, 42, 1, 2, 3, 1, 3], [1, 3, 6, 42, 1, 2, 3, 1, 4], [1, 3, 6, 42, 1, 2, 3, 1, 5] ]; // We need more than 16 oids to test sending more than one packet. var manyOids = [].concat(oids, oids, oids, oids); assert.equal(20, manyOids.length); session.getAll({ oids: manyOids }, function (err, vbs) { if (err) { done(err); } else { vbs.length.should.equal(20); for (var i = 0; i < 4; i++) { vbs[5 * i + 0].value.should.equal('system description'); vbs[5 * i + 1].value.should.equal(1234567890); vbs[5 * i + 2].value.should.equal(1234567890); vbs[5 * i + 3].value.should.equal(1234567890); should.not.exist(vbs[5 * i + 4].value); } done(); } }); }); it('should get an array of oids in string form', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var oids = [ '.1.3.6.42.1.2.3.1.1', '.1.3.6.42.1.2.3.1.2', '.1.3.6.42.1.2.3.1.3', '.1.3.6.42.1.2.3.1.4', '.1.3.6.42.1.2.3.1.5' ]; session.getAll({ oids: oids }, function (err, vbs) { if (err) { done(err); } else { vbs.length.should.equal(5); vbs[0].value.should.equal('system description'); vbs[1].value.should.equal(1234567890); vbs[2].value.should.equal(1234567890); vbs[3].value.should.equal(1234567890); should.not.exist(vbs[4].value); done(); } }); }); it('should get an array of oids from specific host and community', function (done) { var session = new snmp.Session(); var oids = [ [1, 3, 6, 42, 1, 2, 3, 1, 1], [1, 3, 6, 42, 1, 2, 3, 1, 2], [1, 3, 6, 42, 1, 2, 3, 1, 3], [1, 3, 6, 42, 1, 2, 3, 1, 4], [1, 3, 6, 42, 1, 2, 3, 1, 5] ]; session.getAll({ oids: oids, host: 'localhost', community: 'any', port: 1161 }, function (err, vbs) { if (err) { done(err); } else { vbs.length.should.equal(5); vbs[0].value.should.equal('system description'); vbs[1].value.should.equal(1234567890); vbs[2].value.should.equal(1234567890); vbs[3].value.should.equal(1234567890); should.not.exist(vbs[4].value); done(); } }); }); it('gracefully handles undefined oids', function (done) { var session = new snmp.Session(); session.getAll({ }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(0); done(); } }); }); it('gracefully handles empty oids list', function (done) { var session = new snmp.Session(); session.getAll({ oids: [] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(0); done(); } }); }); it('should throw an error for invalid oid', function () { var session = new snmp.Session(); var test = function () { session.getNext({ oids: [ '1.3.6.42.1.2.3.1' ] }, function (err, vbs) { }); }; test.should.throw(/Invalid OID format/); }); }); describe('getNext', function () { it('should get a new value', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.getNext({ oid: [1, 3, 6, 42, 1, 2, 3, 1, 5] }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 6]); varbinds[0].valueHex.should.equal('001122334455'); done(); } }); }); it('should get a new value with oid in string form', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.getNext({ oid: '.1.3.6.42.1.2.3.1.5' }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(1); varbinds[0].oid.should.eql([1, 3, 6, 42, 1, 2, 3, 1, 6]); varbinds[0].valueHex.should.equal('001122334455'); done(); } }); }); it('gracefully handles undefined oid', function (done) { var session = new snmp.Session(); session.getNext({ }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(0); done(); } }); }); it('should throw an error for invalid oid', function () { var session = new snmp.Session(); var test = function () { session.getNext({ oid: '1.3.6.42.1.2.3.1' }, function (err, vbs) { }); }; test.should.throw(/Invalid OID format/); }); }); describe('getSubtree', function () { it('should get a complete tree', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.getSubtree({ oid: [1, 3, 6, 42, 1, 2, 3, 1] }, function (err, vbs) { if (err) { done(err); } else { vbs.length.should.equal(8); vbs[0].value.should.equal('system description'); vbs[1].value.should.equal(1234567890); vbs[2].value.should.equal(1234567890); vbs[3].value.should.equal(1234567890); should.not.exist(vbs[4].value); vbs[5].valueHex.should.equal('001122334455'); done(); } }); }); it('should get a complete tree with oid in string form', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.getSubtree({ oid: '.1.3.6.42.1.2.4.1' }, function (err, vbs) { if (err) { done(err); } else { vbs.length.should.equal(1); vbs[0].value.should.equal('0x11223344'); // Opaque done(); } }); }); it('gracefully handles undefined oid', function (done) { var session = new snmp.Session(); session.getSubtree({ }, function (err, varbinds) { if (err) { done(err); } else { varbinds.length.should.equal(0); done(); } }); }); it('should throw an error for invalid oid', function () { var session = new snmp.Session(); var test = function () { session.get({ oid: '1.3.6.42.1.2.3.1' }, function (err, vbs) { }); }; test.should.throw(/Invalid OID format/); }); }); describe('set', function () { it('should throw an error for unknown value types', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var test = function () { session.set({ oid: [1, 3, 6, 42, 1, 2, 3, 1], value: 5, type: 4 }, function (err, vbs) { }); }; test.should.throw(); }); it('should not throw an error for integer type', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.set({ oid: [1, 3, 6, 42, 1, 2, 3, 1], value: 5, type: 2 }, function (err, vbs) { }); }); it('should gracefully handle an undefined callback', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.set({ oid: [1, 3, 6, 42, 1, 2, 3, 1], value: 5, type: 2 }); }); it('should not throw an error for string oid', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var test = function () { session.set({ oid: '.1.3.6.42.1.2.3.1', value: 5, type: 2 }, function (err, vbs) { }); }; test.should.not.throw(); }); it('should not throw an error for value zero', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var test = function () { session.set({ oid: '.1.3.6.42.1.2.3.1', value: 0, type: 2 }, function (err, vbs) { }); }; test.should.not.throw(); }); it('should not throw an error for IP number value', function () { var session = new snmp.Session({ host: 'localhost', port: 1161 }); var test = function () { session.set({ oid: '.1.3.6.42.1.2.3.1', value: '172.16.32.64', type: 0x40 }, function (err, vbs) { }); }; test.should.not.throw(); }); it('should throw an error for missing oid', function () { var session = new snmp.Session(); var test = function () { session.set({ value: 5, type: 2 }, function (err, vbs) { }); }; test.should.throw(/Missing required option/); }); it('should throw an error for invalid oid', function () { var session = new snmp.Session(); var test = function () { session.set({ oid: '1.3.6.42.1.2.3.1', value: 5, type: 2 }, function (err, vbs) { }); }; test.should.throw(/Invalid OID format/); }); it('should throw an error for missing value', function () { var session = new snmp.Session(); var test = function () { session.set({ oid: '.1.3.6.42.1.2.3.1', type: 2 }, function (err, vbs) { }); }; test.should.throw(/Missing required option/); }); it('should throw an error for missing type', function () { var session = new snmp.Session(); var test = function () { session.set({ oid: '.1.3.6.42.1.2.3.1', value: 42 }, function (err, vbs) { }); }; test.should.throw(/Missing required option/); }); }); describe('errors', function () { it('should return a noSuchObject varbind', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.get({ oid: [1, 3, 6, 0] }, function (err, varbinds) { should.not.exist(err); should.exist(varbinds); varbinds.length.should.equal(1); varbinds[0].type.should.equal(128); varbinds[0].value.should.equal('noSuchObject'); done(); }); }); it('should return a noSuchInstance varbind', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1161 }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1] }, function (err, varbinds) { should.not.exist(err); should.exist(varbinds); varbinds.length.should.equal(1); varbinds[0].type.should.equal(129); varbinds[0].value.should.equal('noSuchInstance'); done(); }); }); it('should return an error for nonexistant host', function (done) { var session = new snmp.Session({ host: '1.2.427.5' }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1] }, function (err, varbinds) { should.exist(err); should.not.exist(varbinds); done(); }); }); it('should return an error for host of the wrong address family', function (done) { // This actually results in a timeout. That works, I guess, since it indicates // a communication problem. I would have expected something more immediate. var session = new snmp.Session({ family: 'udp4', host: '2001:db8::1', timeouts: [ 100 ] }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1] }, function (err, varbinds) { should.exist(err); should.not.exist(varbinds); done(); }); }); it('should throw a parse error when the recieved data makes no sense', function (done) { var session = new snmp.Session({ host: 'localhost', port: 1162 }); session.on('error', function () { done(); }); session.get({ oid: [1, 3, 6, 42, 1, 2, 3, 1] }, function (err, varbinds) { }); }); }); }); ================================================ FILE: test/snmp.js ================================================ var assert = require('assert'); var snmp = require('../lib/snmp'); // A packet as generated by this library, lightly modified by hand to contain easily // distinguishable fields. var ex1 = new Buffer('30 2c 02 01 47 04 07 70 72 69 76 61 74 65 a4 1e 02 01 33 02 01 44 02 01 55 30 13 30 11 06 0d 2b 06 01 04 01 94 78 01 02 07 03 02 00 05 00'.replace(/ /g, ''), 'hex'); // A OctetString GetResponse from Net-SNMP var ex2 = new Buffer('304a 0201 0104 066e 796d 2e73 65a2 3d02 0459 8559 7002 0100 0201 0030 2f30 2d06 082b 0601 0201 0101 0004 2153 6f6c 6172 6973 2061 6e74 6f2e 6e79 6d2e 7365 2031 312e 3020 7068 7973 6963 616c'.replace(/ /g, ''), 'hex'); // A Counter32 GetResponse from Net-SNMP var ex3 = new Buffer('302e 0201 0104 0770 7269 7661 7465 a220 0204 12a1 7180 0201 0002 0100 3012 3010 060b 2b06 0102 011f 0101 0102 0141 0146'.replace(/ /g, ''), 'hex'); // A Counter64 GetResponse from Net-SNMP var ex4 = new Buffer('3030 0201 0104 0770 7269 7661 7465 a222 0204 07ba d0c8 0201 0002 0100 3014 3012 060b 2b06 0102 011f 0101 0106 0146 0305 369d'.replace(/ /g, ''), 'hex'); // A Gauge32 GetResponse from Net-SNMP var ex5 = new Buffer('3030 0201 0104 0770 7269 7661 7465 a222 0204 15fc af68 0201 0002 0100 3014 3012 060a 2b06 0102 0102 0201 0507 4204 3b9a ca00'.replace(/ /g, ''), 'hex'); // A TimeTicks GetResponse from Net-SNMP var ex6 = new Buffer('302e 0201 0104 0770 7269 7661 7465 a220 0204 72eb 6a85 0201 0002 0100 3012 3010 0609 2b06 0102 0119 0101 0043 0304 74ec'.replace(/ /g, ''), 'hex'); // A large SysDescr response. var ex7 = new Buffer('3081 ab02 0101 0407 7072 6976 6174 65a2 819c 0204 5d7f aeee 0201 0002 0100 3081 8d30 818a 0608 2b06 0102 0101 0100 047e 4461 7277 696e 206a 626f 7267 2d6d 6270 2031 312e 322e 3020 4461 7277 696e 204b 6572 6e65 6c20 5665 7273 696f 6e20 3131 2e32 2e30 3a20 5475 6520 4175 6720 2039 2032 303a 3534 3a30 3020 5044 5420 3230 3131 3b20 726f 6f74 3a78 6e75 2d31 3639 392e 3234 2e38 7e31 2f52 454c 4541 5345 5f58 3836 5f36 3420 7838 365f 3634'.replace(/ /g, ''), 'hex'); // A OID GetNextReponse var ex8 = new Buffer('30 35 02 01 01 04 07 70 72 69 76 61 74 65 a2 27 02 04 e6 34 17 a0 02 01 00 02 01 00 30 19 30 17 06 08 2b 06 01 02 01 01 02 00 06 0b 2b 06 01 04 01 bf 08 03 02 81 7f'.replace(/ /g, ''), 'hex'); // An IpAddress GetNextResponse var ex9 = new Buffer('30 36 02 01 01 04 07 70 72 69 76 61 74 65 a2 28 02 04 45 20 95 bb 02 01 00 02 01 00 30 1a 30 18 06 10 2b 06 01 02 01 03 01 01 03 04 01 81 2c 14 0a 01 40 04 ac 14 0a 01'.replace(/ /g, ''), 'hex'); // Some random dudes error packet var ex10 = new Buffer('30 82 00 61 02 01 01 04 06 70 75 62 6c 69 63 a2 82 00 52 02 04 2a 96 a4 01 02 01 00 02 01 00 30 82 00 42 30 82 00 1f 06 82 00 08 2b 06 01 02 01 01 05 00 04 11 44 6f 63 75 50 72 69 6e 74 20 43 4d 32 30 35 20 66 30 82 00 1b 06 82 00 0b 2b 06 01 02 01 2b 05 01 01 11 01 04 0a 57 46 47 2d 30 31 33 34 37 35'.replace(/ /g, ''), 'hex'); describe('snmp', function () { describe('encode()', function () { it('returns a correctly formatted buffer from a packet description', function () { var correct = '30 2c 02 01 01 04 07 70 72 69 76 61 74 65 a0 1e 02 01 05 02 01 06 02 01 07 30 13 30 11 06 0d 2b 06 01 04 01 94 78 01 02 07 03 02 00 05 00'.replace(/ /g, ''); var pkt = new snmp.Packet(); // A default getrequest pkt.community = 'private'; pkt.pdu.reqid = 5; pkt.pdu.error = 6; pkt.pdu.errorIndex = 7; pkt.pdu.varbinds[0].oid = [1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0]; var msg = snmp.encode(pkt); assert.equal(msg.toString('hex'), correct); }); it('returns a correctly formatted buffer from a packet description of a set request', function () { var correct = '302d 0201 0104 0770 7269 7661 7465 a31f 0204 380b b460 0201 0002 0100 3011 300f 060a 2b06 0102 0102 0201 0701 0201 02'.replace(/ /g, ''); var pkt = new snmp.Packet(); // A default getrequest pkt.community = 'private'; pkt.pdu.reqid = 940291168; pkt.pdu.type = 3; pkt.pdu.varbinds[0].oid = [1, 3, 6, 1, 2, 1, 2, 2, 1, 7, 1]; pkt.pdu.varbinds[0].type = 2; pkt.pdu.varbinds[0].value = 2; var msg = snmp.encode(pkt); assert.equal(msg.toString('hex'), correct); }); it('returns a correctly formatted buffer from a NoSuchObject packet', function () { var correct = '302c 0201 0104 0770 7269 7661 7465 a01e 0201 0502 0106 0201 0730 1330 1106 0d2b 0601 0401 9478 0102 0703 0200 8000'.replace(/ /g, ''); var pkt = new snmp.Packet(); // A default getrequest pkt.community = 'private'; pkt.pdu.reqid = 5; pkt.pdu.error = 6; pkt.pdu.errorIndex = 7; pkt.pdu.varbinds = [{ type: 0x80, oid: [1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0] }]; var msg = snmp.encode(pkt); assert.equal(msg.toString('hex'), correct); }); it('returns a correctly formatted buffer from a NoSuchInstance packet', function () { var correct = '302c 0201 0104 0770 7269 7661 7465 a01e 0201 0502 0106 0201 0730 1330 1106 0d2b 0601 0401 9478 0102 0703 0200 8100'.replace(/ /g, ''); var pkt = new snmp.Packet(); // A default getrequest pkt.community = 'private'; pkt.pdu.reqid = 5; pkt.pdu.error = 6; pkt.pdu.errorIndex = 7; pkt.pdu.varbinds = [{ type: 0x81, oid: [1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0] }]; var msg = snmp.encode(pkt); assert.equal(msg.toString('hex'), correct); }); it('returns a correctly formatted buffer from a NoSuchObject packet', function () { var correct = '302c 0201 0104 0770 7269 7661 7465 a01e 0201 0502 0106 0201 0730 1330 1106 0d2b 0601 0401 9478 0102 0703 0200 8200'.replace(/ /g, ''); var pkt = new snmp.Packet(); // A default getrequest pkt.community = 'private'; pkt.pdu.reqid = 5; pkt.pdu.error = 6; pkt.pdu.errorIndex = 7; pkt.pdu.varbinds = [{ type: 0x82, oid: [1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0] }]; var msg = snmp.encode(pkt); assert.equal(msg.toString('hex'), correct); }); }); describe('parse()', function () { it('throws a parse error for invalid packets', function (done) { try { snmp.parse(new Buffer('00112233445566', 'hex')); } catch (err) { done(); } }); it('returns a snmp.Packet structure', function () { var pkt = snmp.parse(ex1); assert.equal('Packet', pkt.constructor.name); }); it('returns a correct SNMP version field', function () { var pkt = snmp.parse(ex1); assert.equal(0x47, pkt.version); }); it('returns a correct SNMP community field', function () { var pkt = snmp.parse(ex1); assert.equal('private', pkt.community); }); it('returns a correct pdu type field', function () { var pkt = snmp.parse(ex1); assert.equal(4, pkt.pdu.type); }); it('returns a correct request id field', function () { var pkt = snmp.parse(ex1); assert.equal(0x33, pkt.pdu.reqid); }); it('returns a correct error field', function () { var pkt = snmp.parse(ex1); assert.equal(0x44, pkt.pdu.error); }); it('returns a correct error index field', function () { var pkt = snmp.parse(ex1); assert.equal(0x55, pkt.pdu.errorIndex); }); it('returns a correct varbind list', function () { var pkt = snmp.parse(ex1); assert.equal(1, pkt.pdu.varbinds.length); assert.deepEqual([1, 3, 6, 1, 4, 1, 2680, 1, 2, 7, 3, 2, 0], pkt.pdu.varbinds[0].oid); assert.equal(5, pkt.pdu.varbinds[0].type); // Null type assert.equal(null, pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed Net-SNMP OctetString GetResponse', function () { var pkt = snmp.parse(ex2); assert.equal(4, pkt.pdu.varbinds[0].type); assert.equal('Solaris anto.nym.se 11.0 physical', pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed Net-SNMP Counter32 GetResponse', function () { var pkt = snmp.parse(ex3); assert.equal(65, pkt.pdu.varbinds[0].type); assert.equal(70, pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed Net-SNMP Counter64 GetResponse', function () { var pkt = snmp.parse(ex4); assert.equal(70, pkt.pdu.varbinds[0].type); assert.equal(341661, pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed Net-SNMP Gauge32 GetResponse', function () { var pkt = snmp.parse(ex5); assert.equal(66, pkt.pdu.varbinds[0].type); assert.equal(1000000000, pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed Net-SNMP TimeTicks GetResponse', function () { var pkt = snmp.parse(ex6); assert.equal(67, pkt.pdu.varbinds[0].type); assert.equal(292076, pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed large OctetString response', function () { var pkt = snmp.parse(ex7); assert.equal(4, pkt.pdu.varbinds[0].type); assert.equal("Darwin jborg-mbp 11.2.0 Darwin Kernel Version 11.2.0: Tue Aug 9 20:54:00 PDT 2011; root:xnu-1699.24.8~1/RELEASE_X86_64 x86_64", pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed ObjectId response', function () { var pkt = snmp.parse(ex8); assert.equal(6, pkt.pdu.varbinds[0].type); assert.deepEqual([1,3,6,1,4,1,8072,3,2,255], pkt.pdu.varbinds[0].value); }); it('returns a correctly parsed IpAddress response', function () { var pkt = snmp.parse(ex9); assert.equal(64, pkt.pdu.varbinds[0].type); assert.deepEqual([172,20,10,1], pkt.pdu.varbinds[0].value); }); it('does not error out on a random packet', function () { var pkt = snmp.parse(ex10); assert.equal("DocuPrint CM205 f", pkt.pdu.varbinds[0].value); assert.equal("WFG-013475", pkt.pdu.varbinds[1].value); }); }); describe('compareOids()', function () { it('returns zero for two empty OIDs', function () { assert.equal(0, snmp.compareOids([], [])); }); it('returns in the favour of the non-undefinedOID', function () { assert.equal(1, snmp.compareOids(undefined, [])); assert.equal(-1, snmp.compareOids([], undefined)); }); it('returns in the favour of the non-empty OID', function () { assert.equal(1, snmp.compareOids([], [0])); assert.equal(-1, snmp.compareOids([0], [])); }); it('returns in the favour of the larger OID', function () { assert.equal(1, snmp.compareOids([1,2,1,2,1,2,1,2], [1,2,1,2,5,2])); assert.equal(-1, snmp.compareOids([1,2,1,2,5,2], [1,2,1,2,1,2,1,2])); }); it('returns in the favour of the longer OID', function () { assert.equal(1, snmp.compareOids([1,2,1,2,1,2,1,2], [1,2,1,2,1,2,1,2,1,2])); assert.equal(-1, snmp.compareOids([1,2,1,2,1,2,1,2,1,2,1,2], [1,2,1,2,1,2,1,2])); }); }); });