"use strict";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 [](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 ================================================
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
};The types are also available for consumers of the library.
exports.types = T;
exports.pduTypes = P;
exports.unittest = {};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] !== 1 || oid[1] !== 3) {
throw new Error("SNMP OIDs always start with .1.3.");
}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 = [];
if (val === 0) {
array.push(0);
} else {
while (val > 0) {
array.push(val % 256);
val = parseInt(val / 256, 10);
}
}Do not produce integers that look negative (high bit of first byte set).
if (array[array.length - 1] >= 0x80) {
array.push(0);
}
return array.reverse();
}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.
exports.encodeInteger = function (val) {
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] = T.Integer;
buf[1] = arr.length;Copy the bytes into the array.
for (i = 0; i < arr.length; i++) {
buf[i + 2] = arr[i];
}
return buf;
};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;
};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);
};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 += buf[i + 1];
res.len *= 256;
}
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;
if (buf[0] !== T.Integer && buf[0] !== T.Counter &&
buf[0] !== T.Counter64 && buf[0] !== T.Gauge &&
buf[0] !== T.TimeTicks) {
throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer');
}
val = 0;
for (i = 0; i < buf[1]; i++) {
val *= 256;
val += buf[i + 2];
}
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) {
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.
return buf.toString('utf-8', 2, 2 + buf[1]);
};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;
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
oid = [ parseInt(buf[2] / 40, 10), buf[2] % 40 ];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*/| Line | Hits | Source |
|---|---|---|
| 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 | ||
| 11 | 1 | var 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 | ||
| 30 | 1 | var 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 | ||
| 39 | 1 | exports.types = T; |
| 40 | 1 | exports.pduTypes = P; |
| 41 | 1 | exports.unittest = {}; |
| 42 | ||
| 43 | // Private helper functions | |
| 44 | // ----- | |
| 45 | ||
| 46 | // Encode a length as it should be encoded. | |
| 47 | ||
| 48 | function lengthArray(len) { | |
| 49 | 345 | var arr = []; |
| 50 | ||
| 51 | 345 | if (len <= 127) { |
| 52 | // Return a single byte if the value is 127 or less. | |
| 53 | 339 | return [ len ]; |
| 54 | } else { | |
| 55 | // Otherwise encode it as a MSB base-256 integer. | |
| 56 | 6 | while (len > 0) { |
| 57 | 8 | arr.push(len % 256); |
| 58 | 8 | 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. | |
| 62 | 6 | arr.push(128 + arr.length); |
| 63 | 6 | arr.reverse(); |
| 64 | 6 | return arr; |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | 1 | exports.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 | ||
| 73 | function wrapper(type, contents) { | |
| 74 | 266 | var buf, len, i; |
| 75 | ||
| 76 | // Get the encoded length of the contents | |
| 77 | 266 | len = lengthArray(contents.length); |
| 78 | ||
| 79 | // Set up a buffer with the type and length bytes plus a straight copy of the content. | |
| 80 | 266 | buf = new Buffer(1 + contents.length + len.length); |
| 81 | 266 | buf[0] = type; |
| 82 | 266 | for (i = 1; i < len.length + 1; i++) { |
| 83 | 271 | buf[i] = len[i - 1]; |
| 84 | } | |
| 85 | 266 | contents.copy(buf, len.length + 1, 0); |
| 86 | 266 | 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 | ||
| 96 | function oidInt(val) { | |
| 97 | 3 | var bytes = []; |
| 98 | ||
| 99 | 3 | bytes.push(val % 128); |
| 100 | 3 | val = parseInt(val / 128, 10); |
| 101 | 3 | while (val > 127) { |
| 102 | 1 | bytes.push(128 + val % 128); |
| 103 | 1 | val = parseInt(val / 128, 10); |
| 104 | } | |
| 105 | 3 | bytes.push(val + 128); |
| 106 | 3 | 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 | ||
| 114 | function oidArray(oid) { | |
| 115 | 76 | var bytes, i, val; |
| 116 | ||
| 117 | // Enforce some minimum requirements on the OID. | |
| 118 | 76 | if (oid.length < 2) { |
| 119 | 1 | throw new Error("Minimum OID length is two."); |
| 120 | 75 | } else if (oid[0] !== 1 || oid[1] !== 3) { |
| 121 | 1 | 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. | |
| 125 | 74 | 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. | |
| 129 | 74 | for (i = 2; i < oid.length; i++) { |
| 130 | 511 | val = oid[i]; |
| 131 | 511 | if (val > 127) { |
| 132 | 3 | bytes = bytes.concat(oidInt(val)); |
| 133 | } else { | |
| 134 | 508 | bytes.push(val); |
| 135 | } | |
| 136 | } | |
| 137 | ||
| 138 | 74 | return bytes; |
| 139 | } | |
| 140 | ||
| 141 | // Divide an integer into base-256 bytes. | |
| 142 | // Most significant byte first. | |
| 143 | function intArray(val) { | |
| 144 | 198 | var array = []; |
| 145 | ||
| 146 | 198 | if (val === 0) { |
| 147 | 94 | array.push(0); |
| 148 | } else { | |
| 149 | 104 | while (val > 0) { |
| 150 | 246 | array.push(val % 256); |
| 151 | 246 | val = parseInt(val / 256, 10); |
| 152 | } | |
| 153 | } | |
| 154 | ||
| 155 | // Do not produce integers that look negative (high bit | |
| 156 | // of first byte set). | |
| 157 | 198 | if (array[array.length - 1] >= 0x80) { |
| 158 | 2 | array.push(0); |
| 159 | } | |
| 160 | ||
| 161 | 198 | 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 | ||
| 172 | 1 | exports.encodeInteger = function (val) { |
| 173 | 198 | var i, arr, buf; |
| 174 | ||
| 175 | // Get the bytes that we're going to encode. | |
| 176 | 198 | 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. | |
| 180 | 198 | buf = new Buffer(2 + arr.length); |
| 181 | 198 | buf[0] = T.Integer; |
| 182 | 198 | buf[1] = arr.length; |
| 183 | ||
| 184 | // Copy the bytes into the array. | |
| 185 | 198 | for (i = 0; i < arr.length; i++) { |
| 186 | 342 | buf[i + 2] = arr[i]; |
| 187 | } | |
| 188 | ||
| 189 | 198 | return buf; |
| 190 | }; | |
| 191 | ||
| 192 | // Create the representation of a Null, `05 00`. | |
| 193 | ||
| 194 | 1 | exports.encodeNull = function () { |
| 195 | 67 | var buf = new Buffer(2); |
| 196 | 67 | buf[0] = T.Null; |
| 197 | 67 | buf[1] = 0; |
| 198 | 67 | return buf; |
| 199 | }; | |
| 200 | ||
| 201 | // Encode a Sequence, which is a wrapper of type `30`. | |
| 202 | ||
| 203 | 1 | exports.encodeSequence = function (contents) { |
| 204 | 168 | return wrapper(T.Sequence, contents); |
| 205 | }; | |
| 206 | ||
| 207 | // Encode an OctetString, which is a wrapper of type `04`. | |
| 208 | ||
| 209 | 1 | exports.encodeOctetString = function (string) { |
| 210 | 52 | var buf, contents; |
| 211 | ||
| 212 | 52 | if (typeof string === 'string') { |
| 213 | 49 | contents = new Buffer(string); |
| 214 | 3 | } else if (Buffer.isBuffer(string)) { |
| 215 | 1 | contents = string; |
| 216 | } else { | |
| 217 | 2 | throw new Error('Only Buffer and string types are acceptable as OctetString.'); |
| 218 | } | |
| 219 | ||
| 220 | 50 | return wrapper(T.OctetString, contents); |
| 221 | }; | |
| 222 | ||
| 223 | // Encode an IpAddress, which is a wrapper of type `40`. | |
| 224 | ||
| 225 | 1 | exports.encodeIpAddress = function (address) { |
| 226 | 1 | var contents, octets, value = []; |
| 227 | ||
| 228 | 1 | if (typeof address !== 'string' && !Buffer.isBuffer(address)) { |
| 229 | 0 | 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 | ||
| 235 | 1 | octets = address.toString().split('.'); |
| 236 | 1 | if (octets.length !== 4) { |
| 237 | 0 | throw new Error('IP Addresses must be specified in dotted decimal format.'); |
| 238 | } | |
| 239 | 1 | octets.forEach(function (octet) { |
| 240 | 4 | var octetValue = parseInt(octet, 10); |
| 241 | 4 | if (octet < 0 || octet > 255) { |
| 242 | 0 | throw new Error('IP Address octets must be between 0 and 255 inclusive.' + JSON.stringify(octets)); |
| 243 | } | |
| 244 | 4 | value.push(octetValue); |
| 245 | }); | |
| 246 | ||
| 247 | 1 | contents = new Buffer(value); |
| 248 | ||
| 249 | 1 | return wrapper(T.IpAddress, contents); |
| 250 | }; | |
| 251 | ||
| 252 | // Encode an ObjectId. | |
| 253 | ||
| 254 | 1 | exports.encodeOid = function (oid) { |
| 255 | 76 | var buf, bytes, i, len; |
| 256 | ||
| 257 | // Get the encoded format of the OID. | |
| 258 | 76 | bytes = oidArray(oid); |
| 259 | ||
| 260 | // Get the encoded format of the length | |
| 261 | 74 | len = lengthArray(bytes.length); |
| 262 | ||
| 263 | // Fill in the buffer with type, length and OID data. | |
| 264 | 74 | buf = new Buffer(1 + bytes.length + len.length); |
| 265 | 74 | buf[0] = T.ObjectIdentifier; |
| 266 | 74 | for (i = 1; i < len.length + 1; i++) { |
| 267 | 74 | buf[i] = len[i - 1]; |
| 268 | } | |
| 269 | 74 | for (i = len.length + 1; i < bytes.length + len.length + 1; i++) { |
| 270 | 589 | buf[i] = bytes[i - len.length - 1]; |
| 271 | } | |
| 272 | ||
| 273 | 74 | return buf; |
| 274 | }; | |
| 275 | ||
| 276 | // Encode an SNMP request with specified `contents`. | |
| 277 | // The `type` code is 0 for `GetRequest`, 1 for `GetNextRequest`. | |
| 278 | ||
| 279 | 1 | exports.encodeRequest = function (type, contents) { |
| 280 | 47 | 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. | |
| 287 | function typeAndLength(buf) { | |
| 288 | 268 | var res, len, i; |
| 289 | ||
| 290 | 268 | res = { type: buf[0], len: 0, header: 1 }; |
| 291 | 268 | if (buf[1] < 128) { |
| 292 | // If bit 8 is zero, this byte indicates the content length (up to 127 bytes). | |
| 293 | 261 | res.len = buf[1]; |
| 294 | 261 | 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. | |
| 298 | 7 | for (i = 0; i < buf[1] - 128; i++) { |
| 299 | 10 | res.len += buf[i + 1]; |
| 300 | 10 | res.len *= 256; |
| 301 | } | |
| 302 | 7 | res.header += buf[1] - 128 + 1; |
| 303 | } | |
| 304 | 268 | return res; |
| 305 | } | |
| 306 | ||
| 307 | 1 | exports.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 | ||
| 312 | 1 | exports.parseInteger = function (buf) { |
| 313 | 281 | var i, val; |
| 314 | ||
| 315 | 281 | if (buf[0] !== T.Integer && buf[0] !== T.Counter && |
| 316 | buf[0] !== T.Counter64 && buf[0] !== T.Gauge && | |
| 317 | buf[0] !== T.TimeTicks) { | |
| 318 | 1 | throw new Error('Buffer ' + buf.toString('hex') + ' does not appear to be an Integer'); |
| 319 | } | |
| 320 | ||
| 321 | 280 | val = 0; |
| 322 | 280 | for (i = 0; i < buf[1]; i++) { |
| 323 | 547 | val *= 256; |
| 324 | 547 | val += buf[i + 2]; |
| 325 | } | |
| 326 | ||
| 327 | 280 | 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 | ||
| 333 | 1 | exports.parseOctetString = function (buf) { |
| 334 | 78 | if (buf[0] !== T.OctetString) { |
| 335 | 1 | 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. | |
| 340 | 77 | 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 | ||
| 346 | 1 | exports.parseOid = function (buf) { |
| 347 | 90 | var oid, val, i; |
| 348 | ||
| 349 | 90 | if (buf[0] !== T.ObjectIdentifier) { |
| 350 | 1 | 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 | |
| 354 | 89 | oid = [ parseInt(buf[2] / 40, 10), buf[2] % 40 ]; |
| 355 | ||
| 356 | // The rest of the data is a base-128-encoded OID | |
| 357 | 89 | for (i = 0; i < buf[1] - 1; i++) { |
| 358 | 681 | val = 0; |
| 359 | 681 | while (buf[i + 3] >= 128) { |
| 360 | 17 | val += buf[i + 3] - 128; |
| 361 | 17 | val *= 128; |
| 362 | 17 | i++; |
| 363 | } | |
| 364 | 681 | val += buf[i + 3]; |
| 365 | 681 | oid.push(val); |
| 366 | } | |
| 367 | ||
| 368 | 89 | return oid; |
| 369 | }; | |
| 370 | ||
| 371 | // Parse a buffer containing a representation of an array type. | |
| 372 | // This is for example an IpAddress. | |
| 373 | ||
| 374 | 1 | exports.parseArray = function (buf) { |
| 375 | 3 | var i, nelem, array; |
| 376 | ||
| 377 | 3 | if (buf[0] !== T.IpAddress) { |
| 378 | 1 | throw new Error('Buffer does not appear to be an array type.'); |
| 379 | } | |
| 380 | ||
| 381 | 2 | nelem = buf[1]; |
| 382 | 2 | array = []; |
| 383 | ||
| 384 | 2 | for (i = 0; i < buf[1]; i++) { |
| 385 | 8 | array.push(buf[i + 2]); |
| 386 | } | |
| 387 | ||
| 388 | 2 | return array; |
| 389 | }; | |
| 390 | ||
| 391 | // Parse a buffer containing a representation of an opaque type. | |
| 392 | // This is for example an IpAddress. | |
| 393 | ||
| 394 | 1 | exports.parseOpaque = function (buf) { |
| 395 | 4 | var hdr; |
| 396 | ||
| 397 | 4 | hdr = typeAndLength(buf); |
| 398 | ||
| 399 | 4 | if (hdr.type !== T.Opaque) { |
| 400 | 1 | throw new Error('Buffer does not appear to be an opaque type.'); |
| 401 | } | |
| 402 | ||
| 403 | 3 | return '0x' + buf.slice(hdr.header).toString('hex'); |
| 404 | }; | |
| 405 | ||
| 406 | /*globals exports: false*/ | |
| 407 |
| Line | Hits | Source |
|---|---|---|
| 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. | |
| 17 | 1 | var assert = require('assert'); |
| 18 | 1 | var dgram = require('dgram'); |
| 19 | 1 | var events = require('events'); |
| 20 | ||
| 21 | // We also need our ASN.1 BER en-/decoding routines. | |
| 22 | 1 | var asn1ber = require('./asn1ber'); |
| 23 | ||
| 24 | // Basic structures | |
| 25 | // ---- | |
| 26 | ||
| 27 | // A `VarBind` is the innermost structure, containing an OID-Value pair. | |
| 28 | function VarBind() { | |
| 29 | 223 | this.type = 5; |
| 30 | 223 | this.value = null; |
| 31 | } | |
| 32 | ||
| 33 | // The `PDU` contains the SNMP request or response fields and a list of `VarBinds`. | |
| 34 | function PDU() { | |
| 35 | 108 | this.type = asn1ber.pduTypes.GetRequestPDU; |
| 36 | 108 | this.reqid = 1; |
| 37 | 108 | this.error = 0; |
| 38 | 108 | this.errorIndex = 0; |
| 39 | 108 | this.varbinds = [ new VarBind() ]; |
| 40 | } | |
| 41 | ||
| 42 | // The `Packet` contains the SNMP version and community and the `PDU`. | |
| 43 | function Packet() { | |
| 44 | 108 | this.version = 1; |
| 45 | 108 | this.community = 'public'; |
| 46 | 108 | this.pdu = new PDU(); |
| 47 | } | |
| 48 | ||
| 49 | // Allow consumers to create packet structures from scratch. | |
| 50 | 1 | exports.Packet = Packet; |
| 51 | ||
| 52 | // Private helper functions | |
| 53 | // ---- | |
| 54 | ||
| 55 | // Concatenate several buffers to one. | |
| 56 | function concatBuffers(buffers) { | |
| 57 | 210 | var total, cur = 0, buf; |
| 58 | ||
| 59 | // First we calculate the total length, | |
| 60 | 210 | total = buffers.reduce(function (tot, b) { |
| 61 | 538 | return tot + b.length; |
| 62 | }, 0); | |
| 63 | ||
| 64 | // then we allocate a new Buffer large enough to contain all data, | |
| 65 | 210 | buf = new Buffer(total); |
| 66 | 210 | buffers.forEach(function (buffer) { |
| 67 | // finally we copy the data into the new larger buffer. | |
| 68 | 538 | buffer.copy(buf, cur, 0); |
| 69 | 538 | cur += buffer.length; |
| 70 | }); | |
| 71 | ||
| 72 | 210 | return buf; |
| 73 | } | |
| 74 | ||
| 75 | // Clear a pending packet when it times out or is successfully received. | |
| 76 | ||
| 77 | function clearRequest(reqs, reqid) { | |
| 78 | 43 | var self = this; |
| 79 | ||
| 80 | 43 | var entry = reqs[reqid]; |
| 81 | 43 | if (entry) { |
| 82 | 43 | if (entry.timeout) { |
| 83 | 42 | clearTimeout(entry.timeout); |
| 84 | } | |
| 85 | 43 | delete reqs[reqid]; |
| 86 | } | |
| 87 | } | |
| 88 | ||
| 89 | // Convert a string formatted OID to an array, leaving anything non-string alone. | |
| 90 | ||
| 91 | function parseSingleOid(oid) { | |
| 92 | 79 | if (typeof oid !== 'string') { |
| 93 | 59 | return oid; |
| 94 | } | |
| 95 | ||
| 96 | 20 | if (oid[0] !== '.') { |
| 97 | 4 | throw new Error('Invalid OID format'); |
| 98 | } | |
| 99 | ||
| 100 | 16 | oid = oid.split('.') |
| 101 | .filter(function (s) { | |
| 102 | 154 | return s.length > 0; |
| 103 | }) | |
| 104 | .map(function (s) { | |
| 105 | 138 | return parseInt(s, 10); |
| 106 | }); | |
| 107 | ||
| 108 | 16 | return oid; |
| 109 | } | |
| 110 | ||
| 111 | // Fix any OIDs in the 'oid' or 'oids' objects that are passed as strings. | |
| 112 | ||
| 113 | function parseOids(options) { | |
| 114 | 58 | if (options.oid) { |
| 115 | 48 | options.oid = parseSingleOid(options.oid); |
| 116 | } | |
| 117 | 55 | if (options.oids) { |
| 118 | 5 | 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 | ||
| 125 | function defaults(targ, _defs) { | |
| 126 | 159 | [].slice.call(arguments, 1).forEach(function (def) { |
| 127 | 164 | Object.keys(def).forEach(function (key) { |
| 128 | 818 | if (!targ.hasOwnProperty(key)) { |
| 129 | 468 | 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. | |
| 140 | function encode(pkt) { | |
| 141 | 47 | var version, community, reqid, err, erridx, vbs, pdu, message; |
| 142 | ||
| 143 | // We only support SNMPv2c, so enforce that version stamp. | |
| 144 | 47 | if (pkt.version !== 1) { |
| 145 | 0 | throw new Error('Only SNMPv2c is supported.'); |
| 146 | } | |
| 147 | ||
| 148 | // Encode the message header fields. | |
| 149 | 47 | version = asn1ber.encodeInteger(pkt.version); |
| 150 | 47 | community = asn1ber.encodeOctetString(pkt.community); |
| 151 | ||
| 152 | // Encode the PDU header fields. | |
| 153 | 47 | reqid = asn1ber.encodeInteger(pkt.pdu.reqid); |
| 154 | 47 | err = asn1ber.encodeInteger(pkt.pdu.error); |
| 155 | 47 | erridx = asn1ber.encodeInteger(pkt.pdu.errorIndex); |
| 156 | ||
| 157 | // Encode the PDU varbinds. | |
| 158 | 47 | vbs = []; |
| 159 | 47 | pkt.pdu.varbinds.forEach(function (vb) { |
| 160 | 73 | var oid = asn1ber.encodeOid(vb.oid), val; |
| 161 | ||
| 162 | 73 | if (vb.type === asn1ber.types.Null) { |
| 163 | 66 | val = asn1ber.encodeNull(); |
| 164 | 7 | } else if (vb.type === asn1ber.types.Integer) { |
| 165 | 5 | val = asn1ber.encodeInteger(vb.value); |
| 166 | 2 | } else if (vb.type === asn1ber.types.IpAddress) { |
| 167 | 1 | val = asn1ber.encodeIpAddress(vb.value); |
| 168 | 1 | } else if (vb.type === asn1ber.types.OctetString) { |
| 169 | 1 | val = asn1ber.encodeOctetString(vb.value); |
| 170 | } else { | |
| 171 | 0 | throw new Error('Unknown varbind type "' + vb.type + '" in encoding.'); |
| 172 | } | |
| 173 | 72 | vbs.push(asn1ber.encodeSequence(concatBuffers([oid, val]))); |
| 174 | }); | |
| 175 | ||
| 176 | // Concatenate all the varbinds together. | |
| 177 | 46 | vbs = asn1ber.encodeSequence(concatBuffers(vbs)); |
| 178 | ||
| 179 | // Create the PDU by concatenating the inner fields and adding a request structure around it. | |
| 180 | 46 | pdu = asn1ber.encodeRequest(pkt.pdu.type, concatBuffers([reqid, err, erridx, vbs])); |
| 181 | ||
| 182 | // Create the message by concatenating the header fields and the PDU. | |
| 183 | 46 | message = asn1ber.encodeSequence(concatBuffers([version, community, pdu])); |
| 184 | ||
| 185 | 46 | return message; |
| 186 | } | |
| 187 | ||
| 188 | 1 | exports.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 | ||
| 197 | function parse(buf) { | |
| 198 | 61 | var pkt, oid, bvb, vb, hdr; |
| 199 | ||
| 200 | 61 | pkt = new Packet(); |
| 201 | ||
| 202 | // First we have a sequence marker (two bytes). | |
| 203 | // We don't care about those, so cut them off. | |
| 204 | 61 | hdr = asn1ber.typeAndLength(buf); |
| 205 | 61 | assert.equal(asn1ber.types.Sequence, hdr.type); |
| 206 | 59 | buf = buf.slice(hdr.header); |
| 207 | ||
| 208 | // Then comes the version field (integer). Parse it and slice it. | |
| 209 | 59 | pkt.version = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); |
| 210 | 59 | buf = buf.slice(2 + buf[1]); |
| 211 | ||
| 212 | // We then get the community. Parse and slice. | |
| 213 | 59 | pkt.community = asn1ber.parseOctetString(buf.slice(0, buf[1] + 2)); |
| 214 | 59 | buf = buf.slice(2 + buf[1]); |
| 215 | ||
| 216 | // Here's the PDU structure. We're interested in the type. Slice the rest. | |
| 217 | 59 | hdr = asn1ber.typeAndLength(buf); |
| 218 | 59 | assert.ok(hdr.type >= 0xA0); |
| 219 | 59 | pkt.pdu.type = hdr.type - 0xA0; |
| 220 | 59 | buf = buf.slice(hdr.header); |
| 221 | ||
| 222 | // The request id field. | |
| 223 | 59 | pkt.pdu.reqid = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); |
| 224 | 59 | buf = buf.slice(2 + buf[1]); |
| 225 | ||
| 226 | // The error field. | |
| 227 | 59 | pkt.pdu.error = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); |
| 228 | 59 | buf = buf.slice(2 + buf[1]); |
| 229 | ||
| 230 | // The error index field. | |
| 231 | 59 | pkt.pdu.errorIndex = asn1ber.parseInteger(buf.slice(0, buf[1] + 2)); |
| 232 | 59 | buf = buf.slice(2 + buf[1]); |
| 233 | ||
| 234 | // Here's the varbind list. Not interested. | |
| 235 | 59 | hdr = asn1ber.typeAndLength(buf); |
| 236 | 59 | assert.equal(asn1ber.types.Sequence, hdr.type); |
| 237 | 59 | 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. | |
| 240 | 59 | pkt.pdu.varbinds = []; |
| 241 | 59 | while (buf[0] === asn1ber.types.Sequence) { |
| 242 | 85 | vb = new VarBind(); |
| 243 | ||
| 244 | // Slice off the sequence header. | |
| 245 | 85 | hdr = asn1ber.typeAndLength(buf); |
| 246 | 85 | assert.equal(asn1ber.types.Sequence, hdr.type); |
| 247 | 85 | bvb = buf.slice(hdr.header); |
| 248 | ||
| 249 | // Parse and save the ObjectIdentifier. | |
| 250 | 85 | 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. | |
| 257 | 85 | bvb = bvb.slice(2 + bvb[1]); |
| 258 | 85 | vb.type = bvb[0]; |
| 259 | 85 | if (vb.type === asn1ber.types.Null) { |
| 260 | // Null type. | |
| 261 | 16 | vb.value = null; |
| 262 | 69 | } else if (vb.type === asn1ber.types.OctetString) { |
| 263 | // Octet string type. | |
| 264 | 16 | vb.value = asn1ber.parseOctetString(bvb); |
| 265 | 53 | } 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. | |
| 271 | 41 | vb.value = asn1ber.parseInteger(bvb); |
| 272 | 12 | } else if (vb.type === asn1ber.types.ObjectIdentifier) { |
| 273 | // Object identifier type. | |
| 274 | 1 | vb.value = asn1ber.parseOid(bvb); |
| 275 | 11 | } else if (vb.type === asn1ber.types.IpAddress) { |
| 276 | // IP Address type. | |
| 277 | 1 | vb.value = asn1ber.parseArray(bvb); |
| 278 | 10 | } 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. | |
| 281 | 2 | vb.value = asn1ber.parseOpaque(bvb); |
| 282 | 8 | } 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. | |
| 285 | 1 | vb.value = 'endOfMibView'; |
| 286 | 7 | } else if (vb.type === asn1ber.types.NoSuchObject) { |
| 287 | // No such object error, returned when attempting to Get/GetNext an OID that doesn't exist. | |
| 288 | 1 | vb.value = 'noSuchObject'; |
| 289 | 6 | } 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. | |
| 292 | 6 | 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. | |
| 297 | 0 | 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. | |
| 301 | 85 | vb.valueRaw = bvb.slice(2); |
| 302 | 85 | 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. | |
| 306 | 85 | vb.requestId = pkt.pdu.reqid; |
| 307 | ||
| 308 | // Push whatever we parsed to the varbind list. | |
| 309 | 85 | pkt.pdu.varbinds.push(vb); |
| 310 | ||
| 311 | // Go fetch the next varbind, if there seems to be any. | |
| 312 | 85 | if (buf.length > hdr.header + hdr.len) { |
| 313 | 26 | buf = buf.slice(hdr.header + hdr.len); |
| 314 | } else { | |
| 315 | 59 | break; |
| 316 | } | |
| 317 | } | |
| 318 | ||
| 319 | 59 | return pkt; |
| 320 | } | |
| 321 | ||
| 322 | 1 | exports.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 | ||
| 330 | 1 | exports.compareOids = function (oidA, oidB) { |
| 331 | 9 | var mlen, i; |
| 332 | ||
| 333 | // The undefined OID, if there is any, is deemed lesser. | |
| 334 | 9 | if (typeof oidA === 'undefined' && typeof oidB !== 'undefined') { |
| 335 | 1 | return 1; |
| 336 | 8 | } else if (typeof oidA !== 'undefined' && typeof oidB === 'undefined') { |
| 337 | 1 | 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. | |
| 343 | 7 | mlen = Math.min(oidA.length, oidB.length); |
| 344 | 7 | for (i = 0; i < mlen; i++) { |
| 345 | 26 | if (oidA[i] > oidB[i]) { |
| 346 | 1 | return -1; |
| 347 | 25 | } else if (oidB[i] > oidA[i]) { |
| 348 | 1 | 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. | |
| 354 | 5 | if (oidA.length > oidB.length) { |
| 355 | 2 | return -1; |
| 356 | 3 | } else if (oidB.length > oidA.length) { |
| 357 | 2 | return 1; |
| 358 | } else { | |
| 359 | // The OIDs are obviously equal. | |
| 360 | 1 | return 0; |
| 361 | } | |
| 362 | }; | |
| 363 | ||
| 364 | ||
| 365 | // Communication functions | |
| 366 | // ----- | |
| 367 | ||
| 368 | // This is called for when we receive a message. | |
| 369 | ||
| 370 | function msgReceived(msg, rinfo) { | |
| 371 | 44 | var self = this, now = Date.now(), pkt, entry; |
| 372 | ||
| 373 | 44 | 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. | |
| 378 | 0 | return; |
| 379 | } | |
| 380 | ||
| 381 | // Parse the packet, or call the informative | |
| 382 | // parse error display if we fail. | |
| 383 | 44 | try { |
| 384 | 44 | pkt = parse(msg); |
| 385 | } catch (error) { | |
| 386 | 1 | 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. | |
| 392 | 43 | entry = self.reqs[pkt.pdu.reqid]; |
| 393 | 43 | if (entry) { |
| 394 | 40 | clearRequest(self.reqs, pkt.pdu.reqid); |
| 395 | ||
| 396 | 40 | if (typeof entry.callback === 'function') { |
| 397 | 39 | pkt.pdu.varbinds.forEach(function (vb) { |
| 398 | 65 | vb.receiveStamp = now; |
| 399 | 65 | vb.sendStamp = entry.sendStamp; |
| 400 | }); | |
| 401 | ||
| 402 | 39 | 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. | |
| 409 | 3 | var age = (Date.now() & 0x1fffff) - (pkt.pdu.reqid >>> 10); |
| 410 | 3 | if (age < 0) { |
| 411 | 0 | age += 0x200000; |
| 412 | } | |
| 413 | 3 | 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. | |
| 418 | 1 | exports.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 | ||
| 428 | function Session(options) { | |
| 429 | 47 | var self = this; |
| 430 | ||
| 431 | 47 | self.options = options || {}; |
| 432 | 47 | defaults(self.options, exports.defaultOptions); |
| 433 | ||
| 434 | 47 | self.reqs = {}; |
| 435 | 47 | self.socket = dgram.createSocket(self.options.family); |
| 436 | 47 | self.socket.on('message', msgReceived.bind(self)); |
| 437 | 47 | 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. | |
| 440 | 0 | self.socket = undefined; |
| 441 | }); | |
| 442 | 47 | 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. | |
| 451 | 1 | Session.prototype = Object.create(events.EventEmitter.prototype); |
| 452 | 1 | exports.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 | ||
| 460 | 1 | Session.prototype.requestId = function () { |
| 461 | 45 | var self = this, now = Date.now(); |
| 462 | ||
| 463 | 45 | if (!self.prevTs) { |
| 464 | 35 | self.prevTs = now; |
| 465 | 35 | self.counter = 0; |
| 466 | } | |
| 467 | ||
| 468 | 45 | if (now === self.prevTs) { |
| 469 | 35 | self.counter += 1; |
| 470 | 35 | if (self.counter > 1023) { |
| 471 | 0 | throw new Error('Request ID counter overflow. Adjust algorithm.'); |
| 472 | } | |
| 473 | } else { | |
| 474 | 10 | self.prevTs = now; |
| 475 | 10 | self.counter = 0; |
| 476 | } | |
| 477 | ||
| 478 | 45 | return ((now & 0x1fffff) << 10) + self.counter; |
| 479 | }; | |
| 480 | ||
| 481 | // Display useful debugging information when a parse error occurs. | |
| 482 | ||
| 483 | 1 | Session.prototype.parseError = function (error, buffer) { |
| 484 | 1 | var self = this, hex; |
| 485 | ||
| 486 | // Display a friendly introductory text. | |
| 487 | 1 | console.error('Woops! An error occurred while parsing an SNMP message. :('); |
| 488 | 1 | console.error('To have this problem corrected, please report the information below verbatim'); |
| 489 | 1 | console.error('via email to snmp@nym.se or by creating a GitHub issue at'); |
| 490 | 1 | console.error('https://github.com/calmh/node-snmp-native/issues'); |
| 491 | 1 | console.error(''); |
| 492 | 1 | console.error('Thanks!'); |
| 493 | ||
| 494 | // Display the stack backtrace so we know where the exception happened. | |
| 495 | 1 | console.error(''); |
| 496 | 1 | console.error(error.stack); |
| 497 | ||
| 498 | // Display the buffer data, nicely formatted so we can replicate the problem. | |
| 499 | 1 | console.error('\nMessage data:'); |
| 500 | 1 | hex = buffer.toString('hex'); |
| 501 | 1 | while (hex.length > 0) { |
| 502 | 7 | console.error(' ' + hex.slice(0, 32).replace(/([0-9a-f]{2})/g, '$1 ')); |
| 503 | 7 | hex = hex.slice(32); |
| 504 | } | |
| 505 | ||
| 506 | // Let the exception bubble upwards. | |
| 507 | 1 | self.emit('error', error); |
| 508 | }; | |
| 509 | ||
| 510 | // Send a message. Can be used after manually constructing a correct Packet structure. | |
| 511 | ||
| 512 | 1 | Session.prototype.sendMsg = function (pkt, options, callback) { |
| 513 | 45 | var self = this, buf, reqid, retrans = 0; |
| 514 | ||
| 515 | 45 | defaults(options, self.options); |
| 516 | ||
| 517 | 45 | reqid = self.requestId(); |
| 518 | 45 | pkt.pdu.reqid = reqid; |
| 519 | ||
| 520 | 45 | buf = encode(pkt); |
| 521 | ||
| 522 | function transmit() { | |
| 523 | 47 | 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. | |
| 526 | 0 | clearRequest(self.reqs, reqid); |
| 527 | 0 | return; |
| 528 | } | |
| 529 | ||
| 530 | // Send the message. | |
| 531 | 47 | self.socket.send(buf, 0, buf.length, options.port, options.host, function (err, bytes) { |
| 532 | 47 | var entry = self.reqs[reqid]; |
| 533 | ||
| 534 | 47 | if (err) { |
| 535 | 1 | clearRequest(self.reqs, reqid); |
| 536 | 1 | return callback(err); |
| 537 | 46 | } else if (entry) { |
| 538 | 46 | entry.sendStamp = Date.now(); |
| 539 | ||
| 540 | 46 | if (options.timeouts[retrans]) { |
| 541 | // Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply. | |
| 542 | 44 | entry.timeout = setTimeout(transmit, options.timeouts[retrans]); |
| 543 | 44 | retrans += 1; |
| 544 | } else { | |
| 545 | 2 | clearRequest(self.reqs, reqid); |
| 546 | 2 | return callback(new Error('Timeout')); |
| 547 | } | |
| 548 | } | |
| 549 | }); | |
| 550 | } | |
| 551 | ||
| 552 | // Register the callback to call when we receive a reply. | |
| 553 | 44 | self.reqs[reqid] = { callback: callback }; |
| 554 | // Transmit the message. | |
| 555 | 44 | 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 | ||
| 561 | 1 | Session.prototype.get = function (options, callback) { |
| 562 | 24 | var self = this, pkt; |
| 563 | ||
| 564 | 24 | defaults(options, self.options); |
| 565 | 24 | parseOids(options); |
| 566 | ||
| 567 | 23 | if (!options.oid) { |
| 568 | 1 | return callback(null, []); |
| 569 | } | |
| 570 | ||
| 571 | 22 | pkt = new Packet(); |
| 572 | 22 | pkt.community = options.community; |
| 573 | 22 | pkt.pdu.varbinds[0].oid = options.oid; |
| 574 | 22 | 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 | ||
| 581 | 1 | Session.prototype.set = function (options, callback) { |
| 582 | 10 | var self = this, pkt; |
| 583 | ||
| 584 | 10 | defaults(options, self.options); |
| 585 | 10 | parseOids(options); |
| 586 | ||
| 587 | 9 | if (!options.oid) { |
| 588 | 1 | throw new Error('Missing required option `oid`.'); |
| 589 | 8 | } else if (options.value === undefined) { |
| 590 | 1 | throw new Error('Missing required option `value`.'); |
| 591 | 7 | } else if (!options.type) { |
| 592 | 1 | throw new Error('Missing required option `type`.'); |
| 593 | } | |
| 594 | ||
| 595 | 6 | pkt = new Packet(); |
| 596 | 6 | pkt.community = options.community; |
| 597 | 6 | pkt.pdu.type = asn1ber.pduTypes.SetRequestPDU; |
| 598 | 6 | pkt.pdu.varbinds[0].oid = options.oid; |
| 599 | 6 | pkt.pdu.varbinds[0].type = options.type; |
| 600 | 6 | pkt.pdu.varbinds[0].value = options.value; |
| 601 | 6 | 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 | ||
| 611 | 1 | Session.prototype.getAll = function (options, callback) { |
| 612 | 5 | var self = this, results = []; |
| 613 | ||
| 614 | 5 | defaults(options, self.options, { abortOnError: false }); |
| 615 | 5 | parseOids(options); |
| 616 | ||
| 617 | 5 | if (!options.oids || options.oids.length === 0) { |
| 618 | 2 | return callback(null, []); |
| 619 | } | |
| 620 | ||
| 621 | function getOne(c) { | |
| 622 | 4 | var oid, pkt, m, vb; |
| 623 | ||
| 624 | 4 | pkt = new Packet(); |
| 625 | 4 | pkt.community = options.community; |
| 626 | 4 | 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. | |
| 632 | 4 | for (m = 0; m < 16 && c < options.oids.length; m++) { |
| 633 | 30 | vb = new VarBind(); |
| 634 | 30 | vb.oid = options.oids[c]; |
| 635 | 30 | pkt.pdu.varbinds.push(vb); |
| 636 | 30 | c++; |
| 637 | } | |
| 638 | ||
| 639 | 4 | self.sendMsg(pkt, options, function (err, varbinds) { |
| 640 | 4 | if (options.abortOnError && err) { |
| 641 | 0 | callback(err); |
| 642 | } else { | |
| 643 | 4 | if (varbinds) { |
| 644 | 4 | results = results.concat(varbinds); |
| 645 | } | |
| 646 | 4 | if (c < options.oids.length) { |
| 647 | 1 | getOne(c); |
| 648 | } else { | |
| 649 | 3 | callback(null, results); |
| 650 | } | |
| 651 | } | |
| 652 | }); | |
| 653 | } | |
| 654 | ||
| 655 | 3 | 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 | ||
| 661 | 1 | Session.prototype.getNext = function (options, callback) { |
| 662 | 16 | var self = this, pkt; |
| 663 | ||
| 664 | 16 | defaults(options, self.options); |
| 665 | 16 | parseOids(options); |
| 666 | ||
| 667 | 14 | if (!options.oid) { |
| 668 | 1 | return callback(null, []); |
| 669 | } | |
| 670 | ||
| 671 | 13 | pkt = new Packet(); |
| 672 | 13 | pkt.community = options.community; |
| 673 | 13 | pkt.pdu.type = 1; |
| 674 | 13 | pkt.pdu.varbinds[0].oid = options.oid; |
| 675 | 13 | 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 | ||
| 683 | 1 | Session.prototype.getSubtree = function (options, callback) { |
| 684 | 3 | var self = this, vbs = []; |
| 685 | ||
| 686 | 3 | defaults(options, self.options); |
| 687 | 3 | parseOids(options); |
| 688 | ||
| 689 | 3 | if (!options.oid) { |
| 690 | 1 | return callback(null, []); |
| 691 | } | |
| 692 | ||
| 693 | 2 | 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) { | |
| 698 | 11 | var i; |
| 699 | 11 | if (oid.length <= root.length) { |
| 700 | 1 | return false; |
| 701 | } | |
| 702 | 10 | for (i = 0; i < root.length; i++) { |
| 703 | 79 | if (oid[i] !== root[i]) { |
| 704 | 1 | return false; |
| 705 | } | |
| 706 | } | |
| 707 | 9 | 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) { | |
| 716 | 11 | if (error) { |
| 717 | 0 | callback(error); |
| 718 | } else { | |
| 719 | 11 | if (inTree(options.startOid, varbinds[0].oid)) { |
| 720 | 9 | if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') { |
| 721 | 0 | callback(null, vbs); |
| 722 | } else { | |
| 723 | 9 | vbs.push(varbinds[0]); |
| 724 | 9 | var next = { oid: varbinds[0].oid }; |
| 725 | 9 | defaults(next, options); |
| 726 | 9 | self.getNext(next, result); |
| 727 | } | |
| 728 | } else { | |
| 729 | 2 | callback(null, vbs); |
| 730 | } | |
| 731 | } | |
| 732 | } | |
| 733 | ||
| 734 | 2 | self.getNext(options, result); |
| 735 | }; | |
| 736 | ||
| 737 | // Close the socket. Necessary to finish the event loop and exit the program. | |
| 738 | ||
| 739 | 1 | Session.prototype.close = function () { |
| 740 | 0 | this.socket.close(); |
| 741 | }; | |
| 742 |
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];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();
});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));
}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 ...>
*/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";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');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 = 1;
this.community = 'public';
this.pdu = new PDU();
}Allow consumers to create packet structures from scratch.
exports.Packet = Packet;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];
}
});
});
}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 SNMPv2c, so enforce that version stamp.
if (pkt.version !== 1) {
throw new Error('Only SNMPv2c is 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) {
val = asn1ber.encodeNull();
} else if (vb.type === asn1ber.types.Integer) {
val = asn1ber.encodeInteger(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 {
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 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;
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);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.
bvb = bvb.slice(2 + bvb[1]);
vb.type = bvb[0];
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(2);
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;Compare two OIDs, returning -1, 0 or +1 depending on the relation between oidA and oidB.
exports.compareOids = function (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;
}
};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.parseError(error, msg);
}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') {
pkt.pdu.varbinds.forEach(function (vb) {
vb.receiveStamp = now;
vb.sendStamp = entry.sendStamp;
});
entry.callback(null, pkt.pdu.varbinds);
}
} else {This happens if we receive the response to a message we've already timed out and removed the request entry for. Maybe we shouldn't even log the warning.
Calculate the approximate send time and how old the packet is.
var age = (Date.now() & 0x1fffff) - (pkt.pdu.reqid >>> 10);
if (age < 0) {
age += 0x200000;
}
console.warn('Response with unknown request ID from ' + rinfo.address + '. Consider increasing timeouts (' + age + ' ms old?).');
}
}Default options for new sessions and operations.
exports.defaultOptions = {
host: 'localhost',
port: 161,
community: 'public',
family: 'udp4',
timeouts: [ 5000, 5000, 5000, 5000 ]
};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', 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.
});
}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;
};
Display useful debugging information when a parse error occurs.
Session.prototype.parseError = function (error, buffer) {
var self = this, hex;Display a friendly introductory text.
console.error('Woops! An error occurred while parsing an SNMP message. :(');
console.error('To have this problem corrected, please report the information below verbatim');
console.error('via email to snmp@nym.se or by creating a GitHub issue at');
console.error('https://github.com/calmh/node-snmp-native/issues');
console.error('');
console.error('Thanks!');Display the stack backtrace so we know where the exception happened.
console.error('');
console.error(error.stack);Display the buffer data, nicely formatted so we can replicate the problem.
console.error('\nMessage data:');
hex = buffer.toString('hex');
while (hex.length > 0) {
console.error(' ' + hex.slice(0, 32).replace(/([0-9a-f]{2})/g, '$1 '));
hex = hex.slice(32);
}Let the exception bubble upwards.
self.emit('error', error);
};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;
}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) {
entry.sendStamp = Date.now();
if (options.timeouts[retrans]) {Set timeout and record the timer so that we can (attempt to) cancel it when we receive the reply.
entry.timeout = setTimeout(transmit, options.timeouts[retrans]);
retrans += 1;
} else {
clearRequest(self.reqs, reqid);
return callback(new Error('Timeout'));
}
}
});
}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.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.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 = [];
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.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 (options.abortOnError && err) {
callback(err);
} else {
if (varbinds) {
results = results.concat(varbinds);
}
if (c < options.oids.length) {
getOne(c);
} else {
callback(null, results);
}
}
});
}
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.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 = [];
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 (error) {
callback(error);
} else {
if (inTree(options.startOid, varbinds[0].oid)) {
if (varbinds[0].value === 'endOfMibView' || varbinds[0].value === 'noSuchObject' || varbinds[0].value === 'noSuchInstance') {
callback(null, vbs);
} else {
vbs.push(varbinds[0]);
var next = { oid: varbinds[0].oid };
defaults(next, options);
self.getNext(next, result);
}
} else {
callback(null, vbs);
}
}
}
self.getNext(options, result);
};Close the socket. Necessary to finish the event loop and exit the program.
Session.prototype.close = function () {
this.socket.close();
};