├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── examples └── example.js ├── package.json ├── snmp.js ├── src └── snmp_binding.cc └── wscript /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.4) 2 | 3 | set(cmake_allow_loose_loop_constructs 1) 4 | 5 | if(COMMAND cmake_policy) 6 | cmake_policy(SET CMP0003 NEW) 7 | endif() 8 | 9 | INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/libev) 10 | INCLUDE_DIRECTORIES("/usr/include/node") 11 | 12 | ADD_LIBRARY(snmp_binding MODULE 13 | src/snmp_binding.cc 14 | ) 15 | 16 | IF(NOT DEFINED CONF_SYNC) 17 | SET(CONF_SYNC false 18 | CACHE BOOL "build synchronous interface (requires custom node.js)" FORCE 19 | ) 20 | ENDIF() 21 | 22 | IF(NOT DEFINED NODE_ROOT) 23 | SET(NODE_ROOT "/usr/local" 24 | CACHE STRING "where to look for node.js include files" FORCE) 25 | ENDIF() 26 | 27 | 28 | ADD_DEFINITIONS( 29 | # -D_LARGEFILE_SOURCE 30 | -D_FILE_OFFSET_BITS=64 31 | # -D_GNU_SOURCE 32 | -DEV_STANDALONE=1 33 | ) 34 | 35 | IF(CONF_SYNC) 36 | ADD_DEFINITIONS(-DEV_MULTIPLICITY=1) 37 | ELSE() 38 | ADD_DEFINITIONS(-DEV_MULTIPLICITY=0) 39 | ENDIF() 40 | 41 | INCLUDE_DIRECTORIES("${NODE_ROOT}/include/node") 42 | 43 | # these are needed when node has not been installed yet. Highly unusual situation. 44 | #INCLUDE_DIRECTORIES("${NODE_ROOT}/deps/libev") 45 | #INCLUDE_DIRECTORIES("${NODE_ROOT}/deps/v8/src") 46 | #INCLUDE_DIRECTORIES("${NODE_ROOT}/deps/libeio") 47 | #INCLUDE_DIRECTORIES("${NODE_ROOT}/src") 48 | 49 | SET_TARGET_PROPERTIES(snmp_binding PROPERTIES 50 | PREFIX "" SUFFIX ".node") 51 | 52 | TARGET_LINK_LIBRARIES(snmp_binding 53 | netsnmp 54 | ) 55 | 56 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | node-snmp - Net-SNMP library bindings for Node.js evented I/O for v8 javascript 2 | Copyright (C) 2011 Petr Běhan 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | node-snmp 2 | ========= 3 | 4 | Binding for Net-SNMP library, and simple wrapper to make life a bit easier. 5 | 6 | 7 | Features 8 | -------- 9 | So far only snmp queries and protocol V1 are supported. It has both synchronous 10 | and asynchronous interface, but the former requires libev compiled with support 11 | for multiple event loops. As stock node is by default compiled without this, 12 | only asynchronous queries are usually available - unless you compile node 13 | binary yourself. 14 | 15 | 16 | Dependencies 17 | ------------ 18 | Net-SNMP library - tested with version 5.4.3 19 | 20 | 21 | Build 22 | ----- 23 | 1. node-waf configure build 24 | 2. copy snmp.js and build/default/snmp_binding.node somewhere where node can 25 | find them: 26 | * ~/.node_libraries 27 | * \/node\_modules/snmp (copy package.json too when using this 28 | variant) 29 | 30 | See http://nodejs.org/docs/v0.4.7/api/modules.html for details. 31 | 32 | Eventually, "npm install -g" should do the trick too, but I can't seem to get 33 | it to work right now. 34 | 35 | Build .deb package 36 | ------------------ 37 | 38 | $ # git clone 39 | $ git branch debian origin/debian 40 | $ git-buildpackage --git-upstream-branch=master --git-debian-branch=debian --git-force-create -us -uc 41 | 42 | At present, you need nodejs-dev package from debian/sid repository to build. 43 | git-buildpackage is optional, debian has several options, but I found this 44 | to be the easiest way. 45 | 46 | API 47 | --- 48 | 49 | ### callbacks 50 | all callbacks are called with fairly standard (error, data) arguments. 51 | 52 | Error can be used as boolean false when query finished successfully, Error 53 | object with error description otherwise (convertible to String). 54 | 55 | Data is array of objects with 'value' and 'oid' properties, both of them of 56 | type Value: 57 | 58 | [ 59 | { oid: ..., value: ... }, 60 | { oid: ..., value: ... } 61 | ] 62 | 63 | ### Value - no public constructor 64 | * toArray, toString - return some aproximation of contained data, similar to 65 | what snmpwalk does 66 | * GetData - return raw data in several possible formats - see toArray 67 | implementation for details 68 | * GetType - one of SnmpValue.[VT_NUMBER, VT_TEXT, VT_OID, VT_RAW, VT_NULL] 69 | Remnant of early design, probably useless, could be removed in the future 70 | 71 | ### Error - no public constructor 72 | * toString() 73 | * isEof() - used internally by GetSubtree 74 | 75 | ### Connection(host, community) 76 | * Get, GetNext - map directly to corresponding SNMP operations, take OID (in 77 | any format) or array of OIDs (only as array of integers) and callback 78 | arguments 79 | * GetSubtree - use getNext to walk whole subtree of starting OID 80 | 81 | ### free functions in exports: 82 | * read_objid - parse dotted oid string into array of integers 83 | * parse_oid - parse any string to array of integers (including MIB 84 | translation) 85 | 86 | 87 | Usage example 88 | ------------- 89 | 90 | var conn = new (require('snmp').cSnmpConnection)("localhost", "public"); 91 | conn.get(".1.3.6.1.2.1.1.1", function(aError, aData) { 92 | console.log(aData[0].value.toString()); 93 | }); 94 | 95 | see examples/ directory for more complete sample 96 | 97 | 98 | TODO 99 | ---- 100 | (in no particular order) 101 | 102 | * support for V2 and V3 protocols, bulk queries 103 | * support TRAP operation 104 | * support conversion from OID to MIB symbolic name 105 | * make the package npm-compatible (npm link works, npm install does not) 106 | * find out why example doesn't work when copy/pasted to node cmdline, it must 107 | be run from file on disk 108 | * cleanup the C++ code (memory leaks in error code paths) 109 | 110 | -------------------------------------------------------------------------------- /examples/example.js: -------------------------------------------------------------------------------- 1 | var conn = new (require('snmp').Connection)("localhost", "public"); 2 | 3 | function snmpCallback(aExample, aError, aData) { 4 | if (aError) { 5 | console.log(aExample + ": error occured - " + aError); 6 | return; 7 | } 8 | var i = aData.length - 1; 9 | for (; i >= 0; --i) { 10 | var d = aData[i]; 11 | console.log(aExample + ": " + d.oid.toArray().join(".") + ": " 12 | + d.value.toString()); 13 | } 14 | } 15 | 16 | // error - OID doesn't match any single value (only a tree node) 17 | conn.Get(".1.3.6.1.2.1.1.1", snmpCallback.bind(undefined, "example 1")); 18 | 19 | // various ways to ask for sysDescr.0 20 | conn.Get(".1.3.6.1.2.1.1.1.0", snmpCallback.bind(undefined, "example 2")); 21 | conn.Get("sysDescr.0", snmpCallback.bind(undefined, "example 3")); 22 | conn.Get([[1,3,6,1,2,1,1,1,0], [1,3,6,1,2,1,1,1,0] ], 23 | snmpCallback.bind(undefined, "example 4")); 24 | 25 | // getNext - returns sysDescr.0 too 26 | conn.GetNext([0], snmpCallback.bind(undefined, "example 5")); 27 | 28 | // query all network interface names 29 | conn.GetSubtree("ifDescr", snmpCallback.bind(undefined, "example 6")); 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { "name": "snmp", 2 | "version": "0.1.0", 3 | "description": "SNMP client library", 4 | "keywords" : ["snmp"], 5 | "homepage": "https://github.com/huancz/node-snmp", 6 | "repository" : { 7 | "type" : "git", 8 | "url" : "git://github.com/huancz/node-snmp.git" 9 | }, 10 | "author" : "Petr Běhan ", 11 | "main" : "snmp.js", 12 | "scripts" : { 13 | "install" : "ln -s build/Release/snmp_binding.node . && node-waf configure build" 14 | }, 15 | "licenses" : [ 16 | { 17 | "type" : "MIT" 18 | } 19 | ], 20 | "engines" : { "node": ">= 0.4.0" } 21 | } 22 | -------------------------------------------------------------------------------- /snmp.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var binding = require('./snmp_binding'); 4 | var util = require('util'); 5 | 6 | var hex = [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 7 | 'A', 'B', 'C', 'D', 'E', 'F' ]; 8 | 9 | function interpret_oid(aOid) { 10 | if (typeof(aOid) == "string") { 11 | return binding.parse_oid(aOid); 12 | } else if (aOid instanceof Array) { 13 | return aOid; 14 | } else if (aOid instanceof binding.Value) { 15 | return aOid.toArray(); 16 | } else { 17 | assert.ok(false, "unsupported aOid data type " + typeof(aOid) + ":" + util.inspect(aOid)); 18 | } 19 | } 20 | 21 | // binding.Value.prototype.asString = function() {{{ 22 | binding.Value.prototype.toString = function toString() { 23 | var v = this.GetData(); 24 | 25 | if (v instanceof Buffer) { 26 | // Similar to heuristics in net-snmp snprint_octet_str. We can't use 27 | // exception, Buffer.toString('utf8') appears to NOT throw when string 28 | // contains invalid characters. 29 | // 30 | // We could do better with hints about data type from MIB... TODO? 31 | var printable = true; 32 | for(var i = 0; i < v.length; ++i) { 33 | if (v[i] > 127) { 34 | printable = false; 35 | break; 36 | } 37 | } 38 | if (printable) { 39 | return v.toString('ascii'); 40 | } else { 41 | var output = []; 42 | for (var i = 0; i < v.length; ++i) { 43 | output.push(hex[v[i] >> 4] + hex[v[i] & 15] + " "); 44 | } 45 | return output.join(""); 46 | } 47 | } else if (typeof(v) == "number") { 48 | return ("" + v); 49 | } else if (v instanceof Array) { 50 | return v.join(","); 51 | } else if (v === null) { 52 | return ""; 53 | } else { 54 | assert.ok(false, "internal error: unknown value type received from binding: " + v + " -> " + util.inspect(v)); 55 | } 56 | } 57 | // }}} 58 | 59 | // binding.Value.prototype.toArray = function() {{{ 60 | binding.Value.prototype.toArray = function() { 61 | var v = this.GetData(); 62 | 63 | if (v instanceof Buffer) { 64 | var res = []; 65 | for (var i = 0; i < v.length; ++i) { 66 | res.push(v[i]); 67 | } 68 | return res; 69 | } else if (v instanceof Number) { 70 | return [ v ]; 71 | } else if (v instanceof Array) { 72 | return v; 73 | } else if (v === null) { 74 | return [ v ]; 75 | } else { 76 | throw "internal error: unknown value type received from binding"; 77 | } 78 | } 79 | // }}} 80 | 81 | // function oid_compare_base(aLeft, aRight) {{{ 82 | function oid_compare_base(aLeft, aRight) { 83 | var left = interpret_oid(aLeft); 84 | var right = interpret_oid(aRight); 85 | 86 | var end = Math.min(left.length, right.length); 87 | var i; 88 | for (i = 0; i < end; ++i) { 89 | if (left[i] < right[i]) { 90 | return -1; 91 | } else if (left[i] > right[i]) { 92 | return 1; 93 | } 94 | } 95 | return 0; 96 | } 97 | // }}} 98 | exports.oid_compare_base = oid_compare_base; 99 | 100 | // lexicographicaly compare left and right value, result is same as for strcmp 101 | // (-1 if left sorts before right, 0 for equality and 1...) 102 | // function oid_compare(aLeft, aRight) {{{ 103 | function oid_compare(aLeft, aRight) { 104 | var left = interpret_oid(aLeft); 105 | var right = interpret_oid(aRight); 106 | 107 | var i = oid_compare_base(left, right); 108 | if (i != 0) { 109 | return i; 110 | } 111 | 112 | // common parts are equal - if right is longer, it sorts as greater 113 | if (left.length < right.length) { 114 | return -1; 115 | } else if (left.length > right.length) { 116 | return 1; 117 | } 118 | return 0; 119 | } 120 | // }}} 121 | exports.oid_compare = oid_compare; 122 | 123 | /** 124 | * Parse dotted oid format to array of integers. 125 | */ 126 | exports.read_objid = binding.read_objid; 127 | /** 128 | * Parse any recongnizable oid format (including MIB translation) to array 129 | * of integers. 130 | */ 131 | exports.parse_oid = binding.parse_oid; 132 | 133 | 134 | 135 | 136 | 137 | var ERR_OTHER = 0; 138 | var ERR_EOF = 1; 139 | var ERR_CYCLE = 2; 140 | 141 | var Error = function(aMessage, aCode) { 142 | this.message_ = aMessage; 143 | this.code_ = aCode || ERR_OTHER; 144 | } 145 | 146 | Error.prototype.toString = function() { 147 | return this.message_.toString(); 148 | } 149 | 150 | Error.prototype.isEof = function() { 151 | return this.code_ == ERR_EOF; 152 | } 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | var conn = exports.Connection = function Connection(aHost, aCredentials) { 161 | this.worker_ = new (binding.Connection)(aHost, aCredentials); 162 | } 163 | 164 | conn.prototype.Get = function(aOid, aCallback) { 165 | var oid = interpret_oid(aOid); 166 | if (aCallback) { 167 | return this.worker_.Get(oid, aCallback, false); 168 | } else { 169 | var result_err; 170 | var result_val; 171 | 172 | function callback(aError, aData) { 173 | result_err = aError; 174 | result_val = aData; 175 | } 176 | 177 | this.worker_.Get(oid, callback, true); 178 | 179 | if (result_err) { 180 | this.lastError = new Error(result_err); 181 | this.lastResult = null; 182 | return false; 183 | } else { 184 | this.lastResult = result_val; 185 | this.lastError = null; 186 | return true; 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * Direct mapping for GET_NEXT snmp operation - returns contents of next 193 | * lexicographically greater MIB variable, without restrictions. Sync behaviour 194 | * when callback is not specified and binding module was compiled with 195 | * -DEV_MULTIPLICITY=1, otherwise it behaves asynchronously. 196 | * 197 | * Sync version returns true if query succeeded, false when not. Results are 198 | * passed to caller in session.lastResult and session.lastError. 199 | * 200 | * Async version just calls callback with (aError, aData) when response is 201 | * ready. 202 | * 203 | * Data is passed as array of objects - we could support multi-oid queries in 204 | * future. Each member of the array will have 'oid' and 'value' properties. 205 | */ 206 | // conn.prototype.GetNext = function(aOid, aCallback) {{{ 207 | conn.prototype.GetNext = function(aOid, aCallback) { 208 | if (aCallback) { 209 | assert.ok(aCallback instanceof Function, 210 | "callback must be a function"); 211 | } 212 | 213 | var oid = interpret_oid(aOid); 214 | var that = this; 215 | 216 | function verifyNextResult(aReqOid, aReplyOid) { 217 | // reply to GET_NEXT must be next lexicographically greater row... but some 218 | // implementations don't follow this and sometimes return EQUAL_OR_GREATER 219 | // row => endless cycle. We break it here. 220 | if (oid_compare(aReqOid, aReplyOid) >= 0) { 221 | return false; 222 | } 223 | return true; 224 | } 225 | 226 | function sync_callback(aError, aData) { 227 | if (aError) { 228 | sync_err = true; 229 | that.lastResult = null; 230 | that.lastError = new Error(aError); 231 | return; 232 | } 233 | if (!verifyNextResult(oid, aData[0].oid)) { 234 | that.lastResult = null; 235 | that.lastError = new Error("broken peer implementation", ERR_CYCLE, null); 236 | return; 237 | } 238 | that.lastResult = aData; 239 | that.lastError = null; 240 | } 241 | 242 | function async_callback(aError, aData) { 243 | if (aError) { 244 | aCallback(new Error(aError), null); 245 | return; 246 | } 247 | // XXX: this won't work for multi-oid queries 248 | if (!verifyNextResult(oid, aData[0].oid)) { 249 | aCallback(new Error("broken peer implementation", ERR_CYCLE), null); 250 | console.log([oid, aData[0].oid]); 251 | return; 252 | } 253 | aCallback(false, aData); 254 | } 255 | 256 | if (aCallback) { 257 | return this.worker_.GetNext(oid, async_callback, false); 258 | } else { 259 | this.worker_.GetNext(oid, sync_callback, true); 260 | return !this.lastError; 261 | } 262 | } 263 | // }}} 264 | 265 | /** 266 | * Wrapper for GetNext, restricted to subtree queries. 267 | */ 268 | // conn.prototype.getNextSubtree = function(aOid, aBase, aCallback) {{{ 269 | conn.prototype.getNextSubtree = function(aOid, aBase, aCallback) { 270 | if (aCallback) { 271 | assert.ok(aCallback instanceof Function, "callback must be a function"); 272 | } 273 | var base = interpret_oid(aBase); 274 | 275 | function async_callback(aError, aData) { 276 | if (aError) { 277 | aCallback(new Error(aError), aData); 278 | return; 279 | } 280 | if (oid_compare_base(aData[0].oid, base) != 0) { 281 | aCallback(new Error("end of subtree", ERR_EOF), null); 282 | } else { 283 | aCallback(null, aData); 284 | } 285 | } 286 | 287 | if (aCallback) { 288 | return this.GetNext(aOid, async_callback); 289 | } else { 290 | if (!this.GetNext(base)) { 291 | return false; 292 | } 293 | if (oid_compare_base(this.lastResult[0].oid, base) != 0) { 294 | this.lastError = new Error("end of subtree", ERR_EOF); 295 | return false; 296 | } 297 | return true; 298 | } 299 | } 300 | // }}} 301 | 302 | // conn.prototype.GetSubtree = function(aOid, aCallback) {{{ 303 | conn.prototype.GetSubtree = function(aOid, aCallback) { 304 | // TODO: great opportunity for GET_BULK. IF we support v2 protocol, and the 305 | // connection is v2... 306 | 307 | // no sync version available for now 308 | // if (aCallback) { 309 | assert.ok(aCallback instanceof Function, "callback must be a function"); 310 | // } 311 | 312 | var that = this; 313 | var results = []; 314 | 315 | function get_subtree_callback(aError, aData) { 316 | if (aError) { 317 | if (aError.isEof()) { 318 | aCallback(false, results); 319 | } else { 320 | aCallback(aError); 321 | } 322 | } else { 323 | results.push(aData[0]); 324 | that.getNextSubtree(aData[0].oid, aOid, get_subtree_callback); 325 | } 326 | } 327 | 328 | this.getNextSubtree(aOid, aOid, get_subtree_callback); 329 | } 330 | // }}} 331 | 332 | // vim: ts=2 sw=2 et 333 | -------------------------------------------------------------------------------- /src/snmp_binding.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | extern "C" { 10 | 11 | // #include 12 | // we can't include mib_api.h where this function is declared, it causes 13 | // collision with node namespace. 14 | int read_objid(const char *, oid *, size_t *); 15 | 16 | oid* snmp_parse_oid(const char * argv, 17 | oid * root, 18 | size_t * rootlen 19 | ); 20 | 21 | } // extern "C" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | // TODO's: exception safety, RAII (see PerformRequest's handling of pdu for 38 | // example of the WRONG way to do it). Does RAII even work v8::ThrowException? 39 | // Plug memory leaks in error paths. 40 | 41 | 42 | namespace js = v8; 43 | using namespace v8; 44 | 45 | #if 1 && MODULE_EXPORTS_DOC 46 | 47 | // net-snmp "single session" (threadsafe) interface information 48 | // ============================================================ 49 | - session defines peer, tcp/udp, timeouts and credentials to use 50 | - session has at most one socket, and when querying select_info, only reports 51 | one timeout (least of all timeouts of all outstanding requests) 52 | - PDU - one request, asociated with session (sessions keeps copy, we can 53 | deallocate the PDU after snmp_sess_[async]_send) 54 | - Multiple PDUs can be queued at same time, we don''t need to wait for 55 | completion. 56 | - both session and PDU can have callback. Session callback gets 57 | called only if PDU callback was NULL (see snmp_api.c:5247, deep in 58 | _sess_process_packet). 59 | - callback receives msgid in it''s parameters to help pair the requests. 60 | Msgid is autogenerated by snmp internals in snmp_pdu_create. 61 | 62 | // generic net-snmp behaviour and pitfalls 63 | // ======================================= 64 | - GETNEXT operation doesn''t need any "previous" context. It requires input 65 | OID just like GET, but returns value of next strictly greater OID from the 66 | tree. Fails only when used at OID at or past the tree end. There can be 67 | multiple GETNEXT queries in progress at once. 68 | - GET and GET_NEXT support obtaining multiple values in one query (just call 69 | snmp_add_null_var more than once). GET_BULK is only needed to fetch whole 70 | subtree (and available only in SNMP2c and greater protocol versions). 71 | - it is unclear what callback should return. All examples just return 1 in 72 | every case. 73 | 74 | #endif // MODULE_EXPORTS_DOC 75 | 76 | // ==== SnmpSessionManager {{{ 77 | 78 | class SnmpSessionManager { 79 | public: 80 | struct storage_el { 81 | void* snmpHandle_; 82 | ev_io io_watcher_; 83 | }; 84 | 85 | typedef std::list storage_type; 86 | typedef storage_type::iterator storage_iterator; 87 | 88 | struct ex_prepare { 89 | ev_prepare watcher_; 90 | SnmpSessionManager* selfPtr_; 91 | }; 92 | struct ex_check { 93 | ev_check watcher_; 94 | SnmpSessionManager* selfPtr_; 95 | }; 96 | struct ex_timeout { 97 | bool active_; 98 | ev_timer watcher_; 99 | SnmpSessionManager* selfPtr_; 100 | 101 | ex_timeout() : active_(false), selfPtr_(NULL) {} 102 | }; 103 | 104 | private: 105 | static SnmpSessionManager* defaultInst_; 106 | 107 | storage_type storage_; 108 | ex_prepare prepare_; 109 | ex_check check_; 110 | ex_timeout timeout_; 111 | #if EV_MULTIPLICITY 112 | struct ev_loop* loop_; 113 | #endif 114 | 115 | SnmpSessionManager() { 116 | prepare_.selfPtr_ = this; 117 | check_.selfPtr_ = this; 118 | timeout_.selfPtr_ = this; 119 | #if EV_MULTIPLICITY 120 | loop_ = NULL; 121 | #endif 122 | } 123 | 124 | SnmpSessionManager(const SnmpSessionManager&); 125 | SnmpSessionManager& operator==(const SnmpSessionManager&); 126 | 127 | void prepare_cb_impl(EV_P); 128 | void check_cb_impl(EV_P); 129 | 130 | public: 131 | ~SnmpSessionManager() { 132 | assert(storage_.empty()); 133 | } 134 | 135 | void addClient(void* aSnmp); 136 | void removeClient(void* aSnmp); 137 | 138 | static void prepare_cb(EV_P_ ev_prepare* w, int revents); 139 | static void check_cb(EV_P_ ev_check* w, int revents); 140 | static void timeout_cb(EV_P_ ev_timer* w, int revents); 141 | 142 | static SnmpSessionManager* default_inst(); 143 | 144 | // call with ev_loop_new result 145 | static SnmpSessionManager* create(EV_P); 146 | }; 147 | 148 | SnmpSessionManager* SnmpSessionManager::defaultInst_ = NULL; 149 | 150 | SnmpSessionManager* SnmpSessionManager::default_inst() { 151 | if (!defaultInst_) { 152 | defaultInst_ = new SnmpSessionManager(); 153 | #if EV_MULTIPLICITY 154 | defaultInst_->loop_ = ev_default_loop(0); // EV_DEFAULT; 155 | #endif 156 | } 157 | return defaultInst_; 158 | } 159 | 160 | SnmpSessionManager* SnmpSessionManager::create(EV_P) { 161 | SnmpSessionManager* result = new SnmpSessionManager(); 162 | #if EV_MULTIPLICITY 163 | result->loop_ = loop; 164 | #endif 165 | return result; 166 | } 167 | 168 | void SnmpSessionManager::timeout_cb( 169 | EV_P_ ev_timer* w, int revents) 170 | { 171 | assert(false && "timeout callback shouldn't have been called directly!"); 172 | } 173 | 174 | void SnmpSessionManager::prepare_cb( 175 | EV_P_ ev_prepare* w, int revents) 176 | { 177 | ex_prepare* data = reinterpret_cast(w); 178 | assert(!data->selfPtr_->storage_.empty()); 179 | data->selfPtr_->prepare_cb_impl(EV_A); 180 | } 181 | 182 | void SnmpSessionManager::prepare_cb_impl(EV_P) { 183 | assert(!this->storage_.empty()); 184 | 185 | #ifndef NDEBUG 186 | // zero initialized, used for assert checks 187 | static unsigned int zero[sizeof(fd_set) / sizeof(unsigned int)]; 188 | #endif 189 | 190 | int nfds = 0; 191 | fd_set readSet; 192 | struct timeval timeout; 193 | int block = 1; 194 | 195 | FD_ZERO(&readSet); 196 | 197 | storage_iterator it_end = this->storage_.end(); 198 | for(storage_iterator it = this->storage_.begin(); 199 | it != it_end; ++it) 200 | { 201 | if (!it->snmpHandle_) { 202 | continue; 203 | } 204 | int retval = snmp_sess_select_info(it->snmpHandle_, &nfds, &readSet, 205 | &timeout, &block); 206 | #ifdef ENABLE_DEBUG_PRINTS 207 | // fprintf(stderr, "snmp_sess_select_info: %x -> %d\n", readSet, nfds - 1); 208 | #endif 209 | 210 | #ifndef NDEBUG 211 | // validity of asumptions used here is NOT guaranteed by net-snmp. It could 212 | // in theory add any number of read descriptors to the set. But when using 213 | // session API, it happens to add only one descriptor per session handle, 214 | // and it is equal to nfds - 1. We save several loops from 0 to nfds - one 215 | // for each managed handle. 216 | assert(retval == 1); 217 | assert(FD_ISSET((nfds - 1), &readSet)); 218 | FD_CLR((nfds - 1), &readSet); 219 | assert(!memcmp(&readSet, zero, sizeof(fd_set))); 220 | #endif 221 | 222 | ev_io_set(&it->io_watcher_, nfds - 1, EV_READ); 223 | ev_io_start(EV_A_ &it->io_watcher_); 224 | 225 | #ifdef ENABLE_DEBUG_PRINTS 226 | fprintf(stderr, "prepare: listen for read event on fd %d\n", it->io_watcher_.fd); 227 | #endif 228 | 229 | nfds = 0; 230 | } 231 | if (!block) { 232 | ev_tstamp next_timeout = timeout.tv_usec; 233 | next_timeout /= 1000000; 234 | next_timeout += timeout.tv_sec; 235 | #ifdef ENABLE_DEBUG_PRINTS 236 | fprintf(stderr, "block until %lf\n", next_timeout); 237 | #endif 238 | this->timeout_.active_ = true; 239 | ev_timer_init(&this->timeout_.watcher_, &SnmpSessionManager::timeout_cb, 240 | next_timeout, 0.0); 241 | ev_timer_start(EV_A_ &this->timeout_.watcher_); 242 | } 243 | } 244 | 245 | void SnmpSessionManager::check_cb( 246 | EV_P_ ev_check* w, int revents) 247 | { 248 | ex_check* data = reinterpret_cast(w); 249 | assert(!data->selfPtr_->storage_.empty()); 250 | data->selfPtr_->check_cb_impl(EV_A); 251 | } 252 | 253 | void SnmpSessionManager::check_cb_impl(EV_P) { 254 | fd_set readSet; 255 | FD_ZERO(&readSet); 256 | 257 | if (this->timeout_.active_) { 258 | ev_timer_stop(EV_A_ &this->timeout_.watcher_); 259 | this->timeout_.active_ = false; 260 | } 261 | 262 | storage_iterator it_end = storage_.end(); 263 | storage_iterator it_next; 264 | for(storage_iterator it = storage_.begin(); 265 | it != it_end; it = it_next) 266 | { 267 | it_next = it; 268 | ++it_next; 269 | if (!it->snmpHandle_) { 270 | storage_.erase(it); 271 | continue; 272 | } 273 | 274 | if (!it->snmpHandle_) { 275 | continue; 276 | } 277 | 278 | int revents = ev_clear_pending(EV_A_ &it->io_watcher_); 279 | if ((revents & READ) == READ) { 280 | #ifdef ENABLE_DEBUG_PRINTS 281 | fprintf(stderr, "read on fd %d\n", it->io_watcher_.fd); 282 | #endif 283 | FD_SET(it->io_watcher_.fd, &readSet); 284 | snmp_sess_read(it->snmpHandle_, &readSet); 285 | } else { 286 | snmp_sess_timeout(it->snmpHandle_); 287 | } 288 | ev_io_stop(EV_A_ &it->io_watcher_); 289 | 290 | if (!it->snmpHandle_) { 291 | storage_.erase(it); 292 | continue; 293 | } 294 | } 295 | 296 | if (storage_.empty()) { 297 | ev_prepare_stop(EV_A_ &this->prepare_.watcher_); 298 | ev_check_stop(EV_A_ &this->check_.watcher_); 299 | } 300 | } 301 | 302 | void SnmpSessionManager::addClient(void* aSnmp) { 303 | if (storage_.empty()) { 304 | ev_prepare_init(&this->prepare_.watcher_, SnmpSessionManager::prepare_cb); 305 | ev_check_init(&this->check_.watcher_, SnmpSessionManager::check_cb); 306 | 307 | #if EV_MULTIPLICITY 308 | ev_prepare_start(this->loop_, &this->prepare_.watcher_); 309 | ev_check_start(this->loop_, &this->check_.watcher_); 310 | #else 311 | ev_prepare_start(&this->prepare_.watcher_); 312 | ev_check_start(&this->check_.watcher_); 313 | #endif 314 | } 315 | storage_.push_front((storage_el){ aSnmp }); 316 | } 317 | 318 | namespace { 319 | struct handleFind { 320 | private: 321 | void* handle_; 322 | 323 | public: 324 | handleFind(void* aHandle) 325 | : handle_(aHandle) 326 | { } 327 | bool operator()(SnmpSessionManager::storage_el& aElement) { 328 | return aElement.snmpHandle_ == handle_; 329 | } 330 | }; 331 | } 332 | 333 | void SnmpSessionManager::removeClient(void* aSnmp) { 334 | storage_iterator it = std::find_if(storage_.begin(), storage_.end(), 335 | handleFind(aSnmp)); 336 | assert(it != storage_.end()); 337 | 338 | // only check_cb_impl is allowed to remove items from storage_ 339 | it->snmpHandle_ = NULL; 340 | } 341 | 342 | // }}} 343 | 344 | 345 | 346 | enum { VT_NUMBER, VT_TEXT, VT_OID, VT_RAW, VT_NULL }; 347 | 348 | // ===== class SnmpValue {{{ 349 | class SnmpValue : public node::ObjectWrap { 350 | private: 351 | static Persistent constructorTemplate_; 352 | 353 | u_char type_; 354 | std::vector data_; 355 | 356 | SnmpValue() {} 357 | 358 | public: 359 | static Handle GetType(const Arguments& args); 360 | static Handle GetData(const Arguments& args); 361 | 362 | static Handle New(u_char type, void* data, std::size_t length); 363 | 364 | static void Initialize(Handle target); 365 | }; 366 | 367 | Persistent SnmpValue::constructorTemplate_; 368 | 369 | // Handle SnmpValue::GetType(const Arguments& args) {{{ 370 | Handle SnmpValue::GetType(const Arguments& args) { 371 | SnmpValue* inst = ObjectWrap::Unwrap(args.This()); 372 | 373 | int kResult = VT_RAW; 374 | switch (inst->type_) { 375 | case ASN_INTEGER: 376 | case ASN_GAUGE: 377 | case ASN_COUNTER: 378 | case ASN_UINTEGER: 379 | case ASN_TIMETICKS: 380 | case ASN_COUNTER64: 381 | #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES 382 | case ASN_OPAQUE_I64: 383 | case ASN_OPAQUE_U64: 384 | case ASN_OPAQUE_COUNTER64: 385 | case ASN_OPAQUE_FLOAT: 386 | case ASN_OPAQUE_DOUBLE: 387 | #endif 388 | kResult = VT_NUMBER; 389 | break; 390 | case ASN_OCTET_STR: 391 | kResult = VT_TEXT; 392 | break; 393 | case ASN_OBJECT_ID: 394 | kResult = VT_OID; 395 | break; 396 | case ASN_NULL: 397 | kResult = VT_NULL; 398 | break; 399 | case ASN_BIT_STR: 400 | case ASN_OPAQUE: 401 | case ASN_IPADDRESS: 402 | kResult = VT_RAW; 403 | break; 404 | default: 405 | return v8::ThrowException( 406 | NODE_PSYMBOL("internal error, unexpected variable" 407 | "type received from net-snmp")); 408 | } 409 | return v8::Number::New(kResult); 410 | } 411 | // }}} 412 | 413 | // Handle SnmpValue::GetData(const Arguments& args) {{{ 414 | Handle SnmpValue::GetData(const Arguments& args) { 415 | HandleScope kScope; 416 | 417 | SnmpValue* inst = ObjectWrap::Unwrap(args.This()); 418 | 419 | netsnmp_vardata data; // union of pointers, it's enough to set one of 420 | // them 421 | data.string = inst->data_.data(); 422 | 423 | switch (inst->type_) { // snmpwalk dumps type as this: 424 | // (mib.c: snprint_variable) 425 | case ASN_INTEGER: // "%ld" on *val.integer 426 | return kScope.Close(v8::Int32::New(*data.integer)); 427 | #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES 428 | case ASN_OPAQUE_I64: // custom 32bit-compatible printer for "%lld" 429 | // (int64.c:printI64) on val.counter->high,low 430 | { 431 | uint64_t buf; 432 | buf = data.counter64->high; 433 | buf <<= 32; 434 | buf |= data.counter64->low; 435 | double num = *((int64_t*)buf); 436 | return kScope.Close(v8::Number::New(num)); 437 | } 438 | #endif 439 | case ASN_GAUGE: // "%u" on (unsigned int)(*val.integer & 0xffffffff)) 440 | case ASN_COUNTER: // -||- 441 | case ASN_UINTEGER: // "%lu" on *val.integer 442 | case ASN_TIMETICKS: // %lu applied to *var->integer 443 | return kScope.Close(v8::Uint32::New(*((unsigned int*)data.integer))); 444 | case ASN_COUNTER64: // custom 32bit-compatible printer for "%llu" 445 | #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES 446 | case ASN_OPAQUE_U64: // custom 32bit-compatible printer for "%llu" 447 | case ASN_OPAQUE_COUNTER64: // custom 32bit-compatible printer for "%llu" 448 | // (int64.c:printU64) 449 | #endif 450 | { 451 | uint64_t buf; 452 | buf = data.counter64->high; 453 | buf <<= 32; 454 | buf |= data.counter64->low; 455 | double num = buf; 456 | return kScope.Close(v8::Number::New(num)); 457 | } 458 | #ifdef NETSNMP_WITH_OPAQUE_SPECIAL_TYPES 459 | case ASN_OPAQUE_FLOAT: // %f applied to *var->floatVal 460 | { 461 | double num = *data.floatVal; 462 | return kScope.Close(v8::Number::New(num)); 463 | } 464 | case ASN_OPAQUE_DOUBLE: // %f applied to *var->doubleVal 465 | // XXX: shouldn't it be %lf? 466 | { 467 | double num = *data.doubleVal; 468 | return kScope.Close(v8::Number::New(num)); 469 | } 470 | #endif 471 | case ASN_OBJECT_ID: // when not translated by mib, use .X.Y.Z.... 472 | // applied to val->objid 473 | { 474 | assert((inst->data_.size() % sizeof(oid)) == 0); 475 | size_t end = inst->data_.size() / sizeof(oid); 476 | Local result = v8::Array::New(end); 477 | for (size_t i = 0; i < end; ++i) { 478 | double num = data.objid[i]; 479 | result->Set(i, v8::Number::New(num)); 480 | } 481 | return kScope.Close(result); 482 | } 483 | case ASN_OCTET_STR: // needs "output format" param. Guess algorithm 484 | // searches for !isprint character, print as hex 485 | // output if one is found, "%s" on val->string 486 | // otherwise 487 | case ASN_BIT_STR: // haven't encountered this one yet... 488 | case ASN_OPAQUE: // uchar as hex string 489 | case ASN_IPADDRESS: // print 4 uchar as IPv4 address 490 | { 491 | // many thanks to 492 | // http://sambro.is-super-awesome.com/2011/03/03/creating-a-proper-buffer-in-a-node-c-addon/ 493 | // for a guide how to return Buffer from a function. 494 | node::Buffer* slowBuffer = node::Buffer::New(inst->data_.size()); 495 | memcpy( 496 | node::Buffer::Data(slowBuffer), 497 | inst->data_.data(), 498 | inst->data_.size()); 499 | v8::Local globalObj = v8::Context::GetCurrent()->Global(); 500 | v8::Local bufferConstructor = 501 | v8::Local::Cast( 502 | globalObj->Get(v8::String::New("Buffer"))); 503 | v8::Handle constructorArgs[3] = { 504 | slowBuffer->handle_, 505 | v8::Integer::New(inst->data_.size()), 506 | v8::Integer::New(0) 507 | }; 508 | v8::Local result = 509 | bufferConstructor->NewInstance(3, constructorArgs); 510 | return kScope.Close(result); 511 | } 512 | case ASN_NULL: // buffer size is 0, print as "NULL" const 513 | // string 514 | { 515 | return kScope.Close(v8::Null()); 516 | } 517 | default: 518 | return kScope.Close(v8::ThrowException( 519 | NODE_PSYMBOL("internal error, unexpected variable " 520 | "type received from net-snmp"))); 521 | } 522 | } 523 | // }}} 524 | 525 | // Handle SnmpValue::New(...) {{{ 526 | Handle SnmpValue::New(u_char type, void* data, std::size_t length) { 527 | HandleScope kScope; 528 | 529 | SnmpValue* v = new SnmpValue(); 530 | v->type_ = type; 531 | v->data_.resize(length); 532 | memcpy(&v->data_[0], data, length); 533 | 534 | Local b = constructorTemplate_->GetFunction()->NewInstance(0, NULL); 535 | 536 | v->Wrap(b); 537 | return kScope.Close(b); 538 | } 539 | // }}} 540 | 541 | // stolen and modified from node.h 542 | #define SNMP_DEFINE_HIDDEN_CONSTANT(target, constant) \ 543 | (target)->Set(v8::String::NewSymbol(#constant), \ 544 | v8::Integer::New(constant), \ 545 | static_cast( \ 546 | v8::ReadOnly|v8::DontDelete|v8::DontEnum)) 547 | 548 | // void SnmpValue::Initialize(Handle target) {{{ 549 | void SnmpValue::Initialize(Handle target) { 550 | js::HandleScope kScope; 551 | 552 | Local t = FunctionTemplate::New(); 553 | constructorTemplate_ = Persistent::New(t); 554 | constructorTemplate_->InstanceTemplate()->SetInternalFieldCount(1); 555 | constructorTemplate_->SetClassName(String::NewSymbol("Value")); 556 | 557 | // t->Inherit(EventEmitter::constructor_template); 558 | 559 | NODE_SET_PROTOTYPE_METHOD(t, "GetType", SnmpValue::GetType); 560 | NODE_SET_PROTOTYPE_METHOD(t, "GetData", SnmpValue::GetData); 561 | 562 | target->Set(String::NewSymbol("Value"), 563 | constructorTemplate_->GetFunction()); 564 | 565 | // support x == SnmpValue.VT_NUMBER 566 | SNMP_DEFINE_HIDDEN_CONSTANT(t, VT_NUMBER); 567 | SNMP_DEFINE_HIDDEN_CONSTANT(t, VT_TEXT); 568 | SNMP_DEFINE_HIDDEN_CONSTANT(t, VT_OID); 569 | SNMP_DEFINE_HIDDEN_CONSTANT(t, VT_RAW); 570 | SNMP_DEFINE_HIDDEN_CONSTANT(t, VT_NULL); 571 | 572 | // support x == v.VT_NUMBER (v is of type Value) 573 | SNMP_DEFINE_HIDDEN_CONSTANT(t->InstanceTemplate(), VT_NUMBER); 574 | SNMP_DEFINE_HIDDEN_CONSTANT(t->InstanceTemplate(), VT_TEXT); 575 | SNMP_DEFINE_HIDDEN_CONSTANT(t->InstanceTemplate(), VT_OID); 576 | SNMP_DEFINE_HIDDEN_CONSTANT(t->InstanceTemplate(), VT_RAW); 577 | SNMP_DEFINE_HIDDEN_CONSTANT(t->InstanceTemplate(), VT_NULL); 578 | } 579 | // }}} 580 | 581 | // }}} 582 | 583 | 584 | 585 | // Using Array of Uint32 should be OK, OIDs are limited to 0..2^32 range, even 586 | // when the underlying type is 8 bytes integer on amd64 platforms (see 587 | // MAX_SUBID in net-snmp types.h). 588 | // v8::Handle read_objid_wrapper(const Arguments& args) {{{ 589 | v8::Handle read_objid_wrapper(const Arguments& args) { 590 | HandleScope kScope; 591 | 592 | if (args.Length() != 1) { 593 | return kScope.Close(v8::ThrowException( 594 | NODE_PSYMBOL("invalid arguments - missing aOid"))); 595 | } 596 | if (!args[0]->IsString()) { 597 | return kScope.Close(v8::ThrowException( 598 | NODE_PSYMBOL("invalid arguments - string expected"))); 599 | } 600 | 601 | oid oid[MAX_OID_LEN]; 602 | std::size_t oidLength = MAX_OID_LEN; 603 | 604 | 605 | if (!read_objid(*String::Utf8Value(args[0]->ToString()), oid, &oidLength)) { 606 | return kScope.Close(v8::ThrowException( 607 | NODE_PSYMBOL("invalid arguments - cannot parse oid"))); 608 | } 609 | 610 | Local result = v8::Array::New(oidLength); 611 | for (size_t i = 0; i < oidLength; ++i) { 612 | result->Set(i, 613 | v8::Integer::NewFromUnsigned( 614 | static_cast(oid[i] & 0xFFFFFFFF))); 615 | } 616 | return kScope.Close(result); 617 | } 618 | // }}} 619 | 620 | // v8::Handle parse_oid_wrapper(const Arguments& args) {{{ 621 | v8::Handle parse_oid_wrapper(const Arguments& args) { 622 | HandleScope kScope; 623 | 624 | if (args.Length() != 1) { 625 | return kScope.Close(v8::ThrowException( 626 | NODE_PSYMBOL("invalid arguments - missing aOid"))); 627 | } 628 | if (!args[0]->IsString()) { 629 | return kScope.Close(v8::ThrowException( 630 | NODE_PSYMBOL("invalid arguments - string expected"))); 631 | } 632 | 633 | oid oid[MAX_OID_LEN]; 634 | std::size_t oidLength = MAX_OID_LEN; 635 | 636 | 637 | if (!snmp_parse_oid(*String::Utf8Value(args[0]->ToString()), oid, &oidLength)) { 638 | return kScope.Close(v8::ThrowException( 639 | NODE_PSYMBOL("invalid arguments - cannot parse oid"))); 640 | } 641 | 642 | Local result = v8::Array::New(oidLength); 643 | for (size_t i = 0; i < oidLength; ++i) { 644 | result->Set(i, 645 | v8::Integer::NewFromUnsigned( 646 | static_cast(oid[i] & 0xFFFFFFFF))); 647 | } 648 | return kScope.Close(result); 649 | } 650 | // }}} 651 | 652 | 653 | 654 | 655 | 656 | 657 | // ===== class SnmpResult : public node::ObjectWrap {{{ 658 | class SnmpResult : public node::ObjectWrap { 659 | public: 660 | static Local New(netsnmp_variable_list* var); 661 | 662 | static void Initialize(Handle target); 663 | }; 664 | 665 | // Local SnmpResult::New(netsnmp_variable_list* var) {{{ 666 | Local SnmpResult::New(netsnmp_variable_list* var) { 667 | assert(var); 668 | HandleScope kScope; 669 | 670 | Local o = Object::New(); 671 | o->Set(String::New("oid"), 672 | SnmpValue::New(ASN_OBJECT_ID, 673 | var->name, var->name_length * sizeof(oid))); 674 | o->Set(String::New("value"), 675 | SnmpValue::New(var->type, var->val.string, var->val_len)); 676 | return kScope.Close(o); 677 | } 678 | // }}} 679 | 680 | // void SnmpResult::Initialize(Handle target) {{{ 681 | void SnmpResult::Initialize(Handle target) { 682 | } 683 | // }}} 684 | 685 | // }}} 686 | 687 | 688 | 689 | // ==== class SnmpSession : public node::ObjectWrap {{{ 690 | 691 | class SnmpSession : public node::ObjectWrap { 692 | public: 693 | typedef Persistent callback_type; 694 | 695 | struct self_data { 696 | SnmpSession* selfPtr_; 697 | }; 698 | 699 | enum req_type { 700 | REQ_NEXT = SNMP_MSG_GETNEXT , 701 | REQ_GET = SNMP_MSG_GET, 702 | REQ_BULK = SNMP_MSG_GETBULK 703 | }; 704 | 705 | struct req_data { 706 | netsnmp_pdu* pdu_; 707 | req_type type_; 708 | callback_type callback_; 709 | }; 710 | 711 | typedef std::deque queue_type; 712 | typedef queue_type::iterator queue_iterator; 713 | 714 | private: 715 | self_data selfData_; 716 | std::string hostName_; 717 | std::string credentials_; 718 | queue_type queue_; 719 | void* sessionHandle_; 720 | SnmpSessionManager* manager_; 721 | Persistent destructorInvoker_; 722 | 723 | private: // ctors 724 | SnmpSession() { 725 | selfData_.selfPtr_ = this; 726 | manager_ = SnmpSessionManager::default_inst(); 727 | #ifdef ENABLE_DEBUG_PRINTS 728 | fprintf(stdout, "SnmpSession()\n"); 729 | #endif 730 | }; 731 | 732 | private: // methods 733 | Handle PerformRequestImpl( 734 | req_type aType, netsnmp_pdu* pdu, callback_type aCallback); 735 | 736 | void snmp_success_cb( 737 | struct snmp_pdu* pdu, 738 | const req_data& magic 739 | ); 740 | 741 | void snmp_fail_cb( 742 | struct snmp_pdu* pdu, 743 | const req_data& magic, 744 | const char* reason 745 | ); 746 | 747 | int snmp_cb_proxy( 748 | int operation, 749 | netsnmp_session* session, 750 | int reqid, 751 | struct snmp_pdu* pdu 752 | ); 753 | 754 | // void getBulk( 755 | // const oid* aOID, std::size_t aOIDLen, 756 | // const callback_type& aCallback); 757 | 758 | /** 759 | * We can't mix sync and async queries in same session (they would use same 760 | * descriptor, and we don't want responses to earlier requests, or to allow 761 | * node to process other async operations, when in sync mode). Since sync 762 | * version is inefficient by definition, it doesn't matter if we add little 763 | * more inefficiency and open another session to same host. 764 | */ 765 | SnmpSession* Clone(SnmpSessionManager* aManager); 766 | 767 | private: // static callback proxy 768 | static int snmp_cb( 769 | int operation, 770 | netsnmp_session* session, 771 | int reqid, 772 | struct snmp_pdu* pdu, 773 | void* magic); 774 | 775 | private: // static interface for V8 integration 776 | static Persistent constructorTemplate_; 777 | 778 | static Handle PerformRequest(req_type aType, const Arguments& args); 779 | 780 | // entrypoints from V8, all just call 781 | static Handle Get(const Arguments& args); 782 | static Handle GetNext(const Arguments& args); 783 | static Handle GetBulk(const Arguments& args); 784 | 785 | static SnmpSession* New(const std::string& hostName, 786 | const std::string& credentials); 787 | 788 | static void Destroy(Persistent v, void* param); 789 | 790 | public: 791 | ~SnmpSession() { 792 | #ifdef ENABLE_DEBUG_PRINTS 793 | fprintf(stdout, "~SnmpSession()\n"); 794 | #endif 795 | if (sessionHandle_) { 796 | #ifdef ENABLE_DEBUG_PRINTS 797 | fprintf(stdout, "close handle %p\n", sessionHandle_); 798 | #endif 799 | snmp_sess_close(sessionHandle_); 800 | sessionHandle_ = NULL; 801 | } 802 | } 803 | 804 | static Handle New(const Arguments& args); 805 | static void Initialize(Handle target); 806 | }; 807 | 808 | Persistent SnmpSession::constructorTemplate_; 809 | 810 | // SnmpSession* SnmpSession::Clone(SnmpSessionManager* aManager) {{{ 811 | SnmpSession* SnmpSession::Clone(SnmpSessionManager* aManager) { 812 | SnmpSession* kResult = SnmpSession::New(hostName_, credentials_); 813 | kResult->manager_ = aManager; 814 | return kResult; 815 | } 816 | // }}} 817 | 818 | // Handle SnmpSession::PerformRequestImpl(...) {{{ 819 | Handle SnmpSession::PerformRequestImpl( 820 | req_type aType, netsnmp_pdu* pdu, callback_type aCallback) 821 | { 822 | HandleScope kScope; 823 | 824 | // net-snmp takes over the pdu pointer! 825 | if (!snmp_sess_send(sessionHandle_, pdu)) { 826 | return kScope.Close( 827 | v8::ThrowException(NODE_PSYMBOL("cannot send query"))); 828 | } 829 | queue_.resize(queue_.size() + 1); 830 | queue_.back().pdu_ = pdu; 831 | queue_.back().type_ = aType; 832 | queue_.back().callback_ = v8::Persistent::New(aCallback); 833 | if (queue_.size() == 1) { 834 | manager_->addClient(sessionHandle_); 835 | } 836 | return kScope.Close(v8::Undefined()); 837 | } 838 | // }}} 839 | 840 | // void SnmpSession::snmp_success_cb(...) {{{ 841 | void SnmpSession::snmp_success_cb( 842 | struct snmp_pdu* pdu, 843 | const req_data& magic 844 | ) 845 | { 846 | HandleScope kScope; 847 | 848 | netsnmp_variable_list* var = pdu->variables; 849 | Local kResult = v8::Array::New(0); 850 | uint32_t index = 0; 851 | 852 | for(; var; var = var->next_variable, ++index) { 853 | kResult->Set(index, SnmpResult::New(var)); 854 | } 855 | 856 | Handle args[2]; 857 | args[0] = v8::Boolean::New(false); 858 | args[1] = kResult; 859 | 860 | { 861 | TryCatch try_catch; 862 | 863 | magic.callback_->Call(v8::Context::GetCurrent()->Global(), 2, args); 864 | 865 | if (try_catch.HasCaught()) { 866 | node::FatalException(try_catch); 867 | } 868 | } 869 | } 870 | // }}} 871 | 872 | // void SnmpSession::snmp_fail_cb(...) {{{ 873 | void SnmpSession::snmp_fail_cb( 874 | struct snmp_pdu* pdu, 875 | const req_data& magic, 876 | const char* reason 877 | ) 878 | { 879 | HandleScope kScope; 880 | 881 | Handle args[2]; 882 | args[0] = v8::String::NewSymbol(reason, strlen(reason)); 883 | args[1] = v8::Null(); 884 | 885 | { 886 | // TryCatch try_catch; 887 | 888 | magic.callback_->Call(v8::Context::GetCurrent()->Global(), 2, args); 889 | 890 | // if (try_catch.HasCaught()) { 891 | // node::FatalException(try_catch); 892 | // } 893 | } 894 | } 895 | // }}} 896 | 897 | // int SnmpSession::snmp_cb_proxy(...) {{{ 898 | int SnmpSession::snmp_cb_proxy( 899 | int operation, 900 | netsnmp_session* session, 901 | int reqid, 902 | struct snmp_pdu* pdu) 903 | { 904 | queue_iterator it_end = queue_.end(); 905 | for (queue_iterator it = queue_.begin(); it != it_end; ++it) { 906 | if (it->pdu_->reqid == reqid) { 907 | // in some more extreme situations, *this can be deallocated inside 908 | // callback (by forcing GC cycle). Everything we want to do with instance 909 | // must be done before trying callback. 910 | req_data kReq = *it; 911 | 912 | queue_.erase(it); 913 | if (queue_.size() == 0) { 914 | manager_->removeClient(sessionHandle_); 915 | } 916 | 917 | if (operation != NETSNMP_CALLBACK_OP_RECEIVED_MESSAGE) { 918 | const char* msg; 919 | switch (operation) { 920 | case NETSNMP_CALLBACK_OP_TIMED_OUT: 921 | msg = "timeout"; 922 | break; 923 | case NETSNMP_CALLBACK_OP_SEND_FAILED: 924 | msg = "send failed"; 925 | break; 926 | case NETSNMP_CALLBACK_OP_CONNECT: 927 | msg = "connect failed"; 928 | break; 929 | case NETSNMP_CALLBACK_OP_DISCONNECT: 930 | msg = "peer has disconnected"; 931 | break; 932 | default: 933 | msg = "unknown snmp error"; 934 | } 935 | snmp_fail_cb(pdu, kReq, msg); 936 | kReq.callback_.Dispose(); 937 | return 1; 938 | } 939 | if (pdu->errstat != SNMP_ERR_NOERROR) { 940 | const char* msg = snmp_errstring(pdu->errstat); 941 | snmp_fail_cb(pdu, kReq, msg); 942 | kReq.callback_.Dispose(); 943 | return 1; 944 | } 945 | 946 | switch (it->type_) { 947 | case REQ_GET: 948 | snmp_success_cb(pdu, kReq); 949 | break; 950 | case REQ_NEXT: 951 | snmp_success_cb(pdu, kReq); 952 | break; 953 | case REQ_BULK: 954 | default: 955 | assert(false && "internal error: inconsistent req_data record"); 956 | snmp_fail_cb(pdu, kReq, 957 | "internal error: inconsistent req_data record"); 958 | break; 959 | } 960 | kReq.callback_.Dispose(); 961 | return 1; 962 | } 963 | } 964 | assert(false && "spurious response received"); 965 | return 1; 966 | } 967 | // }}} 968 | 969 | // void SnmpSession::snmp_cb(...) {{{ 970 | int SnmpSession::snmp_cb( 971 | int operation, 972 | netsnmp_session* session, 973 | int reqid, 974 | struct snmp_pdu* pdu, 975 | void* magic) 976 | { 977 | SnmpSession::self_data* instData = 978 | reinterpret_cast(magic); 979 | return instData->selfPtr_->snmp_cb_proxy(operation, session, reqid, pdu); 980 | } 981 | // }}} 982 | 983 | 984 | // SnmpSession* SnmpSession::New(hostname, community) {{{ 985 | SnmpSession* SnmpSession::New(const std::string& hostName, 986 | const std::string& credentials) 987 | { 988 | SnmpSession* kResult = new SnmpSession(); 989 | kResult->hostName_ = hostName; 990 | kResult->credentials_ = credentials; 991 | 992 | netsnmp_session kSession; 993 | snmp_sess_init(&kSession); 994 | kSession.peername = strdup(kResult->hostName_.c_str()); 995 | kSession.version = SNMP_VERSION_1; 996 | 997 | kSession.community = (u_char*)malloc(kResult->credentials_.size() + 1); 998 | // TODO: some better way to report out of memory instead of SIGSEGV? 999 | memcpy(kSession.community, kResult->credentials_.c_str(), 1000 | kResult->credentials_.size() + 1); 1001 | kSession.community_len = kResult->credentials_.size(); 1002 | 1003 | kSession.callback = SnmpSession::snmp_cb; 1004 | kSession.callback_magic = &kResult->selfData_; 1005 | 1006 | kResult->sessionHandle_ = snmp_sess_open(&kSession); 1007 | #ifdef ENABLE_DEBUG_PRINTS 1008 | fprintf(stderr, "new session handle %p\n", kResult->sessionHandle_); 1009 | #endif 1010 | kResult->manager_ = SnmpSessionManager::default_inst(); 1011 | free(kSession.community); 1012 | free(kSession.peername); 1013 | 1014 | if (!kResult->sessionHandle_) { 1015 | delete kResult; 1016 | return NULL; 1017 | } 1018 | return kResult; 1019 | } 1020 | // }}} 1021 | 1022 | // Handle SnmpSession::New(const Arguments& args) {{{ 1023 | Handle SnmpSession::New(const Arguments& args) { 1024 | HandleScope kScope; 1025 | std::auto_ptr kInst; 1026 | 1027 | if (args.Length() < 2 || !args[0]->IsString() || !args[1]->IsString()) { 1028 | return kScope.Close(v8::ThrowException( 1029 | NODE_PSYMBOL("not enough arguments or wrong type" 1030 | " (expecting two strings)"))); 1031 | } 1032 | 1033 | { 1034 | v8::String::Utf8Value hostname(args[0]->ToString()); 1035 | v8::String::Utf8Value credentials(args[1]->ToString()); 1036 | kInst.reset(SnmpSession::New( 1037 | std::string(*hostname, hostname.length()), 1038 | std::string(*credentials, credentials.length()) 1039 | )); 1040 | } 1041 | 1042 | if (!kInst.get()) { 1043 | return kScope.Close(v8::ThrowException( 1044 | NODE_PSYMBOL("cannot open snmp session"))); 1045 | } 1046 | 1047 | kInst->Wrap(args.This()); 1048 | /* Persistent p = */ 1049 | kInst->destructorInvoker_ = Persistent::New(args.This()); 1050 | kInst->destructorInvoker_.MakeWeak(NULL, SnmpSession::Destroy); 1051 | kInst.release(); 1052 | return kScope.Close(args.This()); 1053 | } 1054 | // }}} 1055 | 1056 | // void SnmpSession::Destroy(Persistent v, void* param) {{{ 1057 | void SnmpSession::Destroy(Persistent v, void* param) { 1058 | assert(v->IsObject()); 1059 | SnmpSession* s = ObjectWrap::Unwrap(v->ToObject()); 1060 | #ifdef ENABLE_DEBUG_PRINTS 1061 | fprintf(stderr, "destroy persistent for %p\n", s); 1062 | #endif 1063 | delete s; 1064 | v.Dispose(); 1065 | } 1066 | // }}} 1067 | 1068 | namespace { 1069 | // Local addNullVarFromV8Array(...) {{{ 1070 | void addNullVarFromV8Array(netsnmp_pdu* pdu, Local var, 1071 | std::vector* tmp) 1072 | { 1073 | // handleScope - intentionally omited, use scope from PerformRequest 1074 | if (!var->IsArray()) { 1075 | v8::ThrowException( 1076 | NODE_PSYMBOL("invalid argument - not an array")); 1077 | return; 1078 | } 1079 | Local a = Local::Cast(var); 1080 | size_t end = a->Length(); 1081 | if (end == 0) { 1082 | v8::ThrowException( 1083 | NODE_PSYMBOL("invalid argument - empty oid")); 1084 | return; 1085 | } 1086 | tmp->resize(end); 1087 | for (size_t i = 0; i < end; ++i) { 1088 | Local v = a->Get(i); 1089 | if (!v->IsUint32()) { 1090 | v8::ThrowException( 1091 | NODE_PSYMBOL("invalid oid - non-integer member")); 1092 | return; 1093 | } 1094 | (*tmp)[i] = v->ToUint32()->Value(); 1095 | } 1096 | if (!snmp_add_null_var(pdu, &((*tmp)[0]), tmp->size())) { 1097 | v8::ThrowException(NODE_PSYMBOL("cannot add query to pdu")); 1098 | return; 1099 | } 1100 | } 1101 | // }}} 1102 | } 1103 | 1104 | // Handle SnmpSession::PerformRequest(...) {{{ 1105 | Handle SnmpSession::PerformRequest( 1106 | req_type aType, const Arguments& args) 1107 | { 1108 | HandleScope kScope; 1109 | SnmpSession* inst = ObjectWrap::Unwrap(args.This()); 1110 | 1111 | // call with (OID, callback, bool (=sync or not sync)) 1112 | if (args.Length() < 3) { 1113 | return kScope.Close(v8::ThrowException(NODE_PSYMBOL("missing arguments"))); 1114 | } 1115 | if (!args[0]->IsArray()) { 1116 | return kScope.Close(v8::ThrowException( 1117 | NODE_PSYMBOL("invalid arguments - only string OID is supported"))); 1118 | } 1119 | if (!args[1]->IsFunction()) { 1120 | return kScope.Close(v8::ThrowException( 1121 | NODE_PSYMBOL("invalid arguments - callback is not a function"))); 1122 | } 1123 | if (!args[2]->IsBoolean()) { 1124 | return kScope.Close(v8::ThrowException( 1125 | NODE_PSYMBOL("invalid argument - sync flag must be boolean"))); 1126 | } 1127 | 1128 | Local kOidArg = Local::Cast(args[0]); 1129 | 1130 | netsnmp_pdu* pdu = NULL; 1131 | 1132 | { 1133 | size_t end = kOidArg->Length(); 1134 | if (end == 0) { 1135 | return kScope.Close(v8::ThrowException( 1136 | NODE_PSYMBOL("invalid argument - empty oid"))); 1137 | } 1138 | 1139 | pdu = snmp_pdu_create(static_cast(aType)); 1140 | if (!pdu) { 1141 | return kScope.Close( 1142 | v8::ThrowException(NODE_PSYMBOL("cannot allocate pdu"))); 1143 | } 1144 | 1145 | std::vector tmp; 1146 | v8::TryCatch tryCatch; 1147 | 1148 | if (kOidArg->Get(0)->IsArray()) { 1149 | // array of arrays - second level arrays must contain integers 1150 | for (size_t i = 0; i < end; ++i) { 1151 | addNullVarFromV8Array(pdu, kOidArg->Get(i), &tmp); 1152 | if (tryCatch.HasCaught()) { 1153 | return kScope.Close(tryCatch.ReThrow()); 1154 | } 1155 | } 1156 | } else { 1157 | // array of integers - single oid query 1158 | addNullVarFromV8Array(pdu, kOidArg, &tmp); 1159 | if (tryCatch.HasCaught()) { 1160 | return kScope.Close(tryCatch.ReThrow()); 1161 | } 1162 | } 1163 | } 1164 | 1165 | if (args[2]->BooleanValue()) { 1166 | #if EV_MULTIPLICITY 1167 | struct ev_loop* our_loop = ev_loop_new(0); 1168 | SnmpSessionManager* manager = SnmpSessionManager::create(our_loop); 1169 | SnmpSession* cloned_sess = inst->Clone(manager); 1170 | 1171 | cloned_sess->PerformRequestImpl(aType, pdu, 1172 | Persistent(Function::Cast(*args[1]))); 1173 | 1174 | #if EV_VERSION_MAJOR == 3 1175 | ev_loop(our_loop, 0); 1176 | #else 1177 | ev_run(our_loop); 1178 | #endif 1179 | 1180 | delete cloned_sess; 1181 | delete manager; 1182 | ev_loop_destroy(our_loop); 1183 | #else 1184 | return kScope.Close( 1185 | v8::ThrowException( 1186 | NODE_PSYMBOL("synchronous interface is unavailable due to" 1187 | " binding module configuration"))); 1188 | #endif 1189 | } else { 1190 | inst->PerformRequestImpl(aType, pdu, 1191 | Persistent(Function::Cast(*args[1]))); 1192 | } 1193 | return kScope.Close(v8::Undefined()); 1194 | } 1195 | // }}} 1196 | 1197 | // Handle SnmpSession::Get(const Arguments& args) {{{ 1198 | Handle SnmpSession::Get(const Arguments& args) { 1199 | return PerformRequest(REQ_GET, args); 1200 | } 1201 | // }}} 1202 | 1203 | // Handle SnmpSession::GetNext(const Arguments& args) {{{ 1204 | Handle SnmpSession::GetNext(const Arguments& args) { 1205 | return PerformRequest(REQ_NEXT, args); 1206 | } 1207 | // }}} 1208 | 1209 | // Handle SnmpSession::GetBulk(const Arguments& args) {{{ 1210 | Handle SnmpSession::GetBulk(const Arguments& args) { 1211 | return PerformRequest(REQ_BULK, args); 1212 | } 1213 | // }}} 1214 | 1215 | 1216 | // void SnmpSession::Initialize(Handle target) {{{ 1217 | void SnmpSession::Initialize(Handle target) { 1218 | js::HandleScope kScope; 1219 | 1220 | Local t = FunctionTemplate::New(SnmpSession::New); 1221 | constructorTemplate_ = Persistent::New(t); 1222 | constructorTemplate_->InstanceTemplate()->SetInternalFieldCount(1); 1223 | constructorTemplate_->SetClassName(String::NewSymbol("Connection")); 1224 | 1225 | // t->Inherit(EventEmitter::constructor_template); 1226 | 1227 | NODE_SET_PROTOTYPE_METHOD(t, "Get", SnmpSession::Get); 1228 | NODE_SET_PROTOTYPE_METHOD(t, "GetNext", SnmpSession::GetNext); 1229 | 1230 | target->Set(String::NewSymbol("Connection"), 1231 | constructorTemplate_->GetFunction()); 1232 | } 1233 | // }}} 1234 | 1235 | // }}} 1236 | 1237 | 1238 | extern "C" void 1239 | init (Handle target) { 1240 | HandleScope scope; 1241 | 1242 | init_snmp("asdf"); 1243 | 1244 | SnmpSession::Initialize(target); 1245 | SnmpValue::Initialize(target); 1246 | SnmpResult::Initialize(target); 1247 | 1248 | NODE_SET_METHOD(target, "read_objid", read_objid_wrapper); 1249 | NODE_SET_METHOD(target, "parse_oid", parse_oid_wrapper); 1250 | } 1251 | 1252 | // vim: ts=2 fdm=marker syntax=cpp expandtab sw=2 1253 | 1254 | -------------------------------------------------------------------------------- /wscript: -------------------------------------------------------------------------------- 1 | import Options, Utils 2 | from os import unlink, symlink, chdir 3 | from os.path import exists 4 | 5 | srcdir = '.' 6 | blddir = 'build' 7 | VERSION = '0.0.1' 8 | 9 | def set_options(opt): 10 | opt.tool_options('compiler_cxx') 11 | 12 | def configure(conf): 13 | conf.check_tool('compiler_cxx') 14 | conf.check_tool('node_addon') 15 | 16 | # conf.env.append_unique('CPPFLAGS', ["-I/usr/local/include"]) 17 | # conf.env.append_unique('CXXFLAGS', ["-Wall"]) 18 | 19 | # conf.env.append_unique('LINKFLAGS', ["-L/usr/local/lib"]) 20 | 21 | 22 | def build(bld): 23 | obj = bld.new_task_gen('cxx', 'shlib', 'node_addon') 24 | obj.target = 'snmp_binding' 25 | obj.source = './src/snmp_binding.cc' 26 | obj.lib = ['snmp'] 27 | 28 | --------------------------------------------------------------------------------